设计模式
# 状态模式
状态模式用与解决系统中复杂对象的状态转换及不同状态下行为的封装问题。
定义:
允许一个对象在其内部状态改变时改变他的行为,对象看起来似乎修改了它的类。
包含三个角色:
Context(环境类):上下文类,拥有多种状态的的对象。包含:一个抽象的state的实例,定义当前状态,方法:request(),setState(State state)
class Contest { private State state private int Value public void setState(State state){ this.state = state } public void request() { state.handle(); } }
1
2
3
4
5
6
7
8
9
10State(抽象状态类):定义一个接口以封装与环境类的一个特定状态相关的行为(handle),声明不同状态对应的方法,其子类实现。
abstract class State{ public abstract void handle(); }
1
2
3ConcreteState(具体状态类 ):具体实现
class ConcreteState { public void handle() { xxx } }
1
2
3
4
5
状态转换方式:
环境类负责状态转换
public void changeState() { if (value == 0) { this.setState(new ConcreteStateA()) } else { this.setState(new ConcreteStateB()) } }
1
2
3
4
5
6
7具体状态实现类状态转换
public void changeState(Context ctx) { if (value == 0) { ctx.setState(new ConcreteStateA()) } else { ctx.setState(new ConcreteStateB()) } }
1
2
3
4
5
6
7
主要优点
- 封装了状态转换规则,状态转换封装在环境或具体状态类中,不是分散在一个个业务方法中
- 状态有关的行为放在一个类中,注入一个不同的状态对象即可使环境对象拥有不同的行为
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块。
- 多个环境对象共享一个状态对象,减少系统中对象的个数
主要缺点
- 增加系统的类和对象
- 结构和实现比较复杂
- 开闭原则支持不太好,增加新的状态需要修改那些负责状态转换的源码
适用场景
- 对象的行为依赖于它的状态,状态改变会导致行为的变化
- 代码中包含大量与对象状态有关的条件语句
# 观察者模式
一个对象发生变化时通知其他对象,其他对象将相应做出反应。
定义
定义对象之间一种一对多依赖关系,每当一个对象状态放生改变时,其相关依赖对象皆通知并被自动更新。别名:发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式,是一种对象行为型模式。
Subject(目标):目标又称为主题,观察的对象。方法:增加/删除对象,通知方法notify(),可以是接口,也能是抽象类具体类。
abstract class Submit { protected ArrayList observers<Observer> = new ArrayList(); public void attach(Observer observer){ observers.add(observer); } public void detach(Observer observer){ } public abstract void notify(); }
1
2
3
4
5
6
7
8
9
10ConcreteSubject(具体目标):目标的子类,通常包含经常发生改变的数据,当数据发生改变,通知各个观察者。
class ConcreteSubject extends Subject { public void notify() { for (Object obs:observers) { ((Observer)abs).update(); } } }
1
2
3
4
5
6
7Observer(观察者):定义观察目标的改变做出反应,一般定义为接口update方法
interface Observer{ public void update(); }
1
2
3ConcreteObserver(具体观察者): 维护一个指向具体目标对象的引用,存储具体观察者的有关状态,实现update方法
class ConcreteObserver implements Observer{ public void update() { xx } }
1
2
3
4
5
JDK事件模型1.0采用责任链模式,改为观察者模式;
MVC设计模式也是一种观察者模式;
主要优势
- 实现表示层和逻辑层分离。
- 目标和观察者之间建立一个抽象耦合。
- 观察者支持广播通信
- 观察者满足开闭原则
主要缺点
- 一个观察者观察目标对象有多个直接和间接观察者,所有观察者都通知会花费很多时间
- 观察者和观察目标存在循环依赖。观察目标会触发它们之间的循环依赖,导致系统崩溃
- 没有相应机制让观察者知道目标对象是怎么发生变化。
使用场景
- 一个抽象模式有两个方面,其中一个方面依赖另外一个方面,将这个两个方面封装在独立对象可以独立改变和复用
- 一个对象改变将导致一个或多个对象也发生改变。
- 需要系统创建一个触发链,A对象的行为将影响B对象,B对象行为将影响C对象..可以创建一种链式触发机制
# 访问者模式
主要包含访问者和被访问两个主要组成部分,被访问的元素有不同类型,且不同访问者可以对它们进行不同的访问操作。
定义
提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作,访问者模式是一种对象的行为型模式。
Vistor(抽象访问者):为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作
abstract class Vistor{ public abstract void visit(ConcreateElementA elementA); public abstract void Visit(ConcreateElementB elementB); public void visit(ConcreteElementC elementC) { // 操作C } }
1
2
3
4
5
6
7ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作作用于访问对象结构中的一种类型的元素
class ConcreteVisitor extends Visitor{ public void visit(ConcreteElementA elementA) { // 元素A的操作代码 } public void visit(ConcreteElementB elementB) { // 元素B的操作代码 } }
1
2
3
4
5
6
7
8Element(抽象元素):接口或抽象类,定义一个accept()方法,该方法通常以一个抽象访问者作为参数
interfact Element{ public void accept(Visitor visitor) }
1
2
3ConcreateElement(具体访问者):实现accept()方法,方法中调用访问者的访问方法,完成对一个元素的操作。
class ConcreteElmentA implements Element{ public void accept(Vistor vistor){ visitor.visit(this); } public void operationA() { // 业务方法 } }
1
2
3
4
5
6
7
8ObjectStructure(对象结构):元素集合,提供了遍历其内部元素的方法,可以结合组合模式来实现,也可以是一个简单的集合对象
class ObjectStructure{ private ArrayList<Element> list = new ArrayList<Element>(); public void accept(Vistor vistor) { Iterator i = list.iterator() while(i.hasNext()) { ((Element)i.next()).accept(vistor) } } }
1
2
3
4
5
6
7
8
9
访问者使用条件较为苛刻,本身复杂,实际使用频道不会很高。
主要优点
- 增加新的访问者操作很方便
- 有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个元素中,职责清晰。
- 用户能够在不修改现有元素类层次结构的情况下,定义用于该层次结构的操作
主要缺点
- 增加新的元素很困难。违背开闭原则
- 破坏封装。访问者要求访问对象并调用每个元素对象的操作。
适用场景
- 对象结构包含多个类型对象,希望对对象实施一些依赖其具体类型的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,需要避免这些操作"污染"这些对象的类.
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
# 策略模式
定义一些独立的类来封装不同的算法,目的是为了将使用和定义分开。
定义
定义一系列算法,将每个算法封装起来,并让它们可以相互替换。
Context(环境类):使用算法的角色,解决某个问题是可以采用多种策略,维持一个抽象策略的引用实例,用于定义所曹勇的策略
class Context{ private AbstractStrategy strategy; public void setStrategy(AbstractStrategy strategy){ this.strategy = strategy; } public void algorigthm() { strategy.algorithm(); } }
1
2
3
4
5
6
7
8
9Strategy(抽象策略类):声明抽象方法,是所有策略的分类,可以是抽象类、具体类,也可以是接口。
abstract class AbstractStrategy{ public abstract void algorithm(); }
1
2
3ConcreteStrategy(具体策略类):实现了再抽象策略类中声明的算法,在运行时,具体策略将覆盖环境类中定义的抽象策略类对象。
class ConcreateStrategyA extends AbstractStrategy{ public void algorithm() { // } }
1
2
3
4
5
典型应用实例:Container > LayoutManager > (FlowLayout,GridLayout)
主要优点
- 符合开闭原则,可以不修改原有系统的基础上选择算法或增加算法
- 提供了管理相关算法族的族。继承、公共的代码移到抽象策略类中,避免重复的代码。
- 可以替换继承关系的办法。
- 避免多重条件选择语句。
- 算法复用机制。
主要缺点
- 客户端必须知道所有的策略类。
- 会产生很多具体策略类。
- 无法同时在客户端使用多个策略类
使用场景
- 一个系统需要动态地在几种算法中选择一种
- 一个对象有很多的行为
- 不希望客户端知道复杂的与算法相关的数据结构,在具体策略中封装算法与相关数据结构,可以提高算法的保密性与安全性。
# 模板方法
模板方法提供了一个模板方法定义算法框架,具体步骤的实现可以在其子类中完成。
定义
定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变一个算法的结构即可重定义该算法的某些特定的步骤。模板方法是一种类型行为模式。
AbstractClass(抽象类):定义一一系列基本操作,可以是具体也可以是抽象,每个基本操作对应算法的一个步骤,在其子类可以重定义或实现这些步骤。
abstract class AbstractClass{ public void templateMethod() { primitiveOperation1(); primitiveOperation2(); } public void primitiveOperation1(){ xx } public abstract void primitiveOperation2(); }
1
2
3
4
5
6
7
8
9
10ConcreteClass(具体子类):抽象类的子类,实现父类中声明的抽象方法
class ConcreteClass extends AbstractClass{ public void primitiveOperation2(){ // 实现 } }
1
2
3
4
5
主要优点
- 在父类形式化地定义一个算法,子类实现细节,实现详细的处理算法不会改变算法中步骤的执行次序
- 代码复用技术
- 反向控制,子类覆盖父类的方法。符合单一职责、开闭原则
主要缺点
要为每个基本方法的不同实现提供一个子类,会导致类变多
适用场景
- 对一些复杂的算法进行分割,固定不变设计为模板方法和父类具体方法,改变的细节有子类实现
- 公共行为提取到父类,避免代码重复
- 通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反控。
# 命令模式
命令模式可以将请求发送者和接收者完全解耦,发送者和接收者之间没有直接引用关系,发送只要知道如何发送,不必知道如何完成请求
定义
将一个请求封装为一个对象,从而可用不同的请求对客户端进行参数化。
Command(抽象命令类);一般是接口或抽象类,声明了用与执行的execute()方法。
abstract class Command{ public abstract void execute(); }
1
2
3ConcreteCommand(具体命令类):抽象美丽类的子类,实现execute方法,他对应具体的接收者对象,将接收者对象的动作绑定其中。
public ConcreteCommand extends Command{ private Receiver receiver; public void execute() { receiver.action(); } }
1
2
3
4
5
6Invoker(调用者):请求发送者,它通过命令对象来执行请求,不需要在设计时确定其接收者,只与抽象命令类之间存在关系。
class Invoker{ private Command command; public Invoker(Command command) { this.command=command; } public void call() { command.execute(); } }
1
2
3
4
5
6
7
8
9Receiver(接收者):执行与请求相关的操作,具体实现对请求的业务处理。
class Receiver{ public void action(){ ... } }
1
2
3
4
5
主要优点
- 降低系统的耦合度。
- 新的命令容易加入到系统中。符合开闭原则。
- 容易设计一个命令队列或宏命令
- 为请求的撤销/恢复提供了一种设计和实现方案
主要缺点
过多具体命令类。
适用场景
- 请求调用者和接收者解耦。请求无效知道接收者存在,接收者也无须关心何时被调用
- 系统支持撤销/恢复操作
- 需要一组操作组合在一起形成宏命令
# 备忘录模式
提供了一种状态恢复的实现机制。
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
Originator(原发器):普通类,可以创建一个备忘录,存储当前内部状态,也可以恢复其内部状态。
public class Originator{ private String state; public Memento createMemento() { return new Memento(this); } public void restoreMemento(Memento m) { state = m.state } public String getState() { return this.state; } }
1
2
3
4
5
6
7
8
9
10
11
12Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。
class Memento{ private String state; public Memento(Originator o) { state = o.getState(); } }
1
2
3
4
5
6Caretaker(负责人):又称为管理者,负责保存备忘录,但是不能对备忘录的内容进行操作或检查。
public class Catetaker{ private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
1
2
3
4
5
6
7
8
9
主要优点
- 提供了一个状态恢复的实现机制
- 实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码改动。
主要缺点
资源消耗过大
使用场景
- 保存一个对象在某一个时刻的全部或部分状态,需要时恢复,提供撤销功能
- 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
# 解释器模式
定义
定义一个语言的文法,建立一个解释器来解耦该语言中的句子,这里的“语言”是指使用规定格式和语法的代码
AbstractExpression(抽象表达式):声明了抽象的解释操作,终结和非总结符表达式的公共父类
abstract class AbstractionExpression{ public abstract void interpret(Context ctx); }
1
2
3TerminalExpression(终结符表达式):AbstractExpression子类,每个总结符都是该类的一个实例
class TerminalExpression extends AbstractExpression{ public void interpret(Context ctx){ // 终结表达式的解释操作 } }
1
2
3
4
5NonterminalExpression(非终结符表达式):AbstractExpression子类,非终结符的解释操作,解释操作一般通过递归的方式来完成
class NoterminalExpression extends AbstractExpression{ private AbstractExpression left; private AbstractExpression right; public NoterminalExpression(AbstractExpression left, AbstractExpression right){ this.left = left; this.right = right; } pbulic void interpret(Context ctx){ // 递归调用每一个组成成员 } }
1
2
3
4
5
6
7
8
9
10
11Context(环境类):又称为上下文类,存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句
class Context{ private HashMap map = newHashMap(); publiv void assign(string key, string value){ // 设值 } public string lookup(String key){ // 获取存储的值 } }
1
2
3
4
5
6
7
8
9
# 组合模式
让叶子对象和容器对象的使用具有一致性。
定义
组合多个对象形成树形结构以表示具有“整体-部分”关系的层次结构
Component(抽象构件):接口或抽象类,为子构件和容器对象声明接口,可以包含所有子类公用有的行为的声明和实现
abstract class Component{ public abstract void add(Component c); public abstract void remove(Component c); public abstract void getChild(int i); public abstract void operation(Component c); }
1
2
3
4
5
6Leaf(叶子构件):没有子节点,实现抽象构件定义的行为
class Leaf extends Component{ public void add(Component c){ // 异常或错误提示 } public void remove(Component c){ // 异常或错误提示 } public void getChild(int i){ // 异常或错误提示 } public void operation(Component c){ // 操作 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14Composite(容器构件):包含子节点,实现抽象构件定义的行为
class Comosite extends Component{ private ArrayList<Component> list = new ArrayList<Component>() public void add(Component c){ list.add(c) } public void remove(Component c){ list.remove(c) } public void getChild(int i){ return list.get(i) } public void operation() { for (Object obj: list){ ((Component)obj).operation(); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java SE中的AWT和Swing包的设计就是基于组合模式。
主要优点
- 清晰定义分层次的复杂对象,表示对象的全部和部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致使用组合结构或其中的单个对象,不关心是单个还是组合,简化客户端。
- 增加容器和叶子很方便,符合开闭原则
- 为树形结构提供了灵活的解决方案
缺点
- 难对容器中的类型进行限制。
适用场景
- 整体和部分层次结构,希望忽略差异,客户端可以一致对待它们
- 树形结构
- 树形结构,分离出叶子对象和容器对象,且它们的类型不固定,希望增加新的类型。
桥接模式
系统中某个类存在独立变化的维度,通过桥接模式可以将这两个维度分离出来,使两者可以独立扩展。
定义
将抽象部分和其实现部分分离,使它们都可以独立地变化。
Abstraction(抽象类):抽象类或接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法
abstract class Abstraction{ protected Implementor impl; public void setImpl(Implementor impl){ this.impl = impl; } public abstract void operation();// 声明抽象业务方法 }
1
2
3
4
5
6
7RefinedAbstraction(扩充抽象类):定义接口,通常情况是具体类,不是抽象类,实现Abstraction中的抽象业务方法,可以调用Implementor中定义的业务方法。
class RefinedAbstraction extends Abstraction{ public void operation() { // 业务代码 impl.operationImpl(); } }
1
2
3
4
5
6Implementor(实现类接口):可以和Abstraction完全不同,定义基本操作。
interface Implementor{ public void operationImpl(); }
1
2
3ConcreteImplementor(具体实现类):实现Implementor接口
class ConcreteImplementor implement Implementor{ public void operationImpl(){ // xx } }
1
2
3
4
5
Java虚拟机和JDBC等驱动程序的核心模式之一。
主要优点
- 分离抽象和实现。
- 可以取代多继承方案
- 提高里系统的扩展性
主要缺点
- 增加系统理解和设计的难道,关联建立在抽象层,要求开发者一开始就要针对抽象层进行设计
- 要求正确识别系统中两个独立变化的维度,使用范围有一定局限性。
适用场景
- 抽象类和具体类之间增加更多的灵活性,避免两个层次建立静态的继承关系。
- 抽象和实现部分可以以继承方式扩展而互不影响。
- 类中存在独立变化的维度。
- 不希望通过继承方式扩展。
# 外观模式
一个子系统的外部和内部通讯通过一个统一的外观角色进行,将客户端与子系统中的内部复杂性分开。
定义
外部与一个子系统通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致入口。
- Facade(外观角色):定义相关系统的功能和职责
- SubSystem(子系统角色):可以有一个或多个子系统角色,不是单独的类,而是一个类的集合,实现子系统的功能。
class Facade{
private SubSystemA obj1= new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
..
public void method() {
obj1.method();
obj2.method();
....
}
}
2
3
4
5
6
7
8
9
10
11
简化客户端和子系统的交付,为复杂的子系统提供一个统一的入口。菜单和工具类就是B/S系统的外观角色。
主要优点
- 客户端屏蔽子系统组件
- 子系统和客户端之间松耦合
- 子系统修改对其他子系统没有影响
- 提供一个访问子系统的统一入口
主要缺点
- 不能好好限制客户端使用子系统类
- 设计不当,添加子系统需要修改外观模式的源码,违背开闭原则
适用场景
- 复杂子系统提供一个简单入口
- 客户端和多个子系统存在很大的依赖关系
- 层次结构中,可以使用外观模式定义系统中每一层的入口,层次之间不直接产生联系,通过外观降低耦合度
# 中介者模式
简化对象之间的复杂交互,迪米特法则的一个典型应用。
定义
封装一系列对象交互,使各对象不需要显式地相互引用,使其耦合松散,可以独立改变它们之间的交互。
Mediator(抽象中介者):定义一个接口,该接口用于与各同事对象之间进行通信
abstract class Mediator{ Protected ArrayList<Colleague> colleagues; // 注册,增加同事对象 public void register(Colleague colleague) { colleagues.add(colleagure) } public abstract void operation(c Colleague); }
1
2
3
4
5
6
7
8ConcreteMediator(具体中介者):Mediator子类,协调各个同事对象来协作行为,维持了对各个同事对象的引用
abstract class ConcreateMediator extends Mediator{ public void operation(c Colleague){ (Colleague)(Colleagues.get(0))).method1();// 通过中介者调用同事类方法 } }
1
2
3
4
5Colleague(抽象同事):各个同事类公有方法,声明了一些抽象方法来供子类实现,同时维持了一个抽象中介者类的引用,子类可以通过该引用来与中介者通信
abstract class Colleague{ protected Mediator meditor; public Colleague(Mediator mediator){ this.meditor = meditor } public abstract void method1(); public void update(){ mediator.operation(this);// 与中介者通信 } }
1
2
3
4
5
6
7
8
9
10ConcreateColleague(具体同事类):Colleague的子类,和其他同事通信先与中介通信,通过中介者间接完成与其他同事类的通信。
class ConcreteColleague extends Colleague{ public ConcreteColleague(Mediator mediator){ super(mediator); } public void method1(){ ... } }
1
2
3
4
5
6
7
8
客户端直接通过调用具体同事类的update()方法调用中介者和其他同事通信。
中介者将一个网状的系统结构变成一个以中介者为中心的星状结构,代替原来的多对多关系,事件驱动软件中用的比较广泛,特别是基于GUI的应用软件。
主要优点
- 简化了对象之间的交互,一对多代替了原来的多对多。
- 将各个同事之间解耦
- 减少同事子类生成,将原本分布于多个对象之间的行为集中在一起。
主要缺点
- 中介者类中包含大量同事之间的交互细节,可能会导致中介者复杂,系统难以维护
适用场景
- 系统中对象之间存在复杂引用,结构混乱
- 一个对象由于引用了其他很多对象,并且直接和对象通信,导致难以复用
- 想通过一个中间类分装多个类中的行为,而又不想生成太多子类。
# 享元模式
通过技术实现相同或相似对象的重用。
定义
支持大量细粒度对象的复用。
Flyweight(抽象享元类):一个接口或抽象类,声明了具体享元的公共方法
ConcreteFlyweight(具体享元类):实现Flyweight,内部提供了存储空间
UnsharedConcreteFlyweight(非共享具体享元类):不同共享的子类可设计为非共享具体享元类。
FlyweightFactory(享元工厂类):创建、管理享元对象。
class FlyweightFactory{ private HashMap flyweights = new HashMap(); public Flywight getFlyweight(string key){ if (flyweights.ContainsKey(key)){ return (Flywight)flyweights.get(key); } else { Flyweight fw = new ConcreteFlyweight(); flyweights.put(key, fw); return fw; } } }
1
2
3
4
5
6
7
8
9
10
11
12
系统中存在大量的相同或者相似的对象时,可以考虑使用该模式
主要优点
- 减少内存中的对象的数量
- 外部状态独立,不会影响其内部状态
主要缺点
- 系统变为复杂
- 对象可以共享,需要分离出内部状态和外部状态。
适用场景
- 系统中存在大量相同或相似的对象。
- 对象的大状态可以外部化。
- 多次重复使用享元对象