设计模式相关内容,参考了一些优秀的博客,其中一些源码例子换成了更为通俗易懂的例子。
六大原则
1. 单一职责原则 SRP
一个类只负责一个功能领域中的相应职责,或者说,就一个类而言,应该只有一个引起它变化的原因。
高内聚低耦合的指导方针。
2. 开闭原则 OCP
一个软件实体应该对扩展开放对修改关闭,即软件实体应该尽量在不修改原有代码的情况下进行扩展。
开闭原则是抽象类,其他五个原则是具体的实现类,即其他五个原则是开闭原则的具体形态。
提高了复用性和维护性。
3. 里氏替换原则 LSP
所有引用基类(父类)的地方必须能透明地使用其子类的对象。克服了继承的缺点。
4. 依赖倒置原则 DIP
高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,核心思想是面向接口编程而不是面向实现编程。
5. 接口隔离原则 ISP
定义多个专门的接口而不是使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
6. 迪米特原则 LoD
一个软件实体应当尽可能少地与其他实体发生相互作用。
总结
单一职责原则告诉我们实现类要职责单一
里氏替换原则告诉我们不要破坏继承体系
依赖倒置原则告诉我们要面向接口编程
接口隔离原则告诉我们在设计接口的时候要精简单一
迪米特原则告诉我们要降低耦合
开闭原则是总纲,告诉我们要对扩展开放,对修改关闭
UML
统一建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言,它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。
根据类与类之间的耦合度从弱到强排列,UML 中的类图有以下几种关系:依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系。其中泛化和实现的耦合度相等,它们是最强的。
创建型设计模式
1. 单例模式
确保一个类只有一个实例,并且提供该实例地全局访问点。
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
a. 懒汉式-线程不安全
如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
|
b. 饿汉式-线程安全
避免了实例化成员变量的线程安全问题,但是丢失了延迟实例化的节约资源的好处。
1
| private static Singleton uniqueInstance = new Singleton();
|
c. 懒汉式-线程安全
对公有静态函数加锁,使一个时间点内只能有一个线程进入,避免了多次实例化。
这会让线程阻塞时间过长,有性能问题。
1 2 3 4 5 6
| public static synchronized Singleton getUniqueInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; }
|
d. 双重校验锁-线程安全
先判断是否被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { if (uniqueInstance == null) { synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
|
Volatile 的作用
如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行instance = new Singleton();
这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
volatile的作用是 1.内存可见性: 所有线程都能看到共享内存的最新状态 2.防止指令重排。 (volatile修饰的变量并不是原子变量。只是变量的读和写变成了原子操作)
uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
- 为 instance 分配内存空间
- 初始化 instance
- 将 instance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 instance 不为空,因此返回 instance ,但此时 instance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
e. 静态内部类实现
当 Singleton 类被加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private Singleton() { } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getUniqueInstance() { return SingletonHolder.INSTANCE; } }
|
f. 枚举实现单例模式
有效防止反射攻击和序列化攻击。
1 2 3 4 5 6
| public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } }
|
2. 简单工厂模式
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
把实例化的操作放到一个类中,这个类就成了简单工厂类,让简单工厂类来决定用哪个子类来实例化。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class SimpleFactory { public Product createProduct(int type) { if (type == 1) { return new ConcreteProduct1(); } else if (type == 2) { return new ConcreteProduct2(); } return new ConcreteProduct(); } } public static void main(String[] args) { SimpleFactory simpleFactory = new SimpleFactory(); Product product = simpleFactory.createProduct(1); }
|
3. 工厂方法
定义了一个创建对象的接口,但由子类决定要实例化哪个类,工厂方法把实例化推迟到子类。
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
例如,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class Factory { abstract public Product factoryMethod(); public void doSomething() { Product product = factoryMethod(); } } public class ConcreteFactory1 extends Factory { public Product factoryMethod() { return new ConcreteProduct1(); } } public class ConcreteFactory2 extends Factory { public Product factoryMethod() { return new ConcreteProduct2(); } }
|
4. 抽象工厂
提供一个接口,用于创建相关的对象家族。
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。
至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。
抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。
角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class AbstractProductA {} public class AbstractProductB {} public class ProductA1 extends AbstractProductA {} public class ProductA2 extends AbstractProductA {} public class ProductB1 extends AbstractProductB {} public class ProductB2 extends AbstractProductB {} public abstract class AbstractFactory { abstract AbstractProductA createProductA(); abstract AbstractProductB createProductB(); } public class ConcreteFactory1 extends AbstractFactory { AbstractProductA createProductA() { return new ProductA1(); }
AbstractProductB createProductB() { return new ProductB1(); } } public class ConcreteFactory2 extends AbstractFactory { AbstractProductA createProductA() { return new ProductA2(); }
AbstractProductB createProductB() { return new ProductB2(); } } public class Client { public static void main(String[] args) { AbstractFactory abstractFactory = new ConcreteFactory1(); AbstractProductA productA = abstractFactory.createProductA(); AbstractProductB productB = abstractFactory.createProductB(); } }
|
5. 建造者模式
封装一个对象的构造过程,并允许按步骤构造。
举例子,建房子:
1 2 3 4 5 6 7 8 9 10 11
| public class House { private String floor; private String wall; private String roof; } House house = new House(); house.setRoof("屋顶"); house.setWall("墙"); house.setFloor("地板"); System.out.println(house.toString());
|
这样写的缺点是,当用户想要不同类型的房子,比如楼房和平房等,需要自己去修改房子的属性,修改完之后还要自己搭建。
创建建造者类,用于建造房子:
1 2 3 4 5 6
| public interface HouseBuilder { void makeFloor(); void makeWall(); void makeRoof(); House getHouse(); }
|
划分不同的建造者,整个平房:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class BungalowBuilder implements HouseBuilder { private House house = new House(); @Override public void makeFloor() { house.setFloor("平房地板"); } @Override public void makeWall() { house.setWall("平房墙"); } @Override public void makeRoof() { house.setRoof("平房屋顶"); } @Override public House getHouse() { return house; } }
|
这个时候,用户再想盖平房变成了这样:
1 2 3 4 5 6 7 8 9
| HouseBuilder builder = new BungalowBuilder();
builder.makeFloor(); builder.makeRoof(); builder.makeWall();
House house = builder.getHouse(); System.out.println(house.toString());
|
这样虽然更方便了,但是还要用户来指导施工队,那如果用户是个二把刀,搞不好就能干出把井建成烟囱,这个时候我们写一个监理,用户的要求告诉监理,到时候拎包入住就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class HouseDirector { private HouseBuilder houseBuilder; public HouseDirector(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } public void makeHouse(){ houseBuilder.makeWall(); houseBuilder.makeRoof(); houseBuilder.makeFloor(); } }
|
建造过程:
1 2 3 4 5 6
| HouseBuilder builder = new BungalowBuilder(); HouseDirector houseDirector = new HouseDirector(builder); houseDirector.makeHouse();
House house = builder.getHouse(); System.out.println(house.toString());
|
行为型
1. 责任链
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public abstract class Handler { protected Handler successor; public Handler(Handler successor) { this.successor = successor; } protected abstract void handleRequest(Request request); } public class ConcreteHandler1 extends Handler { public ConcreteHandler1(Handler successor) { super(successor); } @Override protected void handleRequest(Request request) { if (request.getType() == RequestType.TYPE1) { System.out.println(request.getName() + " is handle by ConcreteHandler1"); return; } if (successor != null) { successor.handleRequest(request); } } } public class ConcreteHandler2 extends Handler { public ConcreteHandler2(Handler successor) { super(successor); } @Override protected void handleRequest(Request request) { if (request.getType() == RequestType.TYPE2) { System.out.println(request.getName() + " is handle by ConcreteHandler2"); return; } if (successor != null) { successor.handleRequest(request); } } } public class Request { private RequestType type; private String name; public Request(RequestType type, String name) { this.type = type; this.name = name; } public RequestType getType() { return type; } public String getName() { return name; } } public enum RequestType { TYPE1, TYPE2 } public class Client { public static void main(String[] args) { Handler handler1 = new ConcreteHandler1(null); Handler handler2 = new ConcreteHandler2(handler1);
Request request1 = new Request(RequestType.TYPE1, "request1"); handler2.handleRequest(request1);
Request request2 = new Request(RequestType.TYPE2, "request2"); handler2.handleRequest(request2); } }
|
2. 命令
将命令封装成对象中,具有以下作用:
- 使用命令来参数化其它对象
- 将命令放入队列中进行排队
- 将命令的操作记录到日志中
- 支持可撤销的操作
结构:
- Command:命令
- Receiver:命令接收者,也就是命令真正的执行者
- Invoker:通过它来调用命令
- Client:可以设置命令与命令的接收者
设计一个可以控制灯开关的遥控器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class Light { public void on() {System.out.println("Light is on!");} public void off() {System.out.println("Light is off!");} }
public interface Command {void execute();} public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.on(); } } public class LightOffCommand implements Command { Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.off(); } }
public class Invoker { private Command[] onCommands; private Command[] offCommands; private final int slotNum = 7; public Invoker() { this.onCommands = new Command[slotNum]; this.offCommands = new Command[slotNum]; } public void setOnCommand(Command command, int slot) { onCommands[slot] = command; } public void setOffCommand(Command command, int slot) { offCommands[slot] = command; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); } } public class Client { public static void main(String[] args) { Invoker invoker = new Invoker(); Light light = new Light(); Command lightOnCommand = new LightOnCommand(light); Command lightOffCommand = new LightOffCommand(light); invoker.setOnCommand(lightOnCommand, 0); invoker.setOffCommand(lightOffCommand, 0); invoker.onButtonWasPushed(0); invoker.offButtonWasPushed(0); } }
|
3. 解释器
提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
举个例子,使用解释器模式设计一个“沂蒙通”交通卡,如果是“沂蒙通”或者其他城市“交通联合”的“成年人”、“孩子”,可以打折乘车,其他刷卡一律2元。
设计文法规则:
1 2 3
| <expression> ::= <city>的<person> <city> ::= 沂蒙通|交通联合 <person> ::= 成年人|儿童
|
设计类:
- 抽象表达式接口,Expression,包含了解释方法,interpret(String info).
- 终结符表达式类,Terminal Expression,用集合 Set 来保存满足条件的城市和人,并实现抽象表达式接口中的解释方法,用来判断被分析的字符串是否是集合中的终结符。
- 非终结符表达式,AndExpression,抽象表达式的子类,包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式的对象,并实现解释方法,用来判断被分析的字符串是否满足条件的卡片类型的满足条件的人员。
- 环境类,Context,包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide 调用表达式对象的解释方法来对被分析的字符串进行解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| interface Expression {public boolean interpret(String info);}
class TerminalExpression implements Expression { private Set<String> set = new HashSet<String>(); public TerminalExpression(String[] data) { for (int i = 0; i < data.length; i++) set.add(data[i]); } @Override public boolean interpret(String info) { if (set.contains(info)) { return true; } return false; } }
class AndExpression implements Expression { private Expression card = null; private Expression person = null; public AndExpression(Expression card, Expression person) { this.card = card; this.person = person; } @Override public boolean interpret(String info) { String s[] = info.split("的"); return card.interpret(s[0]) && person.interpret(s[1]); } }
class Context { private String[] cards = {"沂蒙通", "交通联合"}; private String[] persons = {"成年人", "儿童"}; private Expression cardPerson; public Context() { Expression card = new TerminalExpression(cards); Expression person = new TerminalExpression(persons); cardPerson = new AndExpression(card, person); } public void freeRide(String info) { boolean ok = cardPerson.interpret(info); if (ok) { System.out.println("您是" + info + ",您本次乘车打七折!"); } else { System.out.println(info + ",您持有非优惠卡,本次乘车扣费2元!"); } } }
public class InterpreterPatternDemo{ public static void main(String[] args){ Context bus=new Context(); bus.freeRide("沂蒙通的成年人"); bus.freeRide("交通联合的成年人"); bus.freeRide("杭州通的儿童"); bus.freeRide("八达通的儿童"); bus.freeRide("山东的儿童"); } }
|
4. 迭代器
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| interface Aggregate { public void add(Object obj); public void remove(Object obj); public Iterator getIterator(); }
class ConcreteAggregate implements Aggregate { private List<Object> list = new ArrayList<Object>(); @Override public void add(Object obj) {list.add(obj);} @Override public void remove(Object obj) {list.remove(obj);} @Override public Iterator getIterator() {return (new ConcreteIterator(list));} }
interface Iterator { Object first(); Object next(); boolean hasNext(); }
class ConcreteIterator implements Iterator { private List<Object> list = null; private int index = -1; public ConcreteIterator(List<Object> list) {this.list = list;} @Override public boolean hasNext() { if (index < list.size() - 1) { return true; } else { return false; } } @Override public Object first() { index = 0; Object obj = list.get(index); ; return obj; } @Override public Object next() { Object obj = null; if (this.hasNext()) { obj = list.get(++index); } return obj; } } public class IteratorPattern { public static void main(String[] args) { Aggregate ag = new ConcreteAggregate(); ag.add("中山大学"); ag.add("华南理工"); ag.add("韶关学院"); ag.add(1); System.out.print("聚合的内容有:"); Iterator it = ag.getIterator(); while (it.hasNext()) { Object ob = it.next(); System.out.print(ob.toString() + "\t"); } Object ob = it.first(); System.out.println("\nFirst:" + ob.toString()); } }
|
这里有一个很有意思的事情,因为 ArrayList 使用的是Object类型,设计到 Java 的语法糖,所以在这个例子中,增加一个随便类型的都可以运行。
5. 中介者
现实生活中,有些交互关系是网状结构,也就是说每个对象都必须记得要交互对象的详情,比如每个人必须记得他的朋友的所有电话、 QQ 号,如果有一个人要修改自己的信息,因为自己的信息分布在各处,需要逐一通知。如果将这种网状结构改为星型结构,就非常简单了。比如建一个公共电话本,谁要查询或者更新信息,就前往电话本上操作,大家都可以看到。
在 MVC 模式中,C 就充当了 V 和 M 的中介者,大大降低了系统的耦合性。
- Mediator,中介者接口,用于和同事们通信。
- Colleague,同事,相关对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| abstract class Colleague {public abstract void onEvent(Mediator mediator);} class Alarm extends Colleague { @Override public void onEvent(Mediator mediator) {mediator.doEvent("alarm");} public void doAlarm() {System.out.println("doAlarm()");} } class CoffeePot extends Colleague { @Override public void onEvent(Mediator mediator) {mediator.doEvent("coffeePot");} public void doCoffeePot() {System.out.println("doCoffeePot()");} } class Calender extends Colleague { @Override public void onEvent(Mediator mediator) {mediator.doEvent("calender");} public void doCalender() {System.out.println("doCalender()");} } class Sprinkler extends Colleague { @Override public void onEvent(Mediator mediator) {mediator.doEvent("sprinkler");} public void doSprinkler() {System.out.println("doSprinkler()");} }
abstract class Mediator {public abstract void doEvent(String eventType);} class ConcreteMediator extends Mediator { private Alarm alarm; private CoffeePot coffeePot; private Calender calender; private Sprinkler sprinkler; public ConcreteMediator(Alarm alarm, CoffeePot coffeePot, Calender calender, Sprinkler sprinkler) { this.alarm = alarm; this.coffeePot = coffeePot; this.calender = calender; this.sprinkler = sprinkler; } @Override public void doEvent(String eventType) { switch (eventType) { case "alarm": doAlarmEvent(); break; case "coffeePot": doCoffeePotEvent(); break; case "calender": doCalenderEvent(); break; default: doSprinklerEvent(); } } public void doAlarmEvent() { alarm.doAlarm(); coffeePot.doCoffeePot(); calender.doCalender(); sprinkler.doSprinkler(); } public void doCoffeePotEvent() {} public void doCalenderEvent() {} public void doSprinklerEvent() {} }
|
6. 备忘录
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态,也叫快照模式。
- Originator:原始对象,即发起人,记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- Caretaker:负责保存好备忘录,但是不能对备忘录进行修改和访问。
- Memento:备忘录,存储原始对象的的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Memento { private String state; public Memento(String state) {this.state = state;} public void setState(String state) {this.state = state;} public String getState() {return state;} }
class Originator { private String state; public void setState(String state) {this.state = state;} public String getState() {return state;} public Memento createMemento() {return new Memento(state);} public void restoreMemento(Memento m) {this.setState(m.getState());} }
class Caretaker { private Memento memento; public void setMemento(Memento m) {memento = m;} public Memento getMemento() {return memento;} }
public class MementoPattern { public static void main(String[] args) { Originator or = new Originator(); Caretaker cr = new Caretaker(); or.setState("S0"); System.out.println("初始状态:" + or.getState()); cr.setMemento(or.createMemento()); or.setState("S1"); System.out.println("新的状态:" + or.getState()); or.restoreMemento(cr.getMemento()); System.out.println("恢复状态:" + or.getState()); } }
|
7. 观察者
定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
角色:
- 抽象主题(Subject)角色:也叫抽象目标类,注册和移除观察者,并通知所有观察者,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| interface Observer {void response();}
class ConcreteObserver1 implements Observer { @Override public void response() {System.out.println("具体观察者1作出反应!");} }
class ConcreteObserver2 implements Observer { @Override public void response() {System.out.println("具体观察者2作出反应!");} }
abstract class Subject { protected List<Observer> observers = new ArrayList<Observer>(); public void add(Observer observer) {observers.add(observer); } public void remove(Observer observer) {observers.remove(observer);} public abstract void notifyObserver(); }
class ConcreteSubject extends Subject { @Override public void notifyObserver() { System.out.println("具体目标发生改变..."); System.out.println("--------------"); for (Object obs : observers) { ((Observer) obs).response(); } } }
|
8. 状态
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化
优点:
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点:
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
角色:
- 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
举例,用状态模式实现学生成绩的状态转换程序:
- 抽象状态类,包含了环境属性、状态名属性和当前分数属性,加减分方法和检查当前状态的方法。
- 三个状态实现类,负责检查自己的状态,并根据自己的情况转换。
- 环境类,包含了当前状态对象和加减分的方法,客户端用该方法改变成绩状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| public class ScoreStateTest { public static void main(String[] args) { ScoreContext account = new ScoreContext(); System.out.println("学生成绩状态测试:"); account.add(30); account.add(40); account.add(25); account.add(-15); account.add(-25); } }
class ScoreContext { private AbstractState state; ScoreContext() { state = new LowState(this); } public void setState(AbstractState state) { this.state = state; } public AbstractState getState() { return state; } public void add(int score) { state.addScore(score); } }
abstract class AbstractState { protected ScoreContext hj; protected String stateName; protected int score; public abstract void checkState(); public void addScore(int x) { score += x; System.out.print("加上:" + x + "分,\t当前分数:" + score); checkState(); System.out.println("分,\t当前状态:" + hj.getState().stateName); } }
class LowState extends AbstractState { public LowState(ScoreContext h) { hj = h; stateName = "不及格"; score = 0; } public LowState(AbstractState state) { hj = state.hj; stateName = "不及格"; score = state.score; } public void checkState() { if (score >= 90) { hj.setState(new HighState(this)); } else if (score >= 60) { hj.setState(new MiddleState(this)); } } }
class MiddleState extends AbstractState { public MiddleState(AbstractState state) { hj = state.hj; stateName = "中等"; score = state.score; } public void checkState() { if (score < 60) { hj.setState(new LowState(this)); } else if (score >= 90) { hj.setState(new HighState(this)); } } }
class HighState extends AbstractState { public HighState(AbstractState state) { hj = state.hj; stateName = "优秀"; score = state.score; } public void checkState() { if (score < 60) { hj.setState(new LowState(this)); } else if (score < 90) { hj.setState(new MiddleState(this)); } } }
|
9. 策略
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
优点:
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点:
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
和状态模式的比较:
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
角色:
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class StrategyPattern { public static void main(String[] args) { Context c = new Context(); Strategy s = new ConcreteStrategyA(); c.setStrategy(s); c.strategyMethod(); System.out.println("-----------------"); s = new ConcreteStrategyB(); c.setStrategy(s); c.strategyMethod(); } }
interface Strategy { public void strategyMethod(); }
class ConcreteStrategyA implements Strategy { @Override public void strategyMethod() { System.out.println("具体策略A的策略方法被访问!"); } }
class ConcreteStrategyB implements Strategy { @Override public void strategyMethod() { System.out.println("具体策略B的策略方法被访问!"); } }
class Context { private Strategy strategy; public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void strategyMethod() { strategy.strategyMethod(); } }
|
10. 模板方法
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
优点:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
角色:
- 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是整个算法中的一个步骤。
- 抽象方法:在抽象类中申明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
- 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class TemplateMethodPattern { public static void main(String[] args) { AbstractClass tm=new ConcreteClass(); tm.TemplateMethod(); } }
abstract class AbstractClass { public void TemplateMethod() { SpecificMethod(); abstractMethod1(); abstractMethod2(); } public void SpecificMethod() { System.out.println("抽象类中的具体方法被调用..."); } public abstract void abstractMethod1(); public abstract void abstractMethod2(); }
class ConcreteClass extends AbstractClass { public void abstractMethod1() { System.out.println("抽象方法1的实现被调用..."); } public void abstractMethod2() { System.out.println("抽象方法2的实现被调用..."); } }
|
11. 访问者
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
优点:
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
缺点:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
角色:
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public class VisitorPatternDemo { public static void main(String[] args) { ComputerPart computer = new Computer(); computer.accept(new ComputerPartDisplayVisitor()); } } interface ComputerPart { public void accept(ComputerPartVisitor computerPartVisitor); } class Keyboard implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } class Monitor implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } class Mouse implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } class Computer implements ComputerPart { ComputerPart[] parts; public Computer() { parts = new ComputerPart[]{new Mouse(), new Keyboard(), new Monitor()}; } @Override public void accept(ComputerPartVisitor computerPartVisitor) { for (int i = 0; i < parts.length; i++) { parts[i].accept(computerPartVisitor); } computerPartVisitor.visit(this); } } interface ComputerPartVisitor { public void visit(Computer computer); public void visit(Mouse mouse); public void visit(Keyboard keyboard); public void visit(Monitor monitor); } class ComputerPartDisplayVisitor implements ComputerPartVisitor { @Override public void visit(Computer computer) { System.out.println("Displaying Computer."); } @Override public void visit(Mouse mouse) { System.out.println("Displaying Mouse."); } @Override public void visit(Keyboard keyboard) { System.out.println("Displaying Keyboard."); } @Override public void visit(Monitor monitor) { System.out.println("Displaying Monitor."); } }
|
12. 空对象
在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Client { public static void main(String[] args) { AbstractOperation abstractOperation = func(-1); abstractOperation.request(); AbstractOperation abstractOperation1 = func(1); abstractOperation1.request(); } public static AbstractOperation func(int para) { if (para < 0) { return new NullOperation(); } return new RealOperation(); } } abstract class AbstractOperation { abstract void request(); } class RealOperation extends AbstractOperation { @Override void request() { System.out.println("do something"); } } class NullOperation extends AbstractOperation { @Override void request() { System.out.println("空对象 do nothing"); } }
|
结构型
1. 适配器
把一个类接口转换为另一个用户需要的接口。
优点:
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
缺点:
角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
Duck 和 Turkey 拥有不同的叫声,Duck 的叫声用 quack() 方法,而 Turkey 的叫声用 gobble() 方法。
现在将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public interface Duck {void quack();} public interface Turkey {void gobble();} public class WildTurkey implements Turkey { @Override public void gobble() { System.out.println("gobble!"); } } public class TurkeyAdapter implements Duck { Turkey turkey; public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } @Override public void quack() { turkey.gobble(); } } public class Client { public static void main(String[] args) { Turkey turkey = new WildTurkey(); Duck duck = new TurkeyAdapter(turkey); duck.quack(); } }
|
2. 桥接模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
优点:
- 由于抽象与实现分离,所以扩展能力强;
- 其实现细节对客户透明。
缺点:
- 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
角色:
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
RemoteControl 表示遥控器,指代 Abstraction。
TV 表示电视,指代 Implementor。
桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| public abstract class TV { public abstract void on(); public abstract void off(); public abstract void tuneChannel(); } public class Sony extends TV { @Override public void on() { System.out.println("Sony.on()"); } @Override public void off() { System.out.println("Sony.off()"); } @Override public void tuneChannel() { System.out.println("Sony.tuneChannel()"); } } public class RCA extends TV { @Override public void on() { System.out.println("RCA.on()"); } @Override public void off() { System.out.println("RCA.off()"); } @Override public void tuneChannel() { System.out.println("RCA.tuneChannel()"); } } public abstract class RemoteControl { protected TV tv; public RemoteControl(TV tv) { this.tv = tv; } public abstract void on(); public abstract void off(); public abstract void tuneChannel(); } public class ConcreteRemoteControl1 extends RemoteControl { public ConcreteRemoteControl1(TV tv) { super(tv); } @Override public void on() { System.out.println("ConcreteRemoteControl1.on()"); tv.on(); } @Override public void off() { System.out.println("ConcreteRemoteControl1.off()"); tv.off(); } @Override public void tuneChannel() { System.out.println("ConcreteRemoteControl1.tuneChannel()"); tv.tuneChannel(); } } public class ConcreteRemoteControl2 extends RemoteControl { public ConcreteRemoteControl2(TV tv) { super(tv); } @Override public void on() { System.out.println("ConcreteRemoteControl2.on()"); tv.on(); } @Override public void off() { System.out.println("ConcreteRemoteControl2.off()"); tv.off(); } @Override public void tuneChannel() { System.out.println("ConcreteRemoteControl2.tuneChannel()"); tv.tuneChannel(); } } public class Client { public static void main(String[] args) { RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA()); remoteControl1.on(); remoteControl1.off(); remoteControl1.tuneChannel(); RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony()); remoteControl2.on(); remoteControl2.off(); remoteControl2.tuneChannel(); } }
|