# designPaterrn **Repository Path**: tt_ff/design-paterrn ## Basic Information - **Project Name**: designPaterrn - **Description**: 设计模式demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-12-12 - **Last Updated**: 2024-01-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 面向对象设计原则之单一职责原则 **单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因** - 单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。 - ==单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则== ## 单一职责案例 ```java public class CustomerDataChart { // 连接数据库 void getConnection(); // 查询所有的客户信息 void findCustomers(); // 创建图表 void createChart(); // 显示图表 void displayChart(); } ``` 这个类承担了太多的职责,可以分为三各类 (1) DBUtil:负责连接数据库,包含数据库连接方法getConnection() (2) CustomerDAO:负责操作数据库中的Customer表,包含对Customer表的增删改查等方法,如findCustomers() (3) CustomerDataChart:负责图表的生成和显示,包含方法createChart()和displayChart() # 面向对象设计原则之开闭原则 **开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展** 在开闭原则的定义中,==软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类== **==抽象化是开闭原则的关键==** ## 开闭原则案例 ```java public class ChartDisplay { public void display{ if (type.equals("pie")) { PieChart chart = new PieChart(); chart.display(); } else if (type.equals("bar")) { BarChart chart = new BarChart(); } chart.display(); } } ``` 在该代码中,如果需要增加一个新的图表类,如折线图LineChart, 则需要修改ChartDisplay类的display()方法的源代码,增加新的判断逻辑,违反了开闭原则。 新增一个抽象类 让pie和bar继承 # 面向对象设计原则之里氏代换原则 **里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。** 里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常, 反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。 例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。 里氏代换原则是实现开闭原则的重要方式之一,==由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象== 在使用里氏代换原则时需要注意如下几个问题: - 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。 - 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。 - Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的 ## 里氏代换原则案例 ```java public abstract class EmailSender { public abstract void send(CommonCustomer customer); public abstract void send(VIPCustomer customer); } public class CommonCustomer(){ private String name; private String email; // setter getter... } public class VIPCustomer(){ private String name; private String email; // setter getter... } ``` 在本实例中,可以考虑增加一个新的抽象客户类Customer, 而将CommonCustomer和VIPCustomer类作为其子类,邮件发送类EmailSender类针对抽象客户类Customer编程。 根据里氏代换原则,能够接受基类对象的地方必然能够接受子类对象,因此将EmailSender中的send()方法的参数类型改为Customer。 如果需要增加新类型的客户,只需将其作为Customer类的子类即可。 # 面向对象设计原则之依赖倒转原则 如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现 **依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。** 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。 在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。 ## 依赖倒转原则案例 CustomerDao: + addCustomers(): void TXTDataConvert: + readFile(): void ExcelDataConvert: + readFile(): void 由于每次转换数据时数据来源不一定相同,因此需要更换数据转换类,如有时候需要将TXTDataConvertor改为ExcelDataConvertor,此时,需要修改CustomerDAO的源代码,而且在引入并使用新的数据转换类时也不得不修改CustomerDAO的源代码,系统扩展性较差,违反了开闭原则,现需要对该方案进行重构。 重构: 在本实例中,由于CustomerDAO针对具体数据转换类编程,因此在增加新的数据转换类或者更换数据转换类时都不得不修改CustomerDAO的源代码。 我们可以通过引入抽象数据转换类解决该问题,在引入抽象数据转换类DataConvert之后,CustomerDAO针对抽象类DataConvert编程, 而将具体数据转换类名存储在配置文件中,符合依赖倒转原则。根据里氏代换原则,程序运行时,具体数据转换类对象将替换DataConvert类型的对象,程序不会出现任何问题。 更换具体数据转换类时无须修改源代码,只需要修改配置文件;如果需要增加新的具体数据转换类,只要将新增数据转换类作为DataConvert的子类并修改配置文件即可, 原有代码无须做任何修改,满足开闭原则 config.xml: xxx要用的类 CustomerDao: + addCustomers(): void DataConvert {abstract}: + readFile(): void TXTDataConvert extends DataConvert: + readFile(): void ExcelDataConvert extends DataConvert: + readFile(): void 在上述重构过程中,我们使用了开闭原则、里氏代换原则和依赖倒转原则,在大多数情况下,这三个设计原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充,目标一致,只是分析问题时所站角度不同而已