面向对象程序设计进阶——设计模式 design patterns
最后更新于:2022-04-01 20:01:52
# 前言
设计模式(design pattern)是一套被反复使用、多数人知晓、经过分类编目的优秀代码设计经验的总结。关键词:重用代码、工程化、面向对象。设计模式起源于建筑设计学,最先由 [*Gang of Four*](http://baike.baidu.com/link?url=zWA56qEFuEzh6ikR326s4zGIw1ktjeXXFKMn8G-rgyp15KK72aMfW9k5VSiWW7ZhkDhpYgY0dd0_hHXDHP2pRa) 提升到了理论高度。
可复用面向对象体系分为两大系统:工具箱和框架。Java中的API属于工具箱(toolkit),Java EE (Enterprise Edition)属于框架(Framework)。设计模式是大神们在构造 Java EE 的时候的重要理论依据,学习设计模式有助于深入了解 Java EE。
我最近在[*实验楼*](https://www.shiyanlou.com/courses/100) 学习完了一门课程《Java进阶之设计模式》。截止至2015年12月16日,我已经在实验楼网站有效学习了960分钟,完整学习了5门课程。功夫在课外,我认为实验楼是一个能够开阔视野,快速入门新领域的地方。其主要特点是:课程设置广泛,内容深入浅出,提供linux环境。
GOF最早提出的设计模式总共有23个,分为三类型:创建型模式(5个),构造型模式(7个),行为型模式(11个)。后来,人们总结出了更多的设计模式,[参考wikipedia](https://en.wikipedia.org/wiki/Software_design_pattern)
- 创建型:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式
- 构造性:适配器模式、装饰模式、代理模式、组合模式、桥梁模式、外观模式、享元模式
- 行为型:模板方法模式、命令模式、责任链模式、迭代器模式、中介者模式、观察者模式、访问者模式、状态模式、解释器模式
实验楼里面讲解了其中的6个,这6个大体上是23个中使用频率最高的6个:工厂方法模式、抽象工厂模式、单例模式、适配器模式、装饰者模式、观察者模式。**我下面将首先简单介绍设计原则,然后小结一下上述常用6种模式**。我写本篇博客使用的参考资料包括:实验楼的课程、《Head First Design Patterns》、《设计模式(Java版)》、wikipedia、百度百科。
# 设计原则
-
开闭原则(OCP):open for extension, but close for modification。在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。这个是最最基础的原则。
-
单一职责原则(SRP):never be more than one reason for a class to change。专注做一件事情,仅有一个引起变化的原因,“职责”可以理解成“变化的原因”。唯有专注,才能够保证对象的高内聚;唯有单一,才能保证对象的细粒度。
-
里氏替换原则(LSP):Liskov提出:” Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S, where S is a subtype of T.” 这里的type,我理解成class。子类的对象可以无条件地替换父类的对象,并且不引起程序的改变。或者说:只要父类能出现的地方,子类就可以出现。
-
依赖倒置原则(DIP):High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. 即高层模块不依赖低层模块;高、低层模块都依赖其抽象;抽象不依赖细节,反过来细节依赖抽象。在JAVA中,所谓的抽象,就是接口或者抽象类;所谓细节,就是就实现类(能够实例化生成对象的类)。为什么说“倒置”?因为在传统的面向过程的设计中,高层次的模块依赖于低层次的模块,抽象层依赖于具体层(这样不好),倒置过来后,就成为了面向对象设计的一个原则了(这样子好)。
-
接口隔离原则(ISP):The dependency of one class to another should depend on the smallest possible interface. 类间的依赖关系应该建立在最小的接口上面。客户端不应该依赖他不需要的接口,只提供调用者需要的方法,屏蔽不需要的方法。一个接口代表一个角色,使用多个专门的接口比使用单一的总接口更好。
-
迪米特法则(LoD)又称最少知识原则(LKP): Each unit should only talk to its friends; don’t talk to strangers. Only talk to your immediate friends. Each unit should have only limited knowledge about other units. 曾经在美国有一个项目叫做Demeter。LoD is a specific case of loose coupling.此原则的核心观念就是类之间的弱耦合,在这种情况下,类的复用率才可以提升。
# 工厂方法模式
*Head First* (简称*HF* ) 说:There is more to making objects than just using the **new** operator. Instantiation is an activity that shouldn’t always be done in public and can often lead to coupling problems. 这就是工厂方法的来源。
首先简单提一下:简单工厂。*HF* : The Simple Factory isn’t actually a Design Pattern; it’s more of a programming idiom. 代码实现:把 new操作的过程封装到一个class SimpleFactory 的一个方法中。这个方法有一个参数,这个参数决定如何new。
简单工厂进行抽象化,得到工厂方法模式。工厂方法模式**定义**:Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method let a class defer instantiation to subclasses. 简而言之**,一个工厂父类作为“总管”,管理旗下的具体工厂子类,new 操作在具体工厂子类里面**。定义中”defer”的意思是:父类不立刻进行new操作,而是交给子类进行new操作。
**代码**:[工厂方法模式例子Java源代码 (推荐在新标签页打开)](http://blog.csdn.net/u013390476/article/details/50377680)。我改编自*HF* 的pizza例子(我本学期早上流行吃包子)
**小结**:(看完代码之后再看小结)
一个工厂子类,看上去很像一个简单工厂。确实如此,不过这一些工厂子类都继承自同一个工厂父类,需要实现同一个抽象方法createBaoZi,然后利用多态性。简单工厂像是一个“一锤子买卖”,而工厂方法模式则搭建了一个框架,让工厂子类决定生产那一个具体产品。工厂方法模式更加抽象,更具有通用性,耦合度更低。
在Main类里面,**new 一个工厂子类,把其引用赋给一个工厂父类的引用**。从代码层面可以看出,当需要扩充新的工厂的时候,增加一个继承工厂父类的子类就行了。Main类里面的那个**工厂父类的引用无需改变**。更进一步说,Main类里面的所有的用到工厂父类的引用的地方都无需改变,这就是体现了开闭原则的中的“close for modification”
# 抽象工厂模式
抽象工厂模式**定义**:Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
首先需要知道一个概念“a family of products”,翻译成“产品族”。*Head First* : Factory Method provides an abstract interface for creating **one product**. Abstract Factory provides an abstract interface for creating **a family of products**.
**产品族:**假设有三类抽象产品:手机、笔记本、平板。手机分为两个具体产品:iphone 6s和华为Mate8;电脑分为两个具体产品:Macbook Pro和Surface Book;平板分为iPad Air和Surface 3。那么从手机里面二选一,从笔记本里面二选一,从平板里面二选一,总共产生了8个不同的产品族。比如”iphone 6s + Macbook Pro + iPad Air”就是8个产品族中的1个(土豪!)。
**代码**:[抽象工厂模式例子Java源代码 (推荐在新标签页打开)](http://blog.csdn.net/u013390476/article/details/50396360)
**小结:**抽象工厂模式是对工厂方法的进一步抽象。是把一组具有同一主题的单独的工厂封装起来。抽象工厂可以看成是”工厂的工厂“,抽象工厂中的一个抽象方法,很像一个工厂方法模式。一个工厂有多抽象工厂,就像是有多个工厂方法模式。
# 单例模式
有时候存在这种需求:强制要求某个类只能实例化一个对象, one and only one. 单例模式解决了这种需求,其**定义**是:Singleton Pattern ensures a class has only one instance, and provide a global point of access to it.
应用场景:
- 要求生成唯一序列号的环境
- 需要共享访问点或共享数据,例如CSDN每一篇博客都有一个计数器,用来统计阅读人数,使用单例模式保持计数机的值
- 需要创建的对象消耗大量资源,比如IO和数据库资源
- 需要定义大量的静态常量或者静态方法(工具类)的环境,比如Java基础类库中的java.lang.Runtime类
仔细想想,如果使用全局变量,则可以实现定义中的后半句,但是无法实现定义中的前半句,而且使用全局变量会污染命名空间。
**代码:**[单例模式Java源代码,可以在新标签页打开](http://blog.csdn.net/u013390476/article/details/50405825#t4)
**小结:**单例模式,顾名思义,让一个类**有且只有一个**实例对象。这样做可以达到节约或者控制系统资源的目的。在代码层面,其最主要的特征是其**构造函数是私有的**。次要特点是:数据成员Singleton的实例引用是**静态**的,而且有一个**静态**的getInstance()方法,用来负责 new Singleton() 和返回 Singleton的实例引用。
# 观察者模式
这是一个常用的行为型模式,又称为“发布者——订阅者”模式。发布者比如报社,订阅者比如老百姓。清晨爷爷奶奶们出去晨练,看见一个卖报纸的小男孩在街上大声吆喝:“今天的报纸来了!”,然后爷爷奶奶们得到通知,都去买了报纸。报社是主题(Subject),爷爷奶奶们是观察者(Observer),卖报纸的小男孩,负责告诉Observer:“Subject更新了!”
**定义**:The Observation Pattern defines a one-to-many dependency between objects so that when one objects changes state, all of its dependents are notified and updated automatically.
**代码**:[观察者模式Java代码,可以在新标签页打开](http://blog.csdn.net/u013390476/article/details/50428014)
**小结**:定义中的one就是Subject, many就是Observers;one就是发布者,many就是订阅者;one就是事件源,many就是监听者。多个观察者(Observer)“围观”一个被观察者(Subject),可以说被观察者是万众瞩目的焦点。观察者和被观察者之间是抽象耦合(类图在上面的链接里),它们属于不同的抽象化层次,非常容易扩展。使用场景是:关联行为;事件多级触发;消息队列等。
# 适配器模式
这是一个结构型模式。周边适配器到处都是,比如手机充电器就是一个把插座上面的两孔国标插座,转换成USB接口。去了美国,怎么给我的荣耀手机充电?那就那一个适配器,把美国的两孔/三孔美标插座转换成两孔国标插座。
**定义**:The Adapter Pattern converts the interface of a class into another interface the client expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
在上面的情景中,荣耀手机就是定义中的“client”;两孔国标接口就是”interface the client expect”;美国的两孔/三孔美标插座就是定义中 “converts the interface of a class”中的”interface”;Adapter 负责动词”convert”。
**代码**:[适配器模式 Java源代码,可以在新标签页中打开](http://blog.csdn.net/u013390476/article/details/50444955)
**小结**:适配器Adapter的核心,是**实现Target接口, 组合Adaptee接口**。通过实现和组合,这两种类与类之间的关系,把两个本不兼容的类(Target 和 Adaptee)联系在一起。增强了类的透明性,松耦合,提高了类的复用,增强了代码的灵活性。
# 装饰者模式
这是一个结构型模式。
很多情况下,需要扩展功能,增加职责。如果说生成子类是静态的添加额外的职责的话,那么装饰者模式则提供了一种动态的方法,较为灵活。
**定义**:attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
**代码**:[装饰者模式 Java源代码,可以在新标签页中打开](http://blog.csdn.net/u013390476/article/details/50550256)
**小结**:关键是**Decorator同时继承和组合Component**,Component是被装饰者装饰的对象。装饰者和被装饰者可以独立发展,耦合度很低,很好的符合了开闭原则,是继承关系的一种替代方案。或者说,是一种对继承关系的有力的补充,可以解决类膨胀的问题。Java.IO就是参照装饰者模式设计的。
# 一点感悟
我现在是计算机科学与技术(CS)的大三上学期。设计模式的学习,我都是利用课外时间。我学习设计模式的初衷,除了见见世面之外,就是学习Java(我大一大二都是写C++,大三上学期才学Java),可谓一箭双雕。我下载了《Head First Design Patterns》在GitHub上的样例代码, which is written in Java。我一边看书,一边看代码,抄代码,一边改编代码。理论与敲代码结合,快速提升、强化基本能力。
- 敲了不少Java代码,对Java的”感觉”加深了,代码是需要积累的
- 我在eclipse装一个插件,用来绘制UML类图。我对eclipse的”感觉”加深了,体会到了插件的强大威力
- 继承,多态。以前写C++代码,只用封装,很少用继承和多态。毕竟平时主要在写”小算法”,没有涉及到大的宏观层面的设计。在设计模式中,大量运用继承(还有实现),多态
- decouple。这个词在*HF* 中高频率出现。降低耦合,面向接口,可以增强整个程序的扩展性,便于维护,适合多团队多人合作开发
- 通过几个设计模式,慢慢体会了设计模式中的6条原则,这些原则不是”教条主义“,不能刻板遵守。而是要慢慢把这些思想融入到程序设计中
- 关于算法和设计模式。网上看到一些评论,有一个评论很生动:算法像是”单兵作战和武器装备“,设计模式像是”仗列的阵型“。算法用来解决具体问题,设计模式用来合理的把算法隔离到各个正确的地方去。真正体会设计模式,还是需要很多年的实践积累
设计模式的博客就写到这里。最近在较短时间内,集中学习了6个常用设计模式,收获颇丰。**掌握了基本思想和方法**,在以后的学习和实践中,遇到新的需要使用的模式,我就能快速学会和运用。
[一个设计模式学习网站](http://www.runoob.com/design-pattern/design-pattern-tutorial.html)
';