前言
3003字约10分钟
2024-11-13
《JavaScript 设计模式核心原理与应用实践》笔记。
设计原则
设计原则是设计模式的指导理论,它可以帮助我们规避不良的软件设计。SOLID 指代的五个基本原则分别是:
- 单一功能原则(Single Responsibility Principle)
- 开放封闭原则(Opened Closed Principle)
- 里式替换原则(Liskov Substitution Principle)
- 接口隔离原则(Interface Segregation Principle)
- 依赖反转原则(Dependency Inversion Principle)
单一功能原则
定义:该原则是面向对象设计的基本原则之一,由Robert C. Martin在其著作《敏捷软件开发:原则、模式和实践》中提出。
核心思想:一个类或模块应该有且只有一个职责,即一个类应该只负责一项任务或功能。
原因:通过限制类的职责范围,可以降低类的复杂性,提高代码的可读性和可维护性。
好处
- 降低耦合:当类的职责单一时,类与类之间的依赖关系更简单,修改一个类时对其他类的影响减小。
- 增强可测试性:单一职责的类更容易编写单元测试,因为它们的行为更专注,边界更清晰。
- 提高可扩展性:如果需要添加新功能,可以创建新的类而不是修改已有的类,避免破坏原有的代码结构。
- 减少变更风险:当需求变化时,只需要改动相关联的单一职责类,降低了出错的可能性。
应用:在实际编程中,这意味着应避免在类中混合不同的业务逻辑,如将数据访问、业务规则和用户界面逻辑放在同一个类中。
重构技巧:识别职责并进行分离,创建专门的类或模块来处理每个职责,确保每个组件都有明确的边界。
遵循单一职责原则有助于创建更加灵活、可复用和易于理解的代码库。
开放封闭原则
定义:开放封闭原则是面向对象设计的另一基本原则,同样出自Robert C. Martin的著作。它指出软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
核心思想:对扩展开放:软件设计应允许通过增加新代码来应对变化,以提供新的行为或功能,而不需要修改现有的、经过验证的代码。 对修改封闭:一旦软件组件被测试并发布,就不应该再因为新增功能而对其进行修改,以防止引入错误或破坏现有功能。
目的:
- 提高灵活性:使得系统能够容易地适应需求变化,增加新的功能而不影响已有代码的稳定性。
- 减少风险:修改现有代码可能引入错误,遵守OCP可以最小化这种风险。
- 促进复用:设计为可扩展的组件更易于在不同上下文中复用。
实现策略:
- 抽象与接口:使用抽象类或接口定义一个稳定的合同,具体实现可以根据需求变化而变化,不改变接口定义。
- 继承与多态:通过继承实现具体功能的多样化,同时利用多态确保新功能可以在不修改原有代码的情况下插入系统。
- 依赖倒置:高层模块不应依赖于低层模块,两者都应依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这有助于实现OCP。
案例:假设有一个支付系统,最初只支持信用卡支付。遵循OCP,可以设计一个PaymentMethod接口,初始实现为CreditCardPayment类。当需要添加支付宝支付功能时,只需创建一个新的AlipayPayment类实现PaymentMethod接口,无需修改原有的信用卡支付代码。
开放封闭原则鼓励设计者在面对需求变化时,优先考虑通过扩展而非修改现有代码来满足新需求,从而提升软件的长期可维护性和灵活性。
里式替换原则
定义:由芭芭拉·利斯科夫(Barbara Liskov)提出,是面向对象设计的五大原则之一。LSP表明,在面向对象设计中,子类型(subtype)必须能够替换其基类型(supertype)而不影响程序的正确性。
核心思想:
- 子类型兼容:子类应当能够替换其父类并在不改变程序正确性的前提下工作。换句话说,使用基类的地方,可以无缝地使用子类实例而无需了解这种替换。
- 行为一致性:子类不仅需要保持父类的行为约定,还不能减少父类已经承诺的职责,即子类不能去掉父类已经实现的方法或者改变这些方法的预期行为。
目的与好处:
- 增强代码健壮性:确保子类不会破坏父类在系统中的使用,增强了系统的稳定性和可靠性。
- 提高可维护性:允许在不修改现有客户端代码的情况下引入新的子类,简化了维护和升级过程。
- 促进设计灵活性:鼓励设计更为抽象的基类和符合契约的子类,便于未来的扩展和修改。
应用指南:
- 设计时考虑契约:明确父类接口所表达的契约(前置条件、后置条件和不变量),子类必须遵守这些契约。
- 避免过度特定化:子类应谨慎添加新行为,确保新行为不会违背基类的设计意图。
- 利用多态而非类型检查:利用抽象和多态机制,而不是显式的类型检查,来决定对象的行为。
违反后果:如果子类没有正确实现LSP,可能会导致运行时错误,比如原本在父类中正常工作的代码,在使用子类时出现问题,这会降低代码的可预测性和稳定性。
里式替换原则是实现面向对象设计中“开闭原则”的重要手段,它强调了在继承结构中保持行为一致性和兼容性的重要性,是确保软件模块间松耦合、高内聚的关键原则之一。
接口隔离原则
定义:ISP原则是由罗伯特·C·马丁(Robert C. Martin)提出的,它主张客户端不应该被迫依赖它不需要的接口。换句话说,应该将庞大而臃肿的接口拆分成更小、更具体的接口,使得客户端仅依赖于它们真正需要的方法。
核心思想:
- 接口分组:将相关的操作组合成小的、针对性的接口,而不是创建一个包含所有可能方法的大接口。
- 减少依赖:客户端只依赖那些它实际使用的方法,避免了“胖接口”导致的不必要的耦合。
- 提高灵活性:允许接口的消费者选择合适的接口,使得系统更易于扩展和维护。
目的:
- 降低耦合:通过减少不必要的依赖,提高了模块之间的独立性。
- 提高可重用性:更细粒度的接口可以更好地适应不同场景,增加代码的复用可能性。
- 易于理解和维护:小接口比大接口更易于理解和测试。
应用:
- 接口拆分:分析接口的使用者,根据它们的需求将大接口拆分成小接口。
- 避免上帝接口:避免创建一个“无所不能”的接口,它包含了各种不相关的功能。
- 关注点分离:确保接口的职责单一,每个接口专注于特定的业务领域或功能。
违反后果:如果一个类实现了大而全的接口,但只使用了其中一部分方法,那么这个类就不得不依赖于它不需要的其他方法,这增加了维护成本,也限制了未来的灵活性。
接口隔离原则鼓励设计更加灵活、可扩展的系统,通过最小化接口的粒度来优化代码结构,提高软件设计的质量。
依赖反转原则
定义:依赖反转原则是面向对象设计的基本原则之一,由Robert C. Martin提出。它指出高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象;抽象不应该依赖于具体实现,而是具体实现应该依赖于抽象。
核心思想:
- 依赖抽象:高层次的模块(如业务逻辑)依赖于抽象接口或抽象类,而不是具体实现。
- 控制反转:依赖关系的控制权从调用者转移到了被调用者,即不再由调用者直接创建被调用者的实例,而是通过依赖注入等方式来实现。
目的:
- 降低耦合:通过依赖抽象而不是具体实现,降低了模块间的耦合度。
- 提高可扩展性:当需要更换或添加新实现时,无需修改原有高层模块,只需调整依赖的抽象接口实现。
- 增强可测试性:依赖注入使得测试变得更加容易,因为可以轻松替换依赖项以进行单元测试。
实现方式:
- 使用接口或抽象类:定义接口或抽象类作为高层次模块的依赖。
- 依赖注入:通过构造函数注入、属性注入或服务定位器等方式将依赖的实现传递给高层次模块。
- 依赖反转容器:使用IoC(Inversion of Control)容器,如Spring框架,自动管理对象的创建和依赖关系。
反模式:当高层次模块直接依赖于低层次模块的具体实现,而不是依赖于抽象,这会增加耦合,降低代码的灵活性和可维护性。
应用示例:在设计一个图形用户界面时,高层次的业务逻辑层不应直接依赖于特定的数据库访问类,而应该依赖于一个数据库访问的接口。这样,当需要更换数据库或更改数据库访问方式时,只需修改低层次的实现,而无需改动业务逻辑层。
依赖反转原则是实现“开闭原则”的关键,它促进了模块间的解耦,提高了代码的可复用性和可维护性。
注意
在JavaScript设计模式中,主要用到的是单一功能原则(Single Responsibility Principle)和依赖反转原则(Dependency Inversion Principle)。
设计模式
其实就是指二十年前 GOF 提出的最经典的23种设计模式。二十年前,四位程序员前辈(Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)通过编写《设计模式:可复用面向对象软件的基础》这本书,阐述了设计模式领域的开创性成果。在这本书中,将23种设计模式按照“创建型”、“行为型”和“结构型”进行划分,其中带 *
号的是接下来要学习的,也是开发中最常用的: