写在前面
这23种设计模式算是打开思维,让我知道原来可以这样设计,以为就要具体问题具体分析了。
设计模式的学习并非一日之功,需要在今后的项目中不断学习,总结经验。
代码写的烂,没思路的时候时常翻一翻设计模式,寻找灵感。
纸上得来终觉浅,设计模式更多的是一种经验。
概述
java设计模式由四人帮提出。
主要基于两个面向对象设计原则:
- 对接口编程而不是对实现编程
- 优先使用对象组合而不是继承
总共有23种设计模式,可以分为三大类:
创建型模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
创建型模式分为以下几种:
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
结构性模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。
它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 过滤器模式(Filter Pattern)或标准模式(Criteria Pattern):这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。它结合多个标准来获得单一标准。
行为式模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF中最为庞大的一类,它包含以下 11 种模式。
模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
设计模式之间的关系:
设计模式的六大原则:
- 开闭原则 : 对扩展开放,对修改关闭。
- 里氏代换原则 :任何基类出现的地方,子类一定可以出现
- 依赖倒转原则 : 依赖接口编程
- 接口隔离原则 : 使用多个隔离的几口,比使用单个接口好
- 最少知道原则 :实体尽可能少与其他实体发生相互作用
- 合成复用原则 : 尽量使用合成、聚合的方式,而不是使用继承。
工厂模式
何时使用: 当不同条件下使用不同的实例时
使用场景:
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,”POP3”、”IMAP”、”HTTP”,可以把这三个作为产品类,共同实现一个接口
实现:
工厂类:
1 | public class ShapeFactory { |
工厂实现:
1 | public class FactoryPatternDemo { |
抽象工厂模式
如果要产生一种东西,那么需要一个工厂;要产生另一种东西,我们需要另一个工厂。世界上的东西太多了,我们需要无数个工厂,工厂和工厂其实是差不多的,我们把工厂向上抽象,形成一个工厂的工厂,这就是抽象工厂模式。
详解单例模式
关键代码:构造函数是私有的。
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
1 | public class SingleObject { |
原型模式
object类的clone()方法里面有一个native方法,native方法的效率远高于java的非native方法。
需要实现一个Cloneable接口,Cloneable接口是不包含任何方法的,只是一个标识接口。
之所以要这么设计,是因为接口里面是不能写方法实现的,且clone()方法是一个公用的native方法,那么这个方法就只能被放在所以类的父类即Object类中,所以Cloneable接口只能作为一个标识接口
对比 代理模式、适配器模式、装饰者模式
代理模式和装饰者模式区别
当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器
代理模式和适配器模式区别
代理和被代理实现相同的接口
适配器和被适配实现不同的接口
桥接模式
桥接模式说白了就是一个系统可以从多个角度来分析时,我们选择一个角度以抽象类的继承的方式实现,其他的角度以接口实现的方式实现,然后将接口放入到抽象类中实现组合。
设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
• 第一种设计方案是为每一种形状都提供一套各种颜色的版本。
• 第二种设计方案是根据实际需要对形状和颜色进行组合。
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
模式结构
桥接模式包含如下角色:
• Abstraction:抽象类
• RefinedAbstraction:扩充抽象类
• Implementor:实现类接口
• ConcreteImplementor:具体实现类
模式分析
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
• 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
• 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
• 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
典型的实现类接口代码:
1 | 1 public interface Implementor |
典型的抽象类代码:
1 | 1 public abstract class Abstraction |
典型的扩充抽象类代码:
1 | 1 public class RefinedAbstraction extends Abstraction |
享元模式
面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。享元模式通过共享技术实现相同或相似对象的重用提高系统资源的利用率。
在享元模式中可以共享的相同内容称为 内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为 外部状态(Extrinsic State),其中外部状态和内部状态是相互独立的,外部状态的变化不会引起内部状态的变化。由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。也就是说,享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
- 内部状态 是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
- 外部状态 是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)(用于存储具有相同内部状态的享元对象)。享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
参考:https://blog.csdn.net/justloveyou_/article/details/55045638
过滤器模式
使用foreach对一个列表进行过滤,不同的过滤器对列表的过滤策略不同
对比 外观模式和模板模式
要做某件事情,需要多个类的协作。如果不适用外观模式,那么用户代码将是:串行的一个一个的调用各个类的方法,完成一件事情。这样的过程显然不容易维护,一旦这个步骤需要发生改变,那么就需要修改所有的代码。
而外观模式,则是,使用一个类将这些动作封装在一个函数中。其实外观模式就是个函数,为了复用而存在的函数。外观模式就是起了一层包装作用。外观模式属于事后模式,一般在接手一个复杂的老系统时,因为内部多个子系统间有非常复杂的耦合关联,为了屏蔽这种“坏味道”对新系统的影响,这时候就建立一个门面,新系统只需要面向这个门面(一系列接口)就可以了。
模板模式扩展了外观模式,当做同一件事情,但是这件事情中可以细分出来的一个步骤可以有不同实现。模板模式的实现是基于虚函数的重载实现的。外观模式只是封装了各个类的调用。而模板模式则是加了继承,采用多态的方式来实现统一操作的不同实现。
外观模式实现的是多各类协作共同完成一件事情,因此我们使用一个函数来封装这些操作,(将这个函数放在一个类中)。
模板模式实现的是一个类的多个函数组合完成一件事情,虽然类的每个函数可能有不同的实现方式,但是流程是一样的。因此使用继承方式,在类中新建一个函数依次调用其他的成员函数。模板模式中将一个大函数拆分为小函数,然后又将小函数封装为一个函数。
观察者模式
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
目标与观察者之间建立了一套触发机制。
它的主要缺点如下:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率
观察者模式的主要角色如下:
- 抽象主题:提供了一个观察者列表和对观察者的管理方法和一个抽象的通知方法
- 具体主题:实现具体的通知方法(调用 观察者)
- 抽象观察者:包含一个抽象的收到通知后的方法
- 具体观察者:实现具体的收到通知后的方法。
策略模式
策略模式就是一个类封装一个算法,实现一个算法接口,然后搞个Context来调用这些算法。
迭代器模式
迭代器模式主要包含以下角色。
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含hasNext()方法,first方法,next()方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
1 | //抽象迭代器 |
命令模式
在调用者和实现者之间实现命令类,从而实现调用者和实现者的解耦。
责任链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
责任链模式就是链表
核心代码:在抽象处理者中,调用了当前处理者后,要紧接着调用next。