- 概念:将一些处理特定问题的方式总结成设计模式。(设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。)
面向对象
- 处理复杂性问题:使用面向对象和抽象的方式最大程度的抵御需求变化给代码结构带来的影响。
- 分解,将复杂问题分解成多个简单问题
- 抽象,由于不能掌握全所以的复杂对象,我们选择忽略他的非本质细节,而去处理它的泛化和理想化了的对象模型。
- 面向对象:从微观角度来看,面向对象强调各个类各司其职,需求的变化不应该去改变各个类型的实现。
- 从代码角度来看,对象封装了代码和数据。
- 从规格层面看,对象是一系列可调用的公共接口。
- 从概念层面看,对象是某种责任的抽象。
- 面向对象设计原则
- 依赖倒置原则(DIP):高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖于抽象(稳定);抽象不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。
- 开放封闭原则(OCP):对扩展开放,对更改封闭,类模块是可扩展的,但是不可修改。
- 单一职责原则(SRP):一个类仅有一个引起它的变化的原因,变化的方向隐含类的责任。
- Liskov替换原则(LSP):子类必须可以替换他们的基类(IS-A),继承表达类型抽象。
- 接口隔离原则(ISP):不应该强迫用户程序依赖它们不用的方法,接口应该小而完备。
- 优先使用对象组合,而不是类继承:继承在一定程度上破坏了封装性,子类父类耦合度高;对象组合只要求被组合对象具有良好定义的接口,耦合度低。
- 封装变化点:使用封装来创建对象之间的分界层,让设计者在分界的一侧做修改而不会去对另一侧产生不良影响,从而表现层次间的松偶尔。
- 针对接口编程,而不是针对实现编程:客户程序无需获知对象的具体类型,而是知道对象的实现接口,减少系统中各部分的依赖关系,从而实现“高内聚,低耦合”的类型设计方案。
设计模式
编码技巧 => 设计模式 => 架构设计
设计模式不应该是特定去指定使用,也就是不能够为了使用设计模式而去使用设计模式。一般是在既有代码重构中加入设计模式,来改变代码“变化点”的可控性,提高代码的复用率。
-
分类
- 组件协作:Template Method,Strategy,Observer/Event
- 单一职责:Decorator,Bridge
- 对象创建:Factory Method,Abstract Factory,Prototype,Builder
- 对象性能:Singleton,Flyweight
- 接口隔离:Facade,Proxy,Mediator,Adapter
- 状态变化:Memento,State
- 数据结构:Composite,Iterator,Chain of Responsibility
- 行为变化:Command,Visitor
- 领域问题:Interpreter
-
组件协作:解决框架与应用的协作问题。
- Template Method(模板方法):适用于有稳定的主线结构,但某些子结构却是需求多变的情况。定义一个操作中的算法的骨架(稳定),而将一些步骤的延迟(变化点)放在子类中。Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override)这个算法的某些步骤。
我们在定义某些类库时,可以去使用这个设计模式,我们在类中定义一些抽象方法,应用程序在继承这个类是需要去实现这些抽象方法,这是变化点。但是对于整个类库的使用方法定义在类库中,比如
run
方法,而不是由用户自己去实现,run
方法可能会用到那些需要类库使用者去实现的抽象方法,这是一种反向控制结构,主要利用面向对象的多态特性,通过后绑定的方法解决这个变化点
。- Strategy(策略模式):在对象中使用的算法可能多种多样,如果把这些算法都放在对象中,对象会变得复杂,而且那些不常使用的算法对于对象来说也是一个性能负担。策略模式用于将算法和对象解耦,定义一系列算法,把他们一个个封装起来,并且使他们可以相互替换(变化点)。该模式可以使得算法独立于使用它的应用(稳定)而变化(扩展)。
定义一个基类(接口)来规定那些要实现的算法,子类去实现这些算法,将多个算法分布到各个子类中,实现
开放封闭原则
,再定义一个调用类,利用多态的特性,在使用时传入需要使用的算法的那个子类,动态调用实现算法的方法。Strategy模式
通常是if...else
的另一种选择,当条件判断语句在未来是变化的(扩展),通常可以去考虑Strategy模式
,在Strategy对象中如果没有实例变量,可以通过单例模式来进行性能优化。- Observer(观察者模式):在软件开发过程中,对象之间往往存在一种依赖关系,一个对象(目标对象)的状态改变,所有依赖对象(观察者对象)都会得到通知。观察者模式和发布订阅模式的差别
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并改变。通过面向对象的抽象,Observer模式可以使我们独立的改变目标和观察者,使得二者的依赖关系达到松耦合。Observer模式是基于事件的UI框架中非常常用的设计模式也是MVC模式中一个重要组成部分。
-
单一职责:在使用继承时得到结果往往随着需求变化使得子类代码庞杂,这时候划分责任很重要。
- Decorate(装饰者模式):在某些情况下我们使用继承来扩展对象的功能(动态给一个对象添加一些额外的职责),由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性,而且使得类型不断增多。使用装饰者模式来扩展对象功能,减少类型数量。
动态(组合)地给一个对象增加一些额外的职责,就增加功能而言,Decorate模式比继承更加灵活(消除重复代码,减少子类)。定义一个基类,子类继承基类,装饰者也去继承这个基类,使用多态的特性动态的给装饰者传入子类的实例,然后去扩展子类的方法调用。
- Bridge(桥模式):将抽象部分(业务功能)和实现部分(平台实现)分离,使它们都可以独立的变化。
Bridge模式使用对象间的组合关系解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着自己的纬度变化。
-
对象创建:避免对象创建过程(new)带来的紧耦合(依赖具体类)。
- Factory Method(工厂方法):定义一个用于创建对象的接口,让子类决定实例化哪一个类Factory Method使得一个类的实例化延迟(目的:解耦,手段:抽象方法)到子类,用于解决单个对象的创建。
Factory Method用于隔离类对象使用者和具体类型之间的耦合关系。通过多态的特性,将所要创建具体对象的操作延迟到子类,从而实现一种扩展(而不是更改)。缺点是创建对象的参数要相同。
- Abstract Factory(抽象工厂):解决一系列相互依赖对象的创建工作(比如数据连接对象,必须是配套的一系列对象)。
将创造这一系列对象的方法放到一个工厂方法里面,而不是拆分开来,一系列对象配套使用。
- Prototype(原型模式):解决结构比较复杂,经常发生剧烈变化的,但是拥有稳定一致的接口的对象的创建工作。
使用原型实例来指定创建对象的种类,然后通过拷贝这些原型来创建对象。通常我们在创建复杂对象时使用这种方式,只需创建一个该类对象,再要创建是,调用它的类中定义clone方法深克隆一份该对象即可。
- Builder(构建器):将一个复杂对象的构建和与其表示(属性和方法)相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
Builder模式主要用于分步骤构建一个复杂对象的各个部分,在这其中, 步骤是稳定的算法,而对象的各个部分是经常变化的。
-
对象性能:面向对象模式很好的解决了抽象问题,但是一定程度上带来了性能的问题。
- Singleton(单例模式):在软件设计中,常常有一些类必须保证在程序中只有一个实例,才能确保正确的逻辑性和良好的效率。
保证一个类只有一个实例对象(将构造器私有化),并且提供一个该实例的全局访问点。
- Flyweight(享元模式):运用共享技术有效支持大量细粒度(划分出很多对象)的对象,解决面向对象带来的性能问题。
-
接口隔离:添加一层间接(稳定)接口,来隔开本来互相紧密关联的接口。
- Facade(门面模式):为子系统中的一组接口提供一个一致的(稳定)的界面,Facade模式定义一个高层接口,这个接口是的这一子系统更加容易复用。
Facade设计模式更注重从架构层次来看整个系统,简化了整个组件系统的接口,对于组件内部和外部来说,达到一种解耦的效果,内部子系统的变化不会影响 Facade接口的变化。Facade模式中组件的内部应该是相互耦合关系比较大的一系列组件,而不是一些简单的功能集合。
- Proxy(代理模式):在面向对象系统中,某些对象的直接访问会给使用者或者系统带来很多麻烦(对象创建开销很大,某些操作需要安全控制,需要进程外访问)。
模式定义:为其他对象提供一种代理以控制(隔离:使用接口)对这个对象的访问。
- Adapter(适配器):将一个接口转换成客户希望的另一个接口,Adapter模式使得原本由于接口不兼容而不能在一起的那些类可以在一起工作。
在遗留代码复用和类库迁移方面经常使用。
- Mediator(中介者模式):在对象之间有复杂的引用关系时,如果遇到一些需求的变化,这种引用关系将面临不断的变化。使用一个中介对象来管理对象间的引用关系,避免对象间的紧耦合关系。
用一个中介对象来封装(封装变化)一系列的对象交互。中介者使用各对象不需要显示的相互引用(编译时依赖=>运行时依赖),从而使其耦合松散(管理变化),而且可以独立的改变它们之间的交互。 将多个对象间的控制逻辑进行集中管理,变多个对象的相互关联为多个对象和一个中介者的关联。
-
状态变化:对经常变化的对象的状态进行管理,同时维持高层模块的稳定。
- State(状态模式):根据对象的状态来透明的改变对象的行为。将对象行为放在子类中,从而将对象的状态变化而引发的行为变化放到子类对象的切换中去,符合面向对象设计的开闭原则。
- Memento(备忘录):在对象状态转换时,可能需要回溯到之前的状态。但是如果使用一些公用的接口来让其他对象保存该对象的状态,便会暴露对象的实现细节。备忘录模式用来实现对象状态的保存和恢复,但同时不破坏它的封装性。现在一般使用对象序列化的方式来实现该模式。
-
数据结构:
- Composite(组合模式):将组件内的数据结构和外部使用解耦,将特定的数据结构封装在组件内部,对外提供稳定的接口,来提供与特定数据结构无关的访问。
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。
- Iterator(迭代器):不关系集合内部的结构,而去透明的访问其中的元素。
提供一种方法顺序的访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。
- Chain of Responsibility(职责链):使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理这个请求为止。
-
行为变化:在组件的构建过程中,组件的行为往往导致组件本身剧烈变化,“行为变化”将组件本身和组件行为解耦,从而支持组件行为的变化,实现两者的松耦合。
- Command(命令模式): 将行为请求者和行为实现者解耦,将一组行为抽象成对象,可以实现二者的松耦合。
将一个请求(行为)封装成一个对象,从而使你可用不同的请求来对kuhu客户进行参数化;对请求排队或记录日志,以及支持可撤销的操作。
- Visitor(访问器):由于需求的变化,在某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中增加抽象方法,将会破坏原有的设计。在不增加层次结构的情况下,在运行时根据需求透明的为各个层次增加新的方法,最重要的一点在于双重分发,在基类方法中接收访问者接口调用访问者方法,而在访问者方法中也接收基类接口对象,调用基类方法。
-
领域规则:在特定领域内,某些变化虽然是频繁的,但是可以抽象成某些规则。这时候,结合特定领域,将问题抽象成语法规则,从而结合出在该领域下的一般性解决方案
- Interpreter(解析器):将特定领域问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。