设计模式总结

看完了 Head First 设计模式,收获还是蛮多的,这么多设计模式总算有了个了解,距离实用还有一定距离,毕竟是个经验活,不得不说设计模式中的思想真是太棒了!后面有几种模式没时间拿出来单独研究了,在这里就都战略性总结一下吧,啊哈哈~~
希望我也能写出一手漂亮的代码!

设计原则

一览表:

设计原则名称定 义使用频率
单一职责原则 (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)一个软件实体应当尽可能少地与其他实体发生相互作用★★★☆☆

基本的设计原则

上面的表中都是专业的说法,但是貌似并不怎么好理解,反正我是看的比较懵逼,然后就用普通的语言来进行描述下,首先是下面总结的几条,应该是大纲级别的了

  • 封装变化
    找出应用中可能需要变化的部分,把它们独立出来;不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承
  • 为交互对象之间的松耦合设计而努力

这样设计类才能更好的利用 OO 的特性,也是基础:抽象、封装、继承、多态;
良好的 OO 设计必须具备:可复用、可扩充、可维护三个特性

对上面不太好理解的地方补充:
松耦合:
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节;也就是说,如果改变其中一方并不会影响到另一方,因为两者是松耦合的,所以只有它们之间的接口仍被遵守,那么我们就可以自由的改变它们(接口的重要性)
松耦合的设计之所以能建立起富有弹性的 OO 系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

开闭原则

从表中也可以看出,这个使用的非常频繁,开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则;它规定:类应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为;装饰模式就是遵守的这个原则的体现(硬要说,观察者模式也是遵循的)
当然,并不是让每个地方都遵循开闭原则(这回增加代码的复杂度),我们要把注意力集中在最有可能改变的地方,然后应用开闭原则

依赖倒置原则

规定的是:要依赖抽象,而不是依赖具体的类;emmm,和“针对接口编程,而不是针对实现编程”貌似是差不多的
它说明:不能让高层组件依赖低层组件,而且,不管高层或者低层组件,两者都应该依赖于抽象!

所谓高层组件,是由其他低层组件定义其行为的类。
就比如人类是高层组件,男人就是低层组件,它的部分行为是由男人定义的(差不多就这个意思,不要太纠结)

那么,究竟是哪里“倒置”了呢,低层组件会依赖高层的抽象,高层组件也依赖相同的抽象
(假定男人拥有一个抽象,那么相对于具体实现这个抽象是“高层”的,高层组件也依赖这个抽象)
我们一般的思维是从顶端开始,然后往下到具体的类,倒置你的想法就是别从顶端开始,首先想的是甭管什么样的男人都需要一个共同的抽象类,然后人类也会依赖这个抽象类,这样想其实就已经倒置了!大概…..
然后是几个指导方针:

  • 变量不可以持有具体类的引用
  • 不要让类派生自具体类
  • 不要覆盖基类中已经实现的方法

但毕竟是指导作用,不可避免的在某些条件下要违反,只是尽量的遵守罢了;我们实例化字符串的时候都是 new 啊,违反了方针啊,但完全可以,因为字符串不可能改变,所以说要灵活
工厂模式就是这个原则的代表吧

面向对象的五大基本原则

单一职责原则(SRP)
开放封闭原则(OCP)
里氏替换原则(LSP)
依赖倒置原则(DIP)
接口隔离原则(ISP)

OO原则

下面列出了基本的原则:

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间的松耦合设计而努力
  • 类应该对修改关闭,对扩展开放
  • 依赖抽象,不依赖具体类
  • 只和“朋友”交谈(减少对象之间的交互)
  • 别找(调用)我,我会找(调用)你
  • 类应该只有一个改变的理由

设计模式一览

这里只是列出了常用的一些模式,并不是全部,每一种模式只是做了简单的解释,具体的实践需要看以前的文章。

策略模式

定义算法族,分别封装起来,让它们之间可以互相替换;此模式让算法的变化独立于使用算法的客户。

比如,提供一些 setter 方法设置相应的“策略”,详细解释:飞机

观察者模式

在对象之间定义一对多依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并且自动更新。

当有多个观察者时,不要依赖他们的通知次序,因为是不确定的。详细解释:飞机

装饰者模式

动态的将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择。

符合开闭原则,在动态代理中应用广泛。
装饰者一般对组件的客户是透明的,装饰者会导致设计中出现许多小对象,过度使用会使系统变的复杂。
通常,我们会在调用真正的原始对象方法之前或者之后做一些动作。详细解释:飞机

工厂模式

工厂方法模式:定义了一个创建对象的接口,但子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类。
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

我们常用的是简单工厂模式,而工厂方法模式相对复杂一些,另外还有抽象工厂模式;他们都是属于工厂模式。
其中使用到了依赖倒置原则。详细解释:飞机

单例模式

确保一个类只有一个实例,并提供全局访问点。

最广泛的模式之一了吧,考察它的也相当多,写法也比较简单。
不过需要注意多线程并发的问题,懒汉式和饿汉式,使用双重判断的弊端、原子性和一致性,指令重排等,内容还是比较多的。
注意使用多个类加载器也会导致生成多实例。

命令模式

将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。
命令模式也支持可撤销的操作。

让请求调用者和请求接受者解耦,解耦的两者是通过命令对象进行沟通的,封装了其动作。
其中可能会使用“空对象模式”,它可以实现队列请求、日志请求等需求(实现日志系统和事务系统)。
宏命令是一种简单的延伸。详细解释:飞机

适配器&外观模式

将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

比如 JDK 中的枚举和迭代器?
其中其实还包含有另一个模式:外观模式,它让接口更简单(改变接口的原因),也将客户从组件的子系统解耦。
适配器的意图:“改变”接口符合客户的期望。
外观模式的意图:提供子系统的一个简化接口。

提供了一个统一的接口,用来访问子系统中的一群接口。|外观定义了一个高层接口,让子系统更容易使用。

当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观。
适配器:将一个对象包装起来以改变其接口。
装饰者:将一个对象包装起来增加新的行为和责任。
外观:将一群对象“包装”起来简化其接口。
详细解释:飞机

模板方法模式

在一个方法中,定义一个算法骨架,而将一些步骤延迟到子类中。
模板方法使子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

其中可以使用钩子,钩子控制根据某些条件是否执行某部分算法。
还使用了好莱坞原则:别调用我们,我们会调用你。
模板方法可以是一种代码复用的技巧,可以定义具体方法、抽象方法、钩子(可以选择要不要覆盖)。
为了防止子类改变模板方法中的算法,可以定义为 final,并且可以说工厂方法是模板方法的一种特殊版本。
详细解释:飞机

迭代器&组合模式

迭代器:
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
组合:
允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

他们两个往往能拼在一起使用, 使用组合我们能把相同的操作应用在组合和个别对象上(叶子节点),他们通常有(或者提取出)共同的接口,也就是说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别(只不过有的可能是空实现)。

状态模式

策略模式(主动)和状态模式是双胞胎,在出生时才分开。

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

通常,状态模式用类代表状态;Context 会将行为委托给当前的状态对象,状态类可以被多个 Context 实例共享。
同样,使用状态模式通常会导致设计中的类数量大大增加。

代理模式

为另一个对象提供一个替身或占位符以控制对这个对象的访问。
被代理的对象可以是远程的对象(远程代理)、创建开销大的对象(虚拟代理)或需要安全控制的对象(保护代理)。

代理模式要做的主要就是:控制和管理访问,在 AOP 中使用广泛吧。。。
Java 中的 RMI 就是一个典型的例子,远程代理是一般代理模式的一种实现。
虚拟代理:只有当我们真正需要一个对象的时候才创建它,对象创建后代理就会将请求直接委托给对象(显示图片前的“加载中”)。
为了让客户使用代理而不是真正的对象,一般是创建一个工厂,由工厂返回代理对象。
因为实际的代理类是运行时创建的,我们称这个 Java 技术为动态代理。
衍生类还有很多,比如防火墙代理、缓存代理、智能引用代理、同步代理等。
代理在结构上类似装饰者,但是目的不同;装饰者模式是为对象加上行为;而代理是控制访问。
Java 中内置代理支持,代理同样会造成你设计中类的数目增加。

复合模式

符合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题。

MVC 是典型的复合模式,其实控制器使用了策略模式(控制器是视图的策略)、模型使用了观察者模式、视图使用了组合模式。
Web 的开发人员对 MVC 进行适配,使它符合 B/S 模型,我们称这样的适配为 Model 2。

其他模式

模式是在某情景(Context)下,针对某问题的某种解决方案。
情景:就是应用某个模式的情况。这应该是会不断出现的情况。
问题:你想在某情景下达到的目标,但也可以是某情景下的约束。
解决方案:就是你所追求的一个通用的设计。

当然你可以改变模式。像设计原则一样,模式不是法律或准则,它只是指导方针,你可以改变模式来符合你的需要。

  • 架构模式
  • 应用模式
    三层架构、C/S 系统以及 Web 服务中
  • 桥接模式
    不只改变你的实现,也改变你的抽象;会增加复杂度
  • 生成器模式
    封装一个产品的构造过程,并允许按步骤构造。隐藏内部表现,产品的实现可以被替换,被用来创建组合结构。
  • 责任链模式
    当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。
    如果没有任何对象处理它,那它就可能不会被处理。
  • 蝇量模式
    如果想要某个类的一个实例能用来提供许多“虚拟实例”,就使用此模式。
  • 解释器模式
  • 中介者模式
    使用中介者模式来集中相关对象之间复杂的沟通和控制方式。设计不当,本身会过于复杂
  • 备忘录模式
    当你需要让对象返回之前的状态时(比如撤销操作),用于存储状态。
    存储和恢复的过程比较耗时,Java 中使用序列化
  • 原型模式
    当创建给定类的实例过程很昂贵或者很复杂时,就使用原型模式
  • 访问者模式
    当你想要为一个对象的组合增加新的能力,并且封装不重要时

与设计模式相处

  • 保持简单
    你的目标应该是简单,而不是如何才能应用上模式;正确的说法是:为了让你的设计简单且有弹性,有时候使用模式是最好的方法。
  • 考虑模式带来的后果
  • 知道何时使用
    这更多的是一种经验,要考虑后果,模式往往是在重构中加入的。
    找出你设计中会改变的区域,通常这是需要模式的迹象。
  • 利用模式进行重构
    比如:你的代码中充满了 if 语句,那么可能需要状态模式;或者意味着工厂模式将这些具体的依赖消除掉。
  • 拿掉不需要的模式
    当你的系统变得非常复杂,并且不需要预留任何弹性的时候,就不要使用模式。
  • 现在不需要,就别做
    你不确定这一块以后会不会变化就别“过度优化”

有些模式可能并不适合当前的情况,可以对其改编使其适合
不应急切于使用模式,而是致力于最能解决问题的简单方案
设计模式也会在你的设计中加入更多的层,这不但增加复杂性,而且效率下降。
并且可以适当了解下反模式(看起来是个好模式,真正采用后就会带来麻烦)

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~