# design-patterns **Repository Path**: aismall/design-patterns ## Basic Information - **Project Name**: design-patterns - **Description**: 设计模式 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-09-14 - **Last Updated**: 2024-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 一、设计模式概述 设计模式(Design Patterns)是软件工程中用于解决特定问题的一系列最佳实践。它们是经过时间考验的、被广泛认可的软件设计经验。可以帮助开发者在面对常见问题时做出更好的设计决策。设计模式不是现成的代码,而是一套指导原则,用来指导开发者如何组织代码结构,以便于更好地应对变化和提高代码的可维护性。 ## 二、 设计模式的特点 普遍性:设计模式解决的问题在软件设计中非常普遍,很多设计问题都可以用相应的设计模式来解决。 可重用性:设计模式提供了一种可重用的解决方案,可以帮助开发者避免重复发明轮子。 灵活性:设计模式通常具有良好的灵活性,可以适应不同的应用场景。 可维护性:使用设计模式可以提高代码的可维护性,因为它们提供了一种标准化的解决方案。 可扩展性:设计模式通常具有良好的可扩展性,可以方便地添加新的功能。 ## 三、设计模式七大原则 ### 3.1、单一职责原则 单一职责模式(Single Responsibility Principle,SRP)是面向对象设计中的一个基本原则,由 Robert C. Martin 提出,这一原则强调一个类应该仅有一个引起其变化的原因。 单一职责的核心思想: - 单一职责:一个类应该只负责一项功能或职责,并且该职责应该是独立的。 - 降低复杂度:通过将职责分离到不同的类中,可以降低单个类的复杂度,使代码更易于理解和维护。 - 提高可维护性:当需求发生变化时,如果一个类只负责一个特定的功能,那么修改这个类不会影响到其他功能。 - 提高可测试性:单一职责的类更容易进行单元测试。 示例: - 假设有一个 Order 类,它包含了订单管理和打印订单报告的功能。 - 根据单一职责原则,可以将这两个功能分离到不同的类中。 - 通过这种方式,OrderManager 只负责订单管理,而 ReportGenerator 负责报告生成,每个类都专注于自己的核心功能 ```java // 订单管理类 public class OrderManager { public void placeOrder(Order order) { // 实现订单放置逻辑 } public void cancelOrder(Order order) { // 实现订单取消逻辑 } } // 报告生成类 public class ReportGenerator { public String generateOrderReport(Order order) { // 生成订单报告 return "Order Report: " + order.toString(); } } ``` 总结:单一职责模式有助于提高代码的可维护性和可扩展性。在设计软件时,遵循这一原则可以帮助避免`庞大`类的问题,并使得各个组件更加模块化和灵活。 ### 3.2、接口隔离原则 接口隔离原则(Interface Segregation Principle, ISP)是面向对象设计的一个基本原则,由罗伯特·C·马丁(Robert C. Martin)提出。这一原则强调了接口的设计应该尽可能细粒度,使得实现类只需要知道并实现它们关心的方法,而不需要依赖于那些它们不使用的方法。 接口隔离原则的核心思想 - 细化接口:不要强迫实现类去实现它们不会使用的方法。如果一个接口太大或者包含了多个不相关的操作,那么应该将其拆分为更小、更具体的接口。 - 客户端驱动设计:根据客户端的需求来设计接口。每个客户端都应该仅依赖它需要的那个特定的接口。 - 减少耦合:通过将大接口拆分成多个小接口,可以降低各个组件之间的耦合度,提高系统的灵活性和可维护性。 示例: - 假设有一个 IMachine 接口,包含打印、扫描和传真三个功能。 - 如果一个实现类只需要打印功能,但必须实现 IMachine 接口中的所有方法,这就违反了接口隔离原则。 - 为了遵循 ISP,可以将 IMachine 拆分为三个独立的接口。 - 通过这种方式,实现了更细粒度的接口设计,提高了代码的灵活性和可维护性。 ```java public interface IMachine { void Print(string document); void Scan(string document); void Fax(string document); } ``` ### 3.3、依赖倒转原则 依赖倒转原则(Dependency Inversion Principle, DIP),是面向对象设计的一个基本原则,由是由罗伯特·C·马丁(Robert C. Martin)提出。这一原则鼓励我们以抽象的方式进行编程而不是针对具体实现编程,从而提高系统的灵活性和可维护性。 依赖倒转原则核心思想: - 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。 - 抽象不应该依赖于细节,细节应该依赖于抽象(不可依赖倒转)。 上面的话太抽象了解释一下吧: - 高层模块:指负责业务逻辑处理的模块,通常是服务层(service)、控制层(controller)等。 - 低层模块:指具体的实现细节,通常是数据访问层(dao)或工具类(utils)等。 - 抽象:通常是指接口(Interface)或者抽象类(Abstract Class)。 - 细节:具体实现类。 实践应用: - 使用接口或抽象类定义行为:定义清晰的行为规范,让高层模块通过这些规范来调用低层模块。 - 依赖注入:通过构造函数、setter方法或静态工厂等方式,在运行时将具体实现传递给需要使用的模块,这样可以在不修改代码的情况下更换不同的实现。 - 减少类之间的耦合:依赖倒转原则有助于降低模块间的耦合度,使得各个模块更加独立,易于测试和维护。 示例: - 假设有一个系统需要发送邮件通知和短信通知,可以通过定义一个 NotificationService 接口,并提供两种实现类 EmailNotification 和 SmsNotification。 - 高层模块(如订单服务)只需要依赖 NotificationService 接口即可,而不需要关心具体的实现方式。 - 通过这种方式,当需要更换通知方式时,只需给 OrderService 的构造函数传递不同的通信方式的实现类即可,无需修改其内部代码。 - 依赖倒转原则有助于构建更灵活、可扩展和可维护的软件系统。 ```java // 定义接口 public interface NotificationService { void send(String message); } // 具体实现 public class EmailNotification implements NotificationService { @Override public void send(String message) { // 发送邮件的具体实现 } } public class SmsNotification implements NotificationService { @Override public void send(String message) { // 发送短信的具体实现 } } // 高层模块 public class OrderService { private NotificationService notificationService; public OrderService(NotificationService notificationService) { this.notificationService = notificationService; } public void processOrder() { // 处理订单逻辑 notificationService.send("订单已处理"); } } ``` ### 3.4、里氏替换原则 里氏替换原则(Liskov Substitution Principle, LSP),是是面向对象设计的一个基本原则,由芭芭拉·利斯科夫(Barbara Liskov)提出。 里氏替换原则核心思想: - 子类必须能够替换其基类,并且在不改变程序正确性的前提下,可以自由地使用基类已经提供的接口。 简单来说: - 子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,如果一个函数使用的是父类作为参数,那么用子类去替代父类时,该函数的行为应该不会有任何改变。 - 所有引用基类的地方都必须能透明地使用其子类的对象。换句话说,只要一个变量的类型是一个超类型,在任何时刻都可以用其任意一个子类型的对象进行赋值。 - 遵循里氏替换原则有助于提高代码的可复用性和可维护性,减少因继承关系导致的问题。例如,当系统设计合理地遵循LSP时,添加新的子类或修改现有子类的行为时,不会影响到其他部分的代码,从而降低了系统的耦合度。 在实际开发中,为了更好地实现LSP,我们需要注意: - 设计合理的继承关系,避免不合适的继承。 - 确保子类对父类的方法的重写(覆盖)不会影响到使用父类的地方。 - 在定义接口或抽象类时,要尽量定义得足够通用,使得大部分实现类都能自然地满足接口或抽象类的要求,而不需要强制覆盖方法或做不必要的操作。 ### 3.5、开闭原则 开闭原则(Open-Closed Principle, OCP),由伯纳德·乔利(Bertrand Meyer)提出的一个面向对象编程的设计原则。该原则表述为:软件实体(类、模块、函数等)应该是可扩展的(open for extension),但是不可修改的(closed for modification),即对拓展开放对修改关闭。 这个原则鼓励在设计系统时,使得系统可以在不修改现有代码的基础上进行扩展,以适应新的需求。这样可以减少对现有系统的影响,降低引入错误的风险,并提高系统的可维护性和灵活性。 遵循开闭原则通常需要结合其他设计原则和设计模式来实现,比如使用里氏替换原则确保子类可以替换父类,利用接口隔离原则定义清晰的接口,以及通过依赖倒置原则将高层模块与低层模块解耦等。此外,策略模式、工厂模式、抽象工厂模式等设计模式也有助于实现系统的开放封闭性。 ### 3.6、迪米特法则 迪米特法则(Law of Demeter, LoD),也称为最少知识原则(Least Knowledge Principle)或简称`洛德法则`,是面向对象设计中一个有助于保持系统模块化和降低耦合度的原则。该原则由伊利亚·帕苏普亚提(Ilian Paschalov)在1987年的OOB会议上首次提出,后来以Demeter项目中的角色命名。 迪米特法则的核心思想: - 一个软件实体应当尽可能少地与其他实体发生相互作用。具体来说: - 一个类对自己依赖的类知道得越少越好。 - 一个类应当尽可能减少对其他类的依赖。 - 一个类的方法应该只调用它直接依赖的对象或者其成员变量/属性。 迪米特法则的目的 - 降低耦合性:通过减少类之间的交互,可以降低系统的复杂性和类间的耦合度。 - 提高可维护性:当需要修改某个类时,如果它与其他类的依赖关系较少,则影响范围较小,更容易进行修改和测试。 - 简化系统设计:使得系统更加模块化,易于理解和扩展。 示例: - 假设有一个Student类,它需要获取课程信息。不遵循迪米特法则的做法可能是让Student直接操作Course类。而遵循迪米特法则的方式则是引入一个中间类如SchoolService,Student类通过SchoolService来获取课程信息,这样减少了Student与Course的直接耦合。 ```java public class Course { public String name; public Course(String name) { this.name = name; } } public class SchoolService { public Course getCourse(String name) { return new Course(name); } } public class Student { public Course getCourse(String name) { return new SchoolService().getCourse(name); } } ``` ### 3.7、合成复用原则 合成复用原则(Composite Reuse Principle, CRP)是一种软件设计原则,它提倡通过聚合或组合的方式重用已有代码和对象,而不是通过继承来实现功能的扩展。 具体来说,合成复用原则建议: - 使用对象组合:在设计类的时候,优先考虑通过持有其他对象的引用(即组合或聚合关系)来实现功能的复用,而不是通过继承来扩展类的行为。 - 减少继承体系:虽然继承是面向对象编程的一个重要特性,但是过度使用继承会导致复杂的继承体系,这不仅增加了系统的复杂性,还可能引入紧耦合的问题。因此,在可以使用组合或聚合的情况下,尽量避免使用继承。 - 提高代码灵活性:通过组合或聚合,可以在运行时动态地改变对象的行为,从而提高代码的灵活性和可维护性。 示例: - 假设有一个系统需要表示不同类型的员工,每个员工都有一个工作职责。如果使用继承的方式,可能会有如下结构: ```java public abstract class Employee { public abstract void doWork(); } public class Developer extends Employee { @Override public void doWork() { System.out.println("Developer is coding"); } } public class Manager extends Employee { @Override public void doWork() { System.out.println("Manager is managing"); } } ``` - 如果未来需要增加一个新的职责,比如培训,使用继承的方式就需要新增一个子类。而如果采用合成复用原则,则可以通过持有责任对象的方式来实现,通过这种方式,可以在不修改原有代码的情况下,轻松添加新的职责。这就是合成复用原则的核心思想: ```java // 责任对象 public interface Responsibility { void perform(); } // 研发责任 public class CodingResponsibility implements Responsibility { @Override public void perform() { System.out.println("Coding"); } } // 管理责任 public class ManagingResponsibility implements Responsibility { @Override public void perform() { System.out.println("Managing"); } } // 培训人员责任 public class TrainingResponsibility implements Responsibility { @Override public void perform() { System.out.println("Training"); } } // 员工 public class Employee { private Responsibility responsibility; public Employee(Responsibility responsibility) { this.responsibility = responsibility; } public void doWork() { responsibility.perform(); } } public class Main { public static void main(String[] args) { Employee developer = new Employee(new CodingResponsibility()); developer.doWork(); Employee manager = new Employee(new ManagingResponsibility()); manager.doWork(); Employee trainer = new Employee(new TrainingResponsibility()); trainer.doWork(); } } ``` ## 四、设计模式分类 设计模式一共有23种,可分为以下三大类: - 创建型模式(Creational Patterns):主要关注对象的创建,隐藏对象创建的复杂性,提高程序的可扩展性。常见的创建型模式有`单例模式`、`工厂方法模式`、`抽象工厂模式`、`建造者模式`和`原型模式`。 - 结构型模式(Structural Patterns):主要关注对象的组合,通过对象的组合提高程序的模块化。常见的结构型模式有`适配器模式`、`桥接模式`、`组合模式`、`装饰器模式`、`外观模式`、`享元模式`和`代理模式`。 - 行为型模式(Behavioral Patterns):主要关注对象之间的交互,描述对象如何协作以完成某种任务。常见的行为型模式有`责任链模式`、`命令模式`、`解释器模式`、`迭代器模式`、`中介者模式`、`备忘录模式`、`观察者模式`、`状态模式`、`策略模式`、`访问者模式`和`模板方法模式`。 注意:`设计模式是软件工程中非常重要的概念,掌握设计模式对于提高编程能力和设计能力非常有帮助。但是,设计模式并不是万能的,需要根据具体的应用场景来选择是否使用设计模式,以及使用哪种设计模式。盲目地使用设计模式可能会导致过度设计,反而降低程序的性能和可维护性`。 ## 五、各种设计模式简介 1、单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 2、工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪个类 3、抽象工厂模式:解决一个系列的工厂,用于创建一组相关或依赖的对象 4、建造者模式:分离对象的构建过程和表示,允许通过指定复杂对象类型和内容逐步构造一个复杂对象 5、原型模式:通过复制现有的实例来创建新的实例 6、适配器模式:允许将不兼容的接口转换为一个可以使用的兼容接口 7、桥接模式:分离抽象部分和实现部分,使它们可以独立地变化 8、组合模式:允许将对象组合成树形结构以表示部分-整”的层次结构 9、装饰器模式:动态地添加额外的功能到一个对象上,而不是通过继承 10、外观模式:为子系统中的一组接口提供一个统一的接口 11、享元模式:运用共享技术有效地支持大量细粒度的对象 12、代理模式:为其他对象提供一个代理或占位符,以控制对这个对象的访问 13、责任链模式:使多个对象都有机会处理请求,避免请求的发送者和接收者之间的耦合 14、命令模式:将一个请求封装为一个对象,从而允许用户使用不同的请求来参数化其他对象 15、解释器模式:定义一个语言的文法,并且建立一个解释器来解释该语言中的句子 16、迭代器模式:提供一种顺序访问一个聚合对象元素的方法,而不暴露其内部的表示 17、中介者模式:用一个中介对象来封装一系列对象之间的交互 - 集中管理对象间的通信 18、备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态 19、观察者模式:当对象间存在一对多关系时,则使用观察者模式 20、状态模式:允许对象在其内部状态改变时改变它的行为 21、策略模式:定义一系列算法,把它们一个个封装起来,并使它们可以互换 22、访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 ## 六、参考文档 https://zhuanlan.zhihu.com/p/575645658 https://blog.csdn.net/weixin_37519752/article/details/138796630