设计模式
OOA,OOD,OOP介绍
- OOA:面向对象分析,全称是Object Oriented Analysis.
- OOD:面向对象设计,全称是Object Oriented Design.
- OOP:面向对象编程,全称是Object Oriented Programming.
OOA,OOD,OOP三个连在疫情就是面向对象分析,设计,编程(实现)正好是面向对象软件开发要经历的三个阶段
面向对象编程中有俩个非常重要,非常基础的概念,那就是类(class)和对象(object)。
理解面向对象编程的四大特性:封装,继承,多态,抽象。
说到面向对象分析,设计,编程,有一个能够更好了解面向对象的图,统一建模语言UML(Unified Model Language).
类与类之间的关系
- 依赖关系(局部变量,形参,静态方法的调用)
- 关联关系(成员变量)
- 一般关联关系
- 聚合关系(has-a)
- 组合关系(contains-a)
- 继承关系(父类和子类的关系)is-a
- 实现关系(接口和实现类)
面向对象七大设计原则
面向对象设计原则,主要是用来指导我们程序员如何去定义一个类或者接口,另外还有如何去定义类与类之间的关系。
一般的程序员,用不到面向对象设计原则,但在java领域里面,有一个群体很值钱:写底层框架的小伙伴(自研框架,二次改造开源框架)
对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。
在面向对象设计中,可维护性的复用是以设计原则为基础的。每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结构的设计水平。
**面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结而成的指导性原则。**面向对象设计原则也是我们用于评价一个设计模式的使用效果的重要指标之一,在设计模式的学习中,大家经常会看到诸如“XXX模式符合XXX原则”,“XXX模式违反了XXX原则”这样的语句。
最常见的7中面向对象设计原则如下:
设计原则名称 | 定义 |
---|---|
单一职责原则(Single Responsibility Principle,SRP) | 一个类只负责一个功能领域中的相应职责 |
开闭原则(Open-Closed Principle,OCP) | 软件实体应对扩展开发,而对修改关闭 |
里氏代换原则(Liskov Substitution Principle,LSP) | 所以引用基类对象的地方能够透明地使用其子类的对象 |
依赖倒转原则(Dependence Inversion Principle,DIP) | 抽象不应该依赖于细节,细节应该依赖于抽象 |
接口隔离原则(Interface Segregation Principle,ISP) | 使用多个专门的接口,而不使用单一的总接口 |
合成复用原则(Composite Reuse Principle,CRP) | 尽量使用对象组合,而不是继承来达到复用的目的 |
迪米特法则(Law of Demeter,LoD) | 一个软件实体应当尽可能少地与其他实体发生相互作用 |
这7种设计原则是软件模式必须尽量遵循的原则,各种原则要求的侧重点不同。其中
- 开闭原则是总纲,它告诉我们要 对扩展开发,对修改关闭
- 里氏替换原则告诉我们不用破坏继承体系;依赖倒置原则告诉我们要面向接口编程
- 单一职责原则告诉我们实现类要指责单一,也就是如何定义一个类,如何去封装一个类。
- 接口隔离原则告诉我们在设计接口的时候要精简单一
- 迪米特法则告诉我们要降低耦合度
- 合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
开闭原则
如:
if(type.equals(pie)){
PieChart chart = new PieChart();
chart.display();
}else if(type.equals("bar")){
BarChart chart = new BarChart();
chart.display();
}
在该代码中,如果需要增加一个新的图表类,如折线图LineChart,则需要修改Char源代码,增加新的判断逻辑,违法了开闭原则
现对系统进行重构,使之符合开闭原则。
- 增加一个抽象图表类AbstractChart,将各种具体图标作为其子类
- 针对抽象图表类进行编程,由客户端来决定使用哪种具体图表
单一职责原则
官方定义
单一职责是简单的面向对象设计原则它用来控制类的粒度大小。单一职责原则定义如下:
单一职责原则:一个类只复杂一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
基本介绍
那根据上面给出的定义,我们对单一职责原则进行一个基本结束
即对类来说,一个类应该只负责一项职责。如类A负责俩个不同的职责:职责1,职责2,当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2.
注意:
单一职责原则是实现高内聚,低耦合的指导方针,它最简单但又最难运用的原则,需要设计任意发现类的不同职责并将其分离,而发现类的多重职责需要设计任意具有较强的分析设计能力和相关实践经验。
案例分析
有问题的设计
某软件开发任意针对某CRM(Customer Relationship Management,客户关系管理)系统中客户信息图形设计模块提出了以下解决方案。
CustomerDataChart类中的方法:
- getConnection()方法用于连接数据库
- findCustomers()用于查询所有的客户信息
- createChart()用于创建图表
- displayChart()用于显示图表。
问题分析:
CustomerDataChart类承担了太多的职责,即包含与数据库相关的方法,又包含与图表生成和显示相关的方法。
如果在其他类中也需要连接数据库或者使用findCustomers()方法查询客户信息,则难以实现代码的重用。无论是修改数据库连接方式还是修改图表显示方式都需要修改该类,它不止一个引擎它变化的原因,违背了单一职责原则。因此需要对该类进行拆分,使其满足单一职责原则
重构之后的设计
CustomerDataChart可拆分为如下三个类:
- DBUtil:负责连接数据库,包含数据库连接方法getConnection();
- CustomerDAO:负责操作数据库中的Customer表,包含对Customer表的增删改查等方法,如findCustomers();
- CustomerDataChart:负责图表的生成和显示,包含方法createChart()和displayChart().
里氏替换原则
首先说一下继承性
继承是面向对象的很重要的特性只有,,在编程中如何正确的使用继承,?
继承优势:
- 提高代码的复用性(每个子类都拥有父类的方法和属性)
- 提高代码的可扩展性(很多开源框架的扩展接口都是通过继承父类来完成的)
继承劣势
- 继承是侵入性的(只要继承,就必须拥有父类的所有属性和方法)
- 继承机制很大的增加了耦合性(如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都可能产生故障)
官方定义
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
所有引用基类的地方必须能透明地使用其子类的对象。
基本介绍
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能
在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能使用基类对象。
注意:
- 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的生命,则无法在以父类定义的对象中使用该方法。
- 在运用里氏代换原则是,尽量把父类涉及为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能。同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏替换原则是开闭原则的具体实现手段之一。
- Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的,纯语法意义上的检查,但java编译器的检查是由局限的。
里氏替换原则还有以下俩个含义:
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法,做系统涉及时,经常会涉及接口或抽象类,然后由子类来实现抽象方法,这里使用的其实也是里氏替换原则,子类可以实现父类的抽象方法很好理解,事实上,子类也必须实现父类的抽象方法,那排写一个空方法,否则会编译保存,里氏替换原则的关键点在于不能覆盖父类的非抽象方法。
- 子类可以增加自己特有的方法
在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能,,当功能扩展时,子类不要重写父类的方法,而是另写一个方法
里氏代换原则是实现开闭原则的重要方式之一。
依赖倒转原则
官方定义
依赖导致原则,又称依赖倒置原则,又称DIP原则,
上层模块不应该依赖底层模块,它们都应该依赖于抽象
抽象不应该依赖于细节,细节应该依赖于抽象
基本介绍
依赖倒转原则是基于:相对于细节的多边性,抽象的东西要稳定的多,以抽象为基础搭建的架构要比以细节为基础的架构稳定的多,在java中,抽象指的是接口或抽象类,细节指具体的实现类,既要求程序要依赖于抽象接口,不要依赖于具体实现
使用接口或者抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给它们的实现了去完成。
注意实现&细节
- 底层模块尽量都要有抽象类或接口,或者俩者都有,程序稳定性更好
- 变量的声明类型尽量是抽象类或者接口,这样我们的变量引用和实际对象间,就存在一个缓冲池,利于程序扩展和优化
- 基础时遵循里氏替换原则
在实现依赖倒转原则时,需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象,常用的注入方式有三种,分别是:构造注入,设值注入和接口注入。
开闭原则,里氏代换原则,和依赖倒转原则,在大多数情况下,这三个涉及原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段。
接口分离原则
官方定义
接口隔离原则,又称为ISP原则,官方定义为:
客户端不应有依赖它不需要的接口
类间的依赖关系应该建立在最小的接口上
基本介绍
每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
- 把接口理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将之间带来类型的划分。可以把接口理解成角色,一个接口只能代表一个叫啥,每个接口都有它特定的一个接口,此时,这个原则可以叫做角色隔离原则。
- 如果把接口理解成下一的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不用提供大的总接口。
接口隔离原则就是当我一个类通过接口依赖另一个类的时候,要保证依赖的该接口是最小的,接口里面有方法用不到的,就进行隔离,而隔离的做法就是,对原来的接口进行拆分,拆分为最小的粒度,来避免耦合。
接口隔离于单一职责对比
单一职责原则要求类和接口职责单一,注重的是职责,是业务逻辑上的划分,而接口隔离原则要求方法要尽可能的少,是在接口设计上的考虑。
根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
注意
在使用接口隔离原则时,需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。
合成复用原则
合成复用原则:尽量使用对象组合,而不是继承来达到复用的目的。
合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用以下已有的对象,使之称为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。复用时要尽量使用组合/聚合关系(关联关系),少用继承。
一般而言,如果俩个类之间是Has-A的关系应使用组合或聚合,如果是Is-A关系可使用继承,Is-A是严格的分类学意义上的定义,意思是一个类是另一个类的一种;Has-A则不同,它表示某一个角色具有某一项责任。
迪米特原则
迪米特原则:一个软件实体应当尽可能少地与其他实体发生相互作用。
如果一个系统符合迪米特法则,那么当其一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的现在,迪米特发展可降低系统的耦合度,使类与类之间保存松散的耦合关系。
迪米特发展还有几种定义形式,包括:不要和陌生人说话,只与你的之间朋友通信等。其朋友包括一些几类:
- 当前对象本身 this
- 以参数形式传染到当前对象方法中的对象;
- 当前对象的成员对象;
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
- 当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
迪米特法则要求我们在设计的时候,应该尽量减少对象之间的交互,如果俩个对象之间不必彼此之间通信,那么这俩个对象就不应当方式之间的相互作用,如果其中的一个对象需要调用另外一个对象的某一个方法的话,可以通过第三者转发这个调用。简而言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
注意:
- 在类的划分上,应当经历创建松耦合的类,类之间的耦合度越低,就越利于复用,一个处在耦合中的类一旦被修改,不会对关联的类造成太大波及
- 在类的结构设计上,每一个类都应当降低其成员变量和成员函数的访问权限;
- 再类的设计上只要有可能,一个类型应当设计成不变类;
- 在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
总结
- 开闭原则(大佬的基础)
- 单一职责原则
- 接口隔离原则
- 里氏替换原则
- 依赖倒转原则
- 合成复用原则
- 迪米特发展(最少认知原则)