定义
设计模式就是一套被反复使用的,多数人知晓的,经过分类编目的,代码设计经验的总结,使用设计模式是为了可重用代码,让代码更容易被他人理解并且提高代码可靠性。
设计模式一般包含模式名称
,问题
,目的
,解决方案
,效果
等组成要素,其中关键要素是模式名称,问题,解决方案和效果。
模式名称
:通过一两个词来为模式命名问题
:描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因解决方案
:描述一个设计模式的组成部分,以及这些组成部分之间的相互关系,各自的职责和协作方式,通常解决方案通过UML类图和核心代码之间进行描述效果
:描述了模式的优缺点以及在使用模式时应权衡的问题。
分类
设计模式还可以分成创建型
,结构型
和行为型
3种。
1. 创建型模式主要是用于描述如何创建对象
2. 结构型模式主要是用于描述如何实现类或对象的组合
3. 行为型模式主要用于描述类或对象怎样交互以及怎样分类职责
设计模式还可以根据主要用于处理类之间的关系还是处理对象之间的关系,分成
类模式
和对象模式
。
常用的设计模式
1、创建型模式(Creational Pattern)
模式名称 | 使用频率 |
---|---|
单例模式(Singleton Pattern) | 4星 |
简单工厂模式(Simple Factory Pattern) | 3星 |
工厂方法模式(Factory Method Pattern) | 5星 |
抽象工厂模式(Abstract Factory Pattern) | 5星 |
原型模式(Prototype Pattern) | 3星 |
建造者模式(Builder Pattern) | 2星 |
2、结构型模式(Structural Pattern)
模式名称 | 使用频率 |
---|---|
适配器模式(Adapter Pattern) | 4星 |
桥接模式(Bridge Pattern) | 3星 |
组合模式(Composite Pattern) | 4星 |
装饰模式(Decorator Pattern) | 3星 |
外观模式(Facade Pattern) | 5星 |
享元模式(Flyweight Pattern) | 1星 |
代理模式(Proxy Pattern) | 4星 |
3、行为型模式(Behavior Pattern)
模式名称 | 使用频率 |
---|---|
职责链模式(Chain of Responsibility Pattern) | 2星 |
命令模式(Command Pattern) | 4星 |
解释器模式(Interpreter Pattern) | 1星 |
迭代器模式(Iterator Pattern) | 5星 |
中介者模式(Mediator Pattern) | 2星 |
备忘录模式(Memento Pattern) | 2星 |
观察者模式(Observer Pattern) | 5星 |
状态模式(State Pattern) | 3星 |
策略模式(Strategy Pattern) | 4星 |
模板方法模式(Template Method Pattern) | 3星 |
访问者模式(Visitor Pattern) | 1星 |
设计模式的作用
设计模式源于众多专家的经验和智慧
,他们是从许多优秀的软件系统中总结出的成功的,能够实现可维护性复用的设计方案,使用这些方案可避免做一些重复行的工作,有助于提高设计和开发效率设计模式提供了一套通用的设计词汇和一种通用的形式来方便开发人员之间的交流和沟通,使得设计方案更加通俗易懂
大部分设计模式都兼顾了系统的可重用性和可扩展性
,这使得开发人员可以更好的重用一些已有的设计方案,功能模块甚至一个完整的软件系统。合理使用设计模式并对设计模式的使用情况进行文档化,将有助于别人更快地理解系统
学习设计模式将有助于初学者更加深入的理解面向对象思想
。例如,如何将代码分散在几个不同的类中?为什么要有“接口”?何谓针对抽象编程?何时不应该使用继承?如何不修改源代码增加新功能?同时还能更好的阅读和理解现有类库(如JDK)与其它系统中的源代码,早日脱离面向对象编程的菜鸟
面向对象设计原则
概述
面向对象设计的目标之一在于支持可维护性复用,一方面需要实现设计方案或者源代码的重用,另一方面要确保系统能够易于扩展和修改,具有较好的灵活性。
最常见的7种面向对象设计原则:
设计原则名称 | 定义 | 使用频率 |
---|---|---|
单一职责原则 (Single Responsibility Principle, SRP) | 一个类只负责一个功能领域中的相应职责 | 4星 |
开闭原则 (Open-Closed Principle,OCP) | 软件实体应对扩展开放,而对修改关闭 | 5星 |
里氏代换原则 (Liskov Substitution Principle, LSP) | 所有引用基类对象的地方能够透明地使用其子类的对象 | 5星 |
依赖倒转原则 (Depandence Segregation Principle, ISP) | 抽象不应该依赖于细节,细节应该依赖于抽象 | 5星 |
接口隔离原则 (Interface Segregation Principle,CRP) | 使用多个专门的接口,而不使用单一的总接口 | 2星 |
合成复合原则 (Composite Reuse Principle, CRP) | 尽量使用对象组合,而不是继承来达到复用的目的 | 4星 |
迪米特法则 (Law of Demeter,LoD) | 一个软件实体应当尽可能少地与其他实体发生相互作用 | 3星 |
单一职责原则
单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小
单一职责原则:一个类只负责一个功能领域中的相应职责。或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则结构图:
开闭原则
开闭原则是面向对象的可复用设计的第一块基石。
开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。 24种设计模式中,大部分设计模式都符合开闭原则,在对每一个模式进行优缺点评价时,都会将开闭原则作为一个重要的评价依据,以判断基于该模式设计的系统是否具有良好的灵活性和可扩展性
里氏代换原则
里氏代换原则严格表述:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型S是类型T的子类型。
里氏代换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
依赖倒转原则
依赖倒转原则:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
依赖倒转原则要求在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明,参数类型声明,方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。
在实现依赖转原则时,需要针对抽象层编程,而将具体类的对象通过依赖注入的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有3种,构造注入,设值注入(Setter注入)和接口注入。
大多情况下,开闭原则,里氏代换原则和依赖倒转原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,相辅相成,相互补充,目标一致。
开闭原则、里氏代换和依赖倒转(例子结构图)
接口隔离原则
接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些他不需要的接口。
根据接口隔离原则,当一个接口太大时,需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色.这里的‘接口’有两种不同的含义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象,另一种是指某种语言具体的‘接口’定义,有严格的定义和结构,如java语言中的interface。
对于这两种含义,ISP的表达方式和含义都有所不同
当把‘接口’理解成一个类型所提供的所有方法特征的集合时,这是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫做‘
角色隔离原则
’如果把‘接口’理解为狭义的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独接口,而不要提供大的接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方式应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为‘
定制服务
’,即为不同的客户端提供宽窄不同的接口。
合成复合原则:
合成复合原则又称为组合/聚合复用原则,尽量时候用对象组合,而不是继承来达到复用的目的。
合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简言之 :复用时要尽量使用组合/聚合关系(关联关系),少用继承。
在面对对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承,但首先应该考虑使用组合/聚合关系,组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承,在使用继承时,应该严格遵循里氏代换原则,有效的使用继承会有助于对问题的理解,降低复杂度,而滥用继承,反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;而继承只能在有限的环境中使用(如果类没有声明为不能被继承)
由于组合或聚合关系可以将已有的对象(也可以称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为黑箱复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性的调用对象的操作;合成复合可以在运行时动态进行,新对象可以动态引用与成员对象类型相同的其他对象。
迪米特法则
迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用
迪米特法则要求限制软件实体之间通信的宽度和深度,迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
迪米特法则还有几种定义形式:不要和‘陌生人’说话,只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其‘朋友’包括以下几类:
1. 当前对象本身(this)
2. 以参数形式传入到当前对象方法中的对象
3. 当前对象的成员对象
4. 如果当前对象的成员对象是一个集合,那么集合中元素也是朋友
5. 当前对象所创建的对象
任何一个对象,满足上面的条件之一,就是当前的朋友,否则就是陌生人,在应用迪米特法则时,一个对象只能与直接朋友进行交互,不要与陌生人发生直接交互,这样做可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。
迪米特法则要求在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么两个对象就不应当发生任何直接的相互作用;如果其中一个对象需要调用另一个对象的方法,可以通过第三者转发这个调用,简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
在将迪米特法则运用到系统设计中时,要注意下面几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度降低,就越有利于复用,一个处在松耦合的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。