设计模式下篇-行为型
行为型主要解决类或对象之间交互的经典结构。行为型的设计模式有观察者模式,模板模式,策略模式,职责链模式,状态模式,迭代器模式,访问者模式,备忘录模式,命令模式,解释器模式和中介模式。
接下来以3个W和1个H来学习下这十一种设计模式
十一种模式介绍
观察者模式-Observer
什么是观察者模式
在对象之间定义一个一对多的依赖,当一个对象(被观察者)状态改变的时候,所有依赖的对象(观察者)都会自动收到通知。
为什么使用观察者模式
降低被观察者和观察者的耦合性,提高易用性。
如何使用观察者模式
被观察者,该被观察者有一组注册的观察者,当被观察者改变时,通知每一个观察者
// 观察者
public interface Observer {
void update(Observable o, Object arg);
}
// 被观察者的封装类
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
// something
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
// something
protected synchronized void setChanged() {
changed = true;
}
}
// 被观察者
public class MyObservable extends Observable {
public synchronized void setChanged() {
super.setChanged();
}
}
// 调用类
public class Main {
public static void main(String[] args) {
// java.util.Observable
MyObservable observable = new MyObservable();
observable.addObserver(new java.util.Observer() {
@Override
public void update(Observable o, Object arg) {
System.out.println("A"+arg.toString());
o.deleteObserver(this);
}
});
observable.addObserver(new java.util.Observer() {
@Override
public void update(Observable o, Object arg) {
System.out.println("B"+arg.toString());
}
});
observable.setChanged();
observable.notifyObservers("message1");
observable.setChanged();
observable.notifyObservers("message2");
}
}
// 打印如下:
Bmessage1
Amessage1
Bmessage2
额外学习
在jdk9,Observable被废弃,使用java.beans包下的PropertyChangeEvent和PropertyChangeListener来代替Observer和Observable的功能。
使用观察者模式实现简单的EventBus,git仓库:https://github.com/CNXMBuyu/design-pattern-study.git
- 改进方案-消息队列
设计模式核心是对程序进行解耦,在观察者模式中被观察者需要添加观察者,且当发起通知时要遍历所有的观察者。为了使解耦的更加彻底,就出现了消息队列。
模板模式-Template
什么是模板模式
将算法整体架构定义好,让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
为什么使用模板模式
代码复用
如何使用模板模式
定义算法整体架构(抽象类),将需要子类来实现的方法定义为抽象方法。
public abstract class Player {
public void initPlayer() {
// 保存玩家信息
savePlayer();
// 初始化玩家数据,因为每款游戏都不同,所以需要有子类具体实现
initData();
}
protected void savePlayer() {
System.out.println("已保存玩家信息");
}
/**
* 初始化玩家数据
*/
protected abstract void initData();
}
public class GamePlayer extends Player {
@Override
protected void initData() {
System.out.println("初始化游戏玩家");
}
}
相似功能:回调
A类事先注册某个函数F到B类,A类在调用B类的P函数的时候,B类反过来调用A类注册给它的F函数。
public class Player {
public void initPlayer(PlayerCallback playerCallback){
// 保存玩家信息
savePlayer();
// 初始化玩家数据,因为每款游戏都不同,所以需要有子类具体实现
playerCallback.initData();
}
protected void savePlayer(){
System.out.println("已保存玩家信息");
}
}
public interface PlayerCallback {
/**
* 初始化玩家数据
*/
void initData();
}
// 调用类
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.initPlayer(new PlayerCallback() {
@Override
public void initData() {
System.out.println("初始化游戏玩家数据");
}
});
}
}
回调函数的经典案例:spring的JdbcTemplate
模板模板是使用继承来实现,而回调函数是使用组合来实现。
策略模式-Strategy
什么是策略模式
策略模式就是定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。
为什么使用策略模式
解耦策略的定义、创建、使用这三部分
如何使用策略模式
分为定义、创建、使用三个步骤。
// 以下为策略的定义
// 策略接口
public interface Cache {
void addCache(String key, String content);
String getCache(String key);
}
// 策略的实现者
public class MemoryCache implements Cache {
private ConcurrentMap<String, String> cache = new ConcurrentHashMap<>();
@Override
public void addCache(String key, String content) {
cache.put(key, content);
System.out.println("memory add cache; key:" + key + ", content:" + content);
}
@Override
public String getCache(String key) {
System.out.println("memory get key:" + key + ", content:" + cache.get(key));
return cache.get(key);
}
}
// 策略的实现者
public class RedisCache implements Cache {
@Override
public void addCache(String key, String content) {
System.out.println("redis add cache; key:" + key + ", content:" + content);
}
@Override
public String getCache(String key) {
String content = "redis get key:" + key;
System.out.println(content);
return content;
}
}
// 策略的创建
public class CacheFactory {
public Cache getCache(String type){
String redisType = "redis";
if(redisType.equals(type)){
return RedisCacheSingleton.INSTANCE;
} else {
return MemoryCacheSingleton.INSTANCE;
}
}
/**
* 使用内部类的单例
*/
private static class RedisCacheSingleton{
private static RedisCache INSTANCE = new RedisCache();
}
/**
* 使用内部类的单例
*/
private static class MemoryCacheSingleton{
private static MemoryCache INSTANCE = new MemoryCache();
}
}
// 策略的使用
public class Main {
public static void main(String[] args) {
Cache redisCache = new CacheFactory().getCache("redis");
redisCache.addCache("a", "a content");
redisCache.getCache("a");
Cache memoryCache = new CacheFactory().getCache("memory");
memoryCache.addCache("a", "a content");
memoryCache.getCache("a");
}
}
职责链模式-Chain
什么是职责链模式
职责链模式是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
为什么使用职责链模式
将请求的发送和接收解耦。
如何使用职责链模式
public abstract class AbstractScript{
/**
* 执行初始化
*/
public abstract void doInit();
}
public class ScriptA extends AbstractScript {
@Override
public void doInit() {
System.out.println(this.getClass().getName());
}
}
public class ScriptB extends AbstractScript {
@Override
public void doInit() {
System.out.println(this.getClass().getName());
}
}
// 这里对职责器模式进行了一个小的修改,职责器模式定义“直到链上的某个接收对象能够处理它为止”,而我们处理它之后依然没有停止,知道执行结束
public class ScriptChain {
public void init(List<AbstractScript> initScriptList) {
// 排序
Collections.sort(initScriptList);
// 遍历执行所有脚本
for (AbstractScript script : initScriptList) {
if (script.filter(predicate -> {
return true;
})) {
script.doInit();
}
}
}
}
状态模式-State
什么是状态模式
状态机又叫有限状态机,由3个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。
为什么使用状态模式
当状态条状触发的动作比较复杂时,使用状态模式,可以提高代码的可维护性。
如何使用状态模式
订单有5个状态,分别为待付款,已关闭,已付款,已退款,已完成。事件有同意和拒绝。示例代码简单没有动作。
public class Order {
private OrderStateEnum status = OrderStateEnum.unpaid;
public OrderStateEnum getStatus() {
return status;
}
public void setStatus(OrderStateEnum status) {
this.status = status;
}
}
public enum OrderStateEnum {
unpaid, closed, paid, refund, finish
}
// 状态接口
public interface OrderState {
default void agree(OrderStateMachine orderStateMachine){
// nothing
}
default void reject(OrderStateMachine orderStateMachine){
// nothing
}
}
// 待付款的状态改变
public class UnpaidOrderState implements OrderState {
@Override
public void agree(OrderStateMachine orderStateMachine) {
// 处理已付款的相关事项
// .....
// 状态调整
orderStateMachine.setState(new PaidOrderState());
}
@Override
public void reject(OrderStateMachine orderStateMachine) {
orderStateMachine.setState(new ClosedOrderState());
}
}
// 已付款的状态改变
public class PaidOrderState implements OrderState {
@Override
public void agree(OrderStateMachine orderStateMachine) {
orderStateMachine.setState(new FinishOrderState());
}
@Override
public void reject(OrderStateMachine orderStateMachine) {
orderStateMachine.setState(new RefundedOrderState());
}
}
// 已退款-最终状态,没有任何操作
public class RefundedOrderState implements OrderState {}
// 已关闭-最终状态,没有任何操作
public class ClosedOrderState implements OrderState {}
// 已完成-最终状态,没有任何操作
public class FinishOrderState implements OrderState {}
// 状态机
public class OrderStateMachine {
private OrderState state;
private static Map<OrderStateEnum, OrderState> orderStateMap = new HashMap<>(10);
static{
orderStateMap.put(OrderStateEnum.unpaid, new UnpaidOrderState());
orderStateMap.put(OrderStateEnum.closed, new ClosedOrderState());
orderStateMap.put(OrderStateEnum.paid, new UnpaidOrderState());
orderStateMap.put(OrderStateEnum.refund, new RefundedOrderState());
orderStateMap.put(OrderStateEnum.finish, new FinishOrderState());
}
public OrderStateMachine(Order order){
state = orderStateMap.get(order.getStatus());
}
public void agree(){
state.agree(this);
}
public void reject(){
state.reject(this);
}
public OrderState getState() {
return state;
}
public void setState(OrderState state) {
this.state = state;
}
}
// 调用代码
public class Main {
public static void main(String[] args) {
Order order = new Order();
OrderStateMachine orderStateMachine = new OrderStateMachine(order);
orderStateMachine.agree();
System.out.println(orderStateMachine.getState().toString());
orderStateMachine.agree();
System.out.println(orderStateMachine.getState().toString());
}
}
// 打印日志如下:
cn.hgy.state.PaidOrderState@136432db
cn.hgy.state.FinishOrderState@7382f612
迭代器模式-Iterator
什么是迭代器模式
迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
为什么使用迭代器模式
- 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
- 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
- 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
如何使用迭代器模式
定义迭代器接口和具体的实现。
public interface Iterator<E> {
boolean hasNext();
E next();
}
访问者模式-Visitor
什么是访问者模式
访问者模式允许一个或者多个操作应用到一组对象上。
为什么使用访问者模式
解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。
如何使用访问者模式
public abstract class AbstractFile {
public abstract void read(IRead read);
}
public class Doc extends AbstractFile {
@Override
public void read(IRead read) {
read.read(this);
}
}
public class Pdf extends AbstractFile {
@Override
public void read(IRead read) {
read.read(this);
}
}
public interface IRead {
void read(Doc doc);
void read(Pdf pdf);
}
public class ReadImpl implements IRead {
@Override
public void read(Doc doc) {
System.out.println("doc");
}
@Override
public void read(Pdf pdf) {
System.out.println("pdf");
}
}
public class Main {
public static void main(String[] args) {
List<AbstractFile> fileList = new ArrayList<>();
fileList.add(new Doc());
fileList.add(new Pdf());
IRead read = new ReadImpl();
fileList.forEach(abstractFile -> {
// 编译会失败,所以改用abstractFile.read(read);
// read.read(abstractFile);
abstractFile.read(read);
});
}
}
备忘录模式-Memento
什么是备忘录模式
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
命令模式-Command
什么是命令模式
命令模式将请求封装为一个对象,这样可以使用不同的请求参数化其他对象,并且能够支持请求的排队执行、记录日志、撤销等附加功能。
为什么使用命令模式
解耦行为请求者与行为实现者。
在大部分编程语言中,函数是没法作为参数传递给其他函数的,也没法赋值给变量。借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。
如何使用命令模式
public interface Command {
void execute();
}
// 灯
public class Light {
public void on() {
System.out.println("灯亮了");
}
public void off() {
System.out.println("灯暗了");
}
}
// 开灯的命令
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
// 关灯的命令
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
// 使用类
public class Main {
public static void main(String[] args) {
Queue<Command> request = new LinkedList<>();
Light light = new Light();
request.add(new LightOnCommand(light));
request.add(new LightOffCommand(light));
request.forEach(command -> {
command.execute();
});
}
}
解释器模式-Interpreter
什么是解释器模式
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
中介模式-Mediator
什么是中介模式
中介模式定义了一个单独的对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
总结
观察者模式
观察者将自己注册到被观察者中,当被观察者发生改变时,将改变推送给每一个被观察中。
可以通过框架或者反射解决注册到被观察者中,或者使用消息队列解耦观察者和被观察者;可以通过异步执行观察者函数。
模板模式
定义一套流程,将其中部分功能放在子类实现。
模板模式需要继承实现,可以通过回调来实现类似功能,解决继承问题。
策略模式
定义一组功能相似的算法实现,可以互相替换。
职责链模式
职责链模式是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。
状态模式
适用于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机。
迭代器模式
迭代器模式用来遍历集合对象。解耦容器代码和遍历代码。
访问者模式
访问者模式允许一个或者多个操作应用到一组对象上。
备忘录模式
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
命令模式
命令模式将请求封装为一个对象,这样可以使用不同的请求参数化其他对象,并且能够支持请求的排队执行、记录日志、撤销等附加功能。
解释器模式
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法
中介模式
中介模式定义了一个单独的对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。