大话设计模式
桥接模式
适配器:改变已有的两个接口,让他们相容。
桥接模式:分离抽象化和实现,使两者的接口可以不同,目的是分离。
共同点:桥接和适配器都是让两个东西配合工作。
不同点:出发点不同
- 适配器:改变已有的两个接口,让他们相容。
- 桥接模式:分离抽象化和实现,使两者的接口可以不同,目的是分离
桥接是先有桥,才有两端的东西,适配是先有两边的东西,才有适配器,桥接是在桥好了之后,两边的东西还可以变化
何时使用
- 在抽象化角色和具体化角色之间增加更多的灵活性
- 实现化角色不能影响客户端
责任链模式
- 纯责任链模式要求具体处理必须在两个行为中二选一:承担责任 or 责任推给下家,并且最后一定会被某一个处理者对象所接受
- 责任链模式减低了发出命令的对象和处理命令的对象之间的耦合
- 一个链可以是一条线,一个树,也可以是一个环。链的拓扑结构可以是单连通的或多连通的,责任链模式并不指定责任链的拓扑结构。但是责任链模式要求在同一个时间里,命令只可以被传给一个下家(或被处理掉);而不可以传给多于一个下家。
举例:击鼓传花
- 客户端不知道最后处理者是谁
- Handler是个抽象的类,定义了抽象的handler方法,具体的ConcretHandler继承了Handler,并重写了父类的方法
1 | public class Handler |
什么时候使用
- 事先并不知道到底由哪一个处理者对象处理这个请求
- 当处理一个请求的处理者对象集合需要动态地指定时。
单体模式/单例模式
对象只要利用自己的属性完成了自己的任务
将类的责任集中到唯一的单体对象中,确保该类只有一个实例,并且为该类提供一个全局访问点。这就是单体模式的目的
分为饿汉、懒汉
观察者模式
- 观察者模式又名发布订阅模式。定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- 当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节,观察者模式提供了一种对象设计,让主题和观察者之间松耦合
- 主题只需要知道观察者实现了某个接口,不需要知道观察者的具体类是什么
- 任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现Observer接口的对象列表
- 有新类型的观察者出现时,主题的代码不需要修改。只要在新类里实现观察者接口,然后注册为观察者即可
- 改变主题或观察者任何一方,并不会影响另一方,只要他们之间的接口没有改变即可
角色分工
- 1、抽象目标角色:目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。
- 2、抽象观察者角色:为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。
- 3、具体目标角色:将有关状态存入各个具体观察者角色对象。当它的状态发生改变时, 向它的各个观察者发出通知。
- 4、具体观察者角色:存储有关状态,这些状态应与目标的状态保持一致。实现观察者角色的更新接口以使自身状态与目标的状态保持一致。在这个角色内也可以维护一个指向具体目标角色对象的引用
典型应用
- 1、侦听事件驱动程序设计中的外部事件
- 2、侦听/监视某个对象的状态变化
- 3、发布者/订阅者(publisher/subscriber)模型中,当一个外部事件(新的产品,消息的出现等等)被触发时,通知邮件列表中的订阅者。”
通信有两个版本
- 拉模式:目标角色发生变化后,仅仅告诉观察者橘色『我变化了』,变化的信息是观察者角色主动从目标角色中‘拉’出来的
- 推模式:目标角色发生变化后,通知发生变化的同时,还将变化的细节传递到观察者角色中去
中介者模式
- 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。简单点来说,将原来两个直接引用或者依赖的对象拆开,在中间加入一个‘中介’对象,使得两头的对象分别和‘中介’对象引用或者依赖
- 并不是所有的对象都需要加入‘中介’对象。如果对象之间的关系原本一目了然,中介对象的加入便是‘画蛇添足’
代理模式
作用:代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
在对已有的方法进行使用的时候出现需要对原有方法进行改进或者修改,这时候有两种改进选择:
- 修改原有方法来适应现在的使用方式(明显违背了‘对扩展开放、对修改关闭’(开闭原则))
- 或者使用一个‘第三者’方法来调用原有的方法并且对方法产生的结果进行一定的控制(将功能划分的更加清晰,有助于后面的维护)
代理模式的例子
远程代理:为一个位于不同的地址空间的对象提供一个局域代表对象
虚拟代理:根据需要将一个资源消耗很大或者比较复杂的对象延迟的真正需要时才创建,浏览器展示图片的时候先使用缩略图,图片的加载放到后台来操作
保护代理:控制对一个对象的访问权限。
智能引用:提供比对目标对象额外的服务,如对大幅图片浏览的控制、记录访问的流量
动态代理类,Java.lang.reflect
- 所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
享元模式(Flyweight)
在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象
在Flyweight模式中,由于要产生各种各样的对象,所以在Flyweight(享元)模式中常出现Factory模式。
Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个对象存储池(Flyweight Pool)来存放内部状态的对象
Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度
生成器模式(Builder模式)
有点像工厂模式,但是最终生成‘产品’的是Director而非Factory,Director可以使用builder来生成产品。而builder——生成器则遵循统一的接口,实现不同的内容,从而达到将一个复杂对象的构建与它的表示分离的目标
生成器模式是为了将构建复杂对象的过程和它的部件解耦,比如汽车装配,不仅部件非常多,装配过程(构建复杂对象的过程)也是一门技术活,所以生成器模式就是为了将部件与组装过程分开
工厂模式
工厂模式就相当于
A a = new A();
,但是工厂模式可以有更大的可扩展性与更小的修改量,比如在初始化对象时还需要做一些额外的工作,如赋值查询数据库简单工厂模式又叫静态工厂模式,顾名思义,它是用来实例化目标类的静态类
抽象工厂模式
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。
与工厂模式的区别:抽象工厂模式可以产生同一个产品或者同一个产品的不同类型
原型模式(Prototype)
原型模式是允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,通过拷贝实现
引入原型模式的本质在于利用已有的一个原型对象,快速的生成和原型对象一样的实例。你有一个A的实例a:A a = new A();现在你想生成一个一模一样的实例b,按照原型模式,应该是这样:A b = a.Clone();而不是重新再new一个A对象
优点:允许动态增加或减少产品类;缺点:每一个类都得有一个克隆方法
克隆分为浅拷贝和深拷贝
备忘录模式(Memento)
备忘录(Memento)模式又称标记(Token)模式。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态
备忘录模式中的角色
- 发起人:创建含有内部状态的备忘录对象,并使用备忘录对象存储状态
- 负责人:负责人保存备忘录对象,但不检查备忘录对象的内容
- 备忘录:备忘录对象将发起人对象的内部状态存起来,并保正其内容不被发起人对象之外的对象像读取
备忘录模式与命令模式的异同:
同:都可以保存状态,都拥有前进和后退
异:备忘录模式保存对象的状态,命令模式保存命令
Command模式:将命令当作一个对象进行保存,进行Redo ,Undo操作
操作型模式(State)
- State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换响应的对象;但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦
模板方法模式(Template)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类。子类通过继承这个抽象基类去实现各个步聚的抽象方法,而工作流程却由父类来控制
C++先定义后实现然后再调用其实就是模板方法的体现
模板模式是利用了虚函数的多态性,我们可以实现,也可以不实现
两个角色:
- 抽象模板类:定义一个或多个抽象操作,由子类去实现。这些操作称为基本操作。定义并实现一个具体操作,这个具体操作通过调用基本操作给确定顶级逻辑。这个具体操作称为模板方法
- 具体类:实现抽象模板类所定义的抽象操作
缺陷:
- 组合优先于继承,而模板方法模式是少数几个必须从非纯虚类(C++的称呼,就是java的interface)继承来实现的模式之一
- 从面向对象方法的本质来讲,父类负责抽象,子类负责具象。而模板方法恰好反过来了,父类实现了大部分功能,而子类实现少数纯虚方法,然后由父类的方法调用
理解模板方法模式与控制反转(IOC)
- ‘不要给我们打电话,我们会给你打电话’这是著名的好莱坞原则
- 模板方法模式充分的体现了‘好莱坞’原则。由父类完全控制着子类的逻辑,这就是控制反转。
因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。模板方法模式中,抽象类的模板方法应该声明为final的
模板方法模式中,基本方法应该声明为protcted abstract
模板方法模式与策略模式
- 模板方法模式与策略模式的作用相常类似。有时可以用策略模式替代模板方法模式。模板方法模式通过继承来实现代码复用,策略模式使用委托,委托比继承具有更大的灵活性。继承经常被错误的使用。策略模式把不确定的行为集中到一个接口中,并在主类委托这个接口
依赖反转
在面向对象编程领域中,依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。
该原则规定:
- 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
- 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
状态模式
定义:允许一个对象在其内部状态改变时改变它的行为。这个对象看起来似乎修改了它的类。
用途:能够让程序根据不同的外部情况来做出不同的响应,最直接的方法就是在程序中将这些可能发生的外部情况全部考虑到,使用if else 语句来进行代码响应选择。但是这种方法对于复杂一点的状态判断,就会显得杂乱无章,容易产生错误;而且增加一个新的状态将会带来大量的修改。这个时候‘能够修改自身’的状态模式的引入也许是个不错的主意
优点:1、封装转换过程,也就是转换规则。2、枚举可能的状态,因此,需要事先确定状态种类。
与策略模式的比较:在状态模式中,状态的变迁是由对象的内部条件决定,外界只需关心其接口,不必关心其状态对象的创建和转化;而策略模式里,采取何种策略由外部条件(C)决定。Strategy模式与State模式的结构形式几乎完全一样。但它们的应用场景(目的)却不一样,State模式重在强调对象内部状态的变化改变对象的行为,Strategy模式重在外部对策略的选择,策略的选择由外部条件决定,也就是说算法的动态的切换。但由于它们的结构是如此的相似,我们可以认为状态模式是完全封装且自修改的策略模式
策略模式
策略模式就是定义一系列的算法,把他们一个个封装起来,并且使它们可相互替换。Strategy模式使算法可独立于使用它的客户而变化
策略模式的优点:
- 提供管理相关算法族的办法
- 提供可替代继承关系的办法
- 避免了使用多重条件判断语句
策略模式的缺点:
- 客户必须知道所有的具体策略类及它们的区别
- 需要生成许多的策略类
使用方法:
- 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态的让一个对象在许多行为中选择一种行为。
- 2、如果系统需要动态地在几种算法中选择一种。那么这些算法可以包装到一个个的具体算法类里面,而这些算法类都是一个抽象算法类的子类。
- 3 、一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复发的和只与算法有关的数据
命令模式
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作
优点:
- 1、 命令模式将调用操作的请求对象与知道如何实现该操作的接收对象解耦。
- 2、具体命令角色可以被不同的请求者角色重用。
- 3、 你可将多个命令装配成一个复合命令。
- 4、增加新的具体命令角色很容易,因为这无需改变已有的类。
解释器模式
- 当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式
扩展型模式
- 扩展模式是指向模式添加元素,通常是对象类和属性。缺省的模式中带有可用于各个目录条目的大量对象类和属性。扩展模式之前,请先查看缺省模式中是否有可以使用而不需扩展模式的现有元素
装饰器模式
装饰模式:Decorator常被翻译成‘装饰’,我觉得翻译成‘油漆工’更形象点,油漆工(decorator)是用来刷油漆的,那么被刷油漆的对象我们称decoratee。这两种实体在Decorator模式中是必须的
动态给一个对象添加一些额外的职责,就象在墙上刷油漆。使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活
优点
装饰器与继承的目的都是扩展对象的功能,但装饰器提供了比继承更大的灵活性,可以动态的决定是‘粘上’还是‘去掉’一个装饰。通过使用不同的具体装饰类和这些类的排列组合,可以创建出很多不同行为的组合。
缺点
装饰器比继承关系使用更少的类,但比继承关系使用更多的对象,更多的对象会使查错变得更困难,特别是这些对象看上去很像的时候。
与适配器模式的区别
装饰器模式与适配器模式都叫做包装模式(Warpper),但装饰器与被装饰具有相同的接口(具体表现为都实现想同的Java Interface或装饰器是被装饰类的子类等)。但适配器与被适配的类具有不同的接口
迭代器模式
迭代器模式:分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据
优点:
- 1、支持以不同的方式遍历一个容器角色。根据实现方式的不同,效果上会有差别。
- 2、简化了容器的接口。但是在java Collection中为了提高可扩展性,容器还是提供了遍历的接口。
- 3、对同一个容器对象,可以同时进行多个遍历。因为遍历状态是保存在每一个迭代器对象中的
访问者模式
- 在每个自定义对象中预定义一个Accept(请求访问)方法,这个方法会以对象为参数,调用Visitor(访问者)对象的visit方法来操作这个对象