站长信息
jeffery.xu
jeffery.xu

软件工程师

欢迎访问我的个人笔记网站!我是一名热爱技术的开发者,专注于Web开发和技术分享。

811495111@qq.com
18521510875
筛选

个人笔记

依赖倒置原则(Dependency Inversion Principle, DIP)
设计模式
  1. 高层模块不应依赖低层模块,二者都应依赖抽象。
  2. 抽象不应依赖细节,细节应依赖抽象。
    示例
    # 反例:高层模块依赖低层模块
    class LightBulb:
        def turn_on(self):
            print("灯泡亮了")
    
    class Switch:
        def __init__(self):
            self.bulb = LightBulb()  # 直接依赖具体实现
    
    # 正例:依赖抽象
    from abc import ABC, abstractmethod
    
    class Switchable(ABC):
        @abstractmethod
        def turn_on(self):
            pass
    
    class LightBulb(Switchable):
        def turn_on(self):
            print("灯泡亮了")
    
    class Fan(Switchable):
        def turn_on(self):
            print("风扇转了")
    
    class Switch:
        def __init__(self, device: Switchable):
            self.device = device  # 依赖抽象而非具体实现
接口隔离原则(Interface Segregation Principle, ISP)
设计模式

客户端不应该被迫依赖它不需要的接口。应该把庞大的接口拆分成更小、更具体的接口。

# 反例:一个大而全的接口
class Worker:
    def work(self):
        pass
    def eat(self):
        pass

class Robot(Worker):
    def work(self):
        pass
    def eat(self):
        raise NotImplementedError("机器人不需要吃饭")  # 被迫实现不需要的方法

# 正例:拆分接口
class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

class HumanWorker(Workable, Eatable):
    pass

class RobotWorker(Workable):
    pass
运费模块设计
新框架
  1. 运费模板表
    CREATE TABLE TBFreightTemplate (
        ID uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),           -- 主键ID
        TemplateName nvarchar(100) NOT NULL,                                 -- 模板名称
        FK_TBMerchant_ID uniqueidentifier NOT NULL,                         -- 关联商户ID
        FK_TBStore_ID uniqueidentifier,                                     -- 关联门店ID
        TemplateType tinyint NOT NULL DEFAULT 1,                            -- 模板类型(1-按件数,2-按重量,3-按金额)
        DeliveryMethod tinyint NOT NULL,                                     -- 配送方式(1-快递,2-自提,3-同城配送,4-不配送)
        IsFreeShipping bit NOT NULL DEFAULT 0,                              -- 是否包邮(0-否,1-是)
        IsDefault bit NOT NULL DEFAULT 0,                                   -- 是否默认模板(0-否,1-是)
        IsAllRegion bit NOT NULL DEFAULT 1,                                 -- 是否全国配送(0-否,1-是)
        FreeShippingCondition tinyint,                                      -- 包邮条件类型(1-满件数包邮,2-满金额包邮,3-满重量包邮)
        FreeShippingValue decimal(10,2),                                    -- 包邮条件值
        ReturnValue tinyint,                                                 -- 退货运费:0:卖家承担;1:买家承担
        TemplateStatus tinyint NOT NULL DEFAULT 1,                          -- 模板状态(0-禁用,1-启用)
        CreateTime datetime NOT NULL DEFAULT GETDATE(),                 -- 创建时间
        FK_SystemUser_Create_ID uniqueidentifier,                           -- 创建人ID
        ModifyTime datetime NOT NULL DEFAULT GETDATE(),                 -- 最后更新时间
        FK_SystemUser_Modify_ID uniqueidentifier,                           -- 最后更新人ID
        IsValid bit NOT NULL DEFAULT 1,                                     -- 是否有效(0-无效,1-有效)
        IsDelete bit NOT NULL DEFAULT 0,                                    -- 是否删除(0-未删除,1-已删除)
        FK_Systemuserdepartment_Create_ID uniqueidentifier,                 -- 创建部门ID
        Sort int DEFAULT 0,                                                  -- 排序
        Memo nvarchar(500),                                                  -- 备注
    );
    
    -- 运费模板配送区域表
    CREATE TABLE TBFreightTemplateRegion (
        ID uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),           -- 主键ID
        FK_TBFreightTemplate_ID uniqueidentifier NOT NULL,                  -- 关联运费模板ID
        RegionName nvarchar(100) NOT NULL,                                  -- 配送区域名称
        ProvIds nvarchar(max),                                               -- 省份编码列表(JSON格式)
        CityIds nvarchar(max),                                               -- 城市编码列表(JSON格式) 可为空
        DistrictIds nvarchar(max),                                           -- 区县编码列表(JSON格式) 可为空
        DeliveryType tinyint NOT NULL,                                       -- 配送类型(1-配送,2-不配送)
        FirstUnit int NOT NULL DEFAULT 1,                                   -- 首件/首重/首体积
        FirstPrice decimal(10,2) NOT NULL DEFAULT 0.00,                     -- 首件/首重/首体积价格
        ContinueUnit int NOT NULL DEFAULT 1,                                -- 续件/续重/续体积
        ContinuePrice decimal(10,2) NOT NULL DEFAULT 0.00,                  -- 续件/续重/续体积价格
        FreeShippingCondition tinyint,                                      -- 区域包邮条件类型(1-满件数包邮,2-满金额包邮,3-满重量包邮)
        FreeShippingValue decimal(10,2),                                    -- 区域包邮条件值
        IsRegionFree bit NOT NULL DEFAULT 0,                                -- 该区域是否包邮(0-否,1-是)
        CreateTime datetime NOT NULL DEFAULT GETDATE(),                 -- 创建时间
        FK_SystemUser_Create_ID uniqueidentifier,                           -- 创建人ID
        ModifyTime datetime NOT NULL DEFAULT GETDATE(),                 -- 最后更新时间
        FK_SystemUser_Modify_ID uniqueidentifier,                           -- 最后更新人ID
        IsValid bit NOT NULL DEFAULT 1,                                     -- 是否有效(0-无效,1-有效)
        IsDelete bit NOT NULL DEFAULT 0,                                    -- 是否删除(0-未删除,1-已删除)
        FK_Systemuserdepartment_Create_ID uniqueidentifier,                 -- 创建部门ID
        Sort int DEFAULT 0,                                                  -- 排序
        Memo nvarchar(500),                                                  -- 备注
    );
    
    -- 运费模板计费详情表
    CREATE TABLE TBFreightTemplateDetail (
        ID uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),           -- 主键ID
        FK_TBFreightTemplateRegion_ID uniqueidentifier NOT NULL,            -- 关联运费模板区域ID
        MinValue decimal(10,3) NOT NULL,                                    -- 最小值(件数/重量/体积)
        MaxValue decimal(10,3),                                             -- 最大值(件数/重量/体积)
        Price decimal(10,2) NOT NULL,                                       -- 该区间价格
        PriceType tinyint NOT NULL,                                         -- 价格类型(1-固定价格,2-每单位价格)
        CreateTime datetime NOT NULL DEFAULT GETDATE(),                 -- 创建时间
        FK_SystemUser_Create_ID uniqueidentifier,                           -- 创建人ID
        ModifyTime datetime NOT NULL DEFAULT GETDATE(),                 -- 最后更新时间
        FK_SystemUser_Modify_ID uniqueidentifier,                           -- 最后更新人ID
        IsValid bit NOT NULL DEFAULT 1,                                     -- 是否有效(0-无效,1-有效)
        IsDelete bit NOT NULL DEFAULT 0,                                    -- 是否删除(0-未删除,1-已删除)
        FK_Systemuserdepartment_Create_ID uniqueidentifier,                 -- 创建部门ID
        Sort int DEFAULT 0,                                                  -- 排序
        Memo nvarchar(500),                                                  -- 备注
    );
Json.ToJson乱码解决
编程技巧

  // 使用JsonConvert.SerializeObject保留中文
  var jsonSettings = new JsonSerializerSettings
  {
      StringEscapeHandling = StringEscapeHandling.Default
  };
  
  data.RequestBody = JsonConvert.SerializeObject(resultJson, jsonSettings);
  
  // 这里是关键 - 使用JsonConvert.SerializeObject来序列化整个data对象
  string postData = JsonConvert.SerializeObject(data, jsonSettings);
  
  // 使用序列化后的字符串而不是JSON.ToJSON(data)

 var content = HttpTools.PostHttpNHSHelp(url, postData, "同步答卷结果");

里氏替换原则(Liskov Substitution Principle, LSP)
设计模式

里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计的核心原则之一,由芭芭拉・利斯科夫(Barbara Liskov)在 1987 年提出。其核心思想是:子类对象必须能够替换掉它们的父类对象,而不影响程序的正确性。这意味着,子类应当遵循父类的契约,保持行为的一致性。

核心动机

  • 继承的正确性:确保子类不会破坏父类的原有功能。
  • 多态的可靠性:通过父类引用调用子类方法时,结果应符合预期。
  • 降低系统风险:避免因子类行为异常导致的潜在错误。

LSP 的关键要求

  1. 前置条件不能强化:子类方法的前置条件(输入参数)不能比父类更严格。
  2. 后置条件不能弱化:子类方法的后置条件(返回值)必须满足父类的要求。
  3. 不变式要保持:子类不能改变父类的不变量(如类的约束条件)。
  4. 异常要兼容:子类抛出的异常应当与父类一致或为其子类。

C# 示例:矩形与正方形问题

违反 LSP 的经典案例

假设我们有一个矩形类和一个正方形类,正方形继承自矩形:
// 基类:矩形
public class Rectangle
{
    public virtual double Width { get; set; }
    public virtual double Height { get; set; }

    public double Area() => Width * Height;
}

// 子类:正方形(错误设计)
public class Square : Rectangle
{
    // 正方形的宽和高必须相等
    public override double Width
    {
        get => base.Width;
        set
        {
            base.Width = value;
            base.Height = value;
        }
    }

    public override double Height
    {
        get => base.Height;
        set
        {
            base.Height = value;
            base.Width = value;
        }
    }
}

问题分析

这个设计违反了 LSP,因为:
  • 前置条件被强化:父类允许设置不同的宽和高,但子类强制二者相等。
  • 行为不一致:以下代码在使用父类时正常,但使用子类时会出错:
    public void TestRectangle(Rectangle rect)
    {
        rect.Width = 5;
        rect.Height = 10;
        Console.WriteLine(rect.Area()); // 预期输出50
    }
    
    // 测试
    var rectangle = new Rectangle();
    TestRectangle(rectangle); // 输出50,正确
    
    var square = new Square();
    TestRectangle(square);    // 输出100,错误!

遵循 LSP 的重构方案

方案 1:抽象公共接口

// 抽象接口:四边形
public interface IQuadrilateral
{
    double Width { get; }
    double Height { get; }
    double Area();
}

// 矩形实现
public class Rectangle : IQuadrilateral
{
    public double Width { get; set; }
    public double Height { get; set; }
    public double Area() => Width * Height;
}

// 正方形实现
public class Square : IQuadrilateral
{
    private double _side;
    
    public double Width
    {
        get => _side;
        set => _side = value;
    }
    
    public double Height
    {
        get => _side;
        set => _side = value;
    }
    
    public double Area() => _side * _side;
}

方案 2:组合而非继承

// 正方形包含一个矩形实例
public class Square
{
    private readonly Rectangle _rectangle = new Rectangle();
    
    public double Side
    {
        get => _rectangle.Width;
        set
        {
            _rectangle.Width = value;
            _rectangle.Height = value;
        }
    }
    
    public double Area() => _rectangle.Area();
}

另一个示例:银行账户与透支账户

违反 LSP 的设计

// 基类:银行账户
public class BankAccount
{
    protected double _balance;
    
    public virtual void Withdraw(double amount)
    {
        if (amount > _balance)
            throw new InvalidOperationException("余额不足");
        
        _balance -= amount;
    }
}

// 子类:透支账户(错误设计)
public class OverdraftAccount : BankAccount
{
    private double _overdraftLimit = 1000;
    
    // 重写取款方法,允许透支
    public override void Withdraw(double amount)
    {
        if (amount > _balance + _overdraftLimit)
            throw new InvalidOperationException("超过透支限额");
            
        _balance -= amount;
    }
}

问题分析

  • 前置条件被弱化:子类允许更多情况下的取款(透支),而父类不允许。
  • 行为不一致:依赖父类行为的代码(如检查余额)在使用子类时会失效。

LSP 的其他应用场景

  1. 集合类中的协变与逆变:确保泛型集合的类型安全。
  2. 事件处理:子类事件的参数应与父类兼容。
  3. 数据库操作:子类的查询条件不能比父类更严格。

总结

  • LSP 是继承的契约:子类必须遵守父类的行为约定。
  • 避免破坏父类行为:重写方法时不要改变原有的语义。
  • 优先使用组合:当继承导致 LSP 违反时,考虑使用组合或接口替代。
通过遵循 LSP,代码可以保持良好的可替换性和扩展性,降低系统的耦合度。
托管执行和CLI
编程技巧

处理器不能直接解释程序集。.NET程序集用的是另一种语言,即公共中间语言(Common Intermediate Language,CIL),或称中间语言(IL)。C#编译器将C#源代码转换成中间语言。为了将CIL代码转换成处理器能够理解的的机器码,还要完成一个额外的步骤(通常在运行时进行)。该步骤涉及C#程序执行的一个重要元素:VES(Virtual Execution System, 虚拟执行系统)。VES也称为”运行时“(runtime)。它根据需要编译CIL代码,这个过程称为即时编译JIT编译(just-in-time compilation)。如果代码在像”运行时“这样的一个”代理“的上下文中执行,那么就称为托管代码(managed code),在”运行时“的控制下执行的过程则称为托管执行(managed execution)。之所以称为”托管“,是因为”运行时“管理着诸如内存分配、安全性、JIT编译等方面,从而控制了主要的程序行为。执行时不需要”运行时“的代码则称为本机代码(native code)非托管代码(unmanaged code)

开闭原则(Open/Closed Principle, OCP)
设计模式
开闭原则(Open/Closed Principle, OCP)是面向对象设计的核心原则之一,由 Bertrand Meyer 提出。其核心思想是:软件实体(类、模块、函数等)应当对扩展开放,对修改关闭。这意味着在添加新功能时,应该通过扩展现有代码而非修改它来实现。

核心动机

  • 减少风险:修改现有代码可能引入新的错误,影响原有功能。
  • 提高可维护性:代码无需频繁修改,降低维护成本。
  • 促进复用:通过抽象和多态,可复用现有设计框架。

实现方式

  1. 抽象化:通过接口或抽象类定义稳定的契约。
  2. 多态:利用子类实现行为的扩展。
  3. 依赖注入:通过依赖倒置,使高层模块不依赖具体实现。

C# 示例:报表生成器

假设你正在开发一个报表生成器,初始需求是生成 PDF 报表。随着业务发展,需要支持 Excel 和 CSV 格式。
public class ReportGenerator
{
    public void GenerateReport(string format, ReportData data)
    {
        if (format == "PDF")
        {
            // 生成 PDF 报表的具体实现
            Console.WriteLine("生成 PDF 报表");
        }
        else if (format == "Excel")
        {
            // 生成 Excel 报表的具体实现
            Console.WriteLine("生成 Excel 报表");
        }
        // 问题:每次新增格式都需要修改此方法
    }
}

遵循开闭原则的重构

通过抽象化和多态,将报表生成逻辑封装到独立的类中:
// 定义报表生成器接口(抽象)
public interface IReportGenerator
{
    void Generate(ReportData data);
}

// 具体实现:PDF 报表生成器
public class PdfReportGenerator : IReportGenerator
{
    public void Generate(ReportData data)
    {
        Console.WriteLine("生成 PDF 报表");
    }
}

// 具体实现:Excel 报表生成器
public class ExcelReportGenerator : IReportGenerator
{
    public void Generate(ReportData data)
    {
        Console.WriteLine("生成 Excel 报表");
    }
}

// 新增格式:CSV 报表生成器(无需修改原有代码)
public class CsvReportGenerator : IReportGenerator
{
    public void Generate(ReportData data)
    {
        Console.WriteLine("生成 CSV 报表");
    }
}

// 报表服务:依赖抽象接口
public class ReportService
{
    private readonly IReportGenerator _generator;

    public ReportService(IReportGenerator generator)
    {
        _generator = generator; // 通过构造函数注入依赖
    }

    public void CreateReport(ReportData data)
    {
        _generator.Generate(data);
    }
}

// 使用示例
public class Program
{
    public static void Main()
    {
        var data = new ReportData();
        
        // 需要 PDF 报表时
        var pdfService = new ReportService(new PdfReportGenerator());
        pdfService.CreateReport(data);

        // 需要 Excel 报表时
        var excelService = new ReportService(new ExcelReportGenerator());
        excelService.CreateReport(data);

        // 需要 CSV 报表时(扩展无需修改原有代码)
        var csvService = new ReportService(new CsvReportGenerator());
        csvService.CreateReport(data);
    }
}

关键改进点

  1. 抽象化:通过 IReportGenerator 接口定义稳定的报表生成契约。
  2. 多态:每个报表格式(PDF/Excel/CSV)都实现该接口,行为由具体子类决定。
  3. 依赖注入ReportService 依赖接口而非具体实现,支持动态切换报表生成器。

新增需求:添加 HTML 报表

若需新增 HTML 报表,只需:
  1. 创建 HtmlReportGenerator 类实现 IReportGenerator
  2. 在调用处注入新的生成器,无需修改现有类。
  3. public class HtmlReportGenerator : IReportGenerator
    {
        public void Generate(ReportData data)
        {
            Console.WriteLine("生成 HTML 报表");
        }
    }
    
    // 使用时直接注入新实现
    var htmlService = new ReportService(new HtmlReportGenerator());
    htmlService.CreateReport(data);

    开闭原则的其他应用场景

    1. 插件系统:通过接口定义插件规范,新插件只需实现接口。
    2. 策略模式:将算法封装为策略类,运行时动态切换。
    3. 事件驱动架构:通过事件和监听器实现功能扩展。

    注意事项

    • 过度抽象风险:不要为未来可能的需求过度设计,遵循 YAGNI(You Aren't Gonna Need It)原则。
    • 平衡点:对可能变化的部分应用 OCP,对稳定部分无需过度抽象。
    通过开闭原则,代码可以优雅地应对变化,同时保持稳定性和可维护性。
bundler压缩出现问题
编程技巧

问题出在函数声明的位置和压缩工具处理函数声明的方式上。
在JavaScript中,函数声明提升是很常见的,但是在压缩过程中,当函数声明放在代码中间而不是顶部时,可能会导致问题。

采用将 togglePanel 改为函数表达式的方法

document.addEventListener('DOMContentLoaded', function () {
    // 获取可折叠面板的标题元素
    const collapsibleHeader = document.querySelector('.collapsible-header');
    const collapseElement = document.getElementById('relatedNotesCollapse');
    const toggleIcon = document.querySelector('.toggle-icon');
    const mainContent = document.querySelector('.col-md-8');
    const sidePanel = document.querySelector('.col-md-4');
    const noteContent = document.querySelector('.note-content-detail');
    const floatBtn = document.querySelector('.float-toggle-btn');

    if (!collapsibleHeader || !collapseElement || !toggleIcon || !mainContent || !sidePanel) {
        // 如果找不到必要的元素,就退出
        return;
    }

    // 切换面板状态的函数(使用函数表达式而不是函数声明)
    const togglePanel = function() {
        const isExpanded = !sidePanel.classList.contains('collapsed');

        // 只在非移动设备上更新状态
        if (window.innerWidth >= 768) {
            // 更新本地存储中的状态
            localStorage.setItem('relatedNotesCollapsed', isExpanded);

            if (isExpanded) {
                // 收缩
                sidePanel.classList.add('collapsed');
                mainContent.classList.add('expanded');

                // 隐藏内容区域
                collapseElement.classList.remove('show');

                // 显示悬浮按钮
                if (floatBtn) {
                    floatBtn.classList.add('visible');
                }
            } else {
                // 展开
                sidePanel.classList.remove('collapsed');
                mainContent.classList.remove('expanded');

                // 显示内容区域
                collapseElement.classList.add('show');

                // 隐藏悬浮按钮
                if (floatBtn) {
                    floatBtn.classList.remove('visible');
                }
            }
        }
    };

    // 从本地存储中获取面板状态(如果有)
    const isCollapsed = localStorage.getItem('relatedNotesCollapsed') === 'true';

    // 初始化面板状态
    if (isCollapsed && window.innerWidth >= 768) {
        // 只在非移动设备上应用收缩状态
        // 收缩
        sidePanel.classList.add('collapsed');
        mainContent.classList.add('expanded');

        // 隐藏内容区域
        collapseElement.classList.remove('show');

        // 显示悬浮按钮
        if (floatBtn) {
            floatBtn.classList.add('visible');
        }
    }

    // 添加点击事件监听器,仅在非移动设备下启用
    collapsibleHeader.addEventListener('click', function (e) {
        // 在移动设备下阻止叉号图标的点击事件
        if (window.innerWidth < 768 && e.target.classList.contains('toggle-icon')) {
            e.stopPropagation();  // 阻止事件传播
            return;  // 在移动设备上不处理叉号图标点击
        }

        // 非移动设备下正常切换面板
        if (window.innerWidth >= 768) {
            togglePanel();
        }
    });

    // 特别为移动设备处理叉号图标
    if (toggleIcon) {
        toggleIcon.addEventListener('click', function (e) {
            // 在移动设备下阻止点击事件
            if (window.innerWidth < 768) {
                e.stopPropagation();
                e.preventDefault();
            }
        });
    }

    // 如果存在悬浮按钮,添加点击事件
    if (floatBtn) {
        floatBtn.addEventListener('click', function () {
            togglePanel();
        });
    }

    // 窗口大小改变时的处理
    window.addEventListener('resize', function () {
        if (window.innerWidth < 768) {
            // 在移动设备视图下恢复正常布局
            sidePanel.classList.remove('collapsed');
            mainContent.classList.remove('expanded');

            // 显示相关笔记内容
            collapseElement.classList.add('show');

            // 隐藏悬浮按钮
            if (floatBtn) {
                floatBtn.classList.remove('visible');
            }

            // 确保叉号图标在移动设备上不执行收缩功能
            if (toggleIcon) {
                toggleIcon.style.pointerEvents = 'none';
                toggleIcon.style.display = 'none';
            }
        } else {
            // 恢复图标功能
            if (toggleIcon) {
                toggleIcon.style.pointerEvents = 'auto';
                toggleIcon.style.display = '';
            }

            // 在桌面视图下,如果状态是收缩的,保持收缩
            if (localStorage.getItem('relatedNotesCollapsed') === 'true') {
                sidePanel.classList.add('collapsed');
                mainContent.classList.add('expanded');
                collapseElement.classList.remove('show');
                if (floatBtn) {
                    floatBtn.classList.add('visible');
                }
            }
        }
    });

    // 初始检查是否为移动设备,禁用叉号图标功能
    if (window.innerWidth < 768 && toggleIcon) {
        toggleIcon.style.pointerEvents = 'none';
        toggleIcon.style.display = 'none';
    }
});
解决弹出层乱码问题
编程技巧

是因为这里选了GB936出的错,改了以后好了

单例模式
软考

单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。

为什么使用单例模式

在应用系统开发中,我们常常有以下需求:

-在多个线程之间,比如初始化一次socket资源;比如servlet环境,共享同一个资源或者操作同一个对象

-在整个程序空间使用全局变量,共享资源

-大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

因为Singleton模式可以保证为一个类只生成唯一的实例对象,所以这些情况,Singleton模式就派上用场了。

实现单例步骤常用步骤

a)构造函数私有化

b)提供一个全局的静态方法(全局访问点)

c)在类中定义一个静态指针,指向本类的变量的静态变量指针