目录
设计程序过程中可能遇到如下情况:所有关键步骤已知,执行顺序已知,但有部分步骤的具体实现未知。此时可以运用模板模式:定义一个算法骨架,将具体实现延迟到子类中。
其实本质上是面向对象继承多态思想的一个具体实现方案。
包含以下组件:
抽象类:给出一个算法的骨架。
模板方法:按照某种顺序调用其他的基本方法,定义为final防止子类重写。
基本方法:实现具体步骤的方法,又可以分为:
具体子类:实现抽象类中的抽象方法和钩子方法。
优点:提高代码复用性,符合开闭原则。
缺点:对于不同的实现都需要定义一个子类,系统复杂度增加,也提高代码阅读难度。
定义了一系列功能相近的算法,并将每种算法封装起来,使他们可以相互替换,且算法的变化不会影响使用算法的客户。
使用场景:有多种功能相似且相互独立的算法需要动态地选择,且用户无须知道算法的实现细节。如排序算法中可以使用不同的比较器。
包含以下组件:
//抽象策略
public interface Strategy {
void do();
}
//具体策略
public class StrategyA implements Strategy {
public void do {
...
}
}
public class StrategyB implements Strategy {
public void do {
...
}
}
//环境类
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void userDo() {
strategy.do();
}
}
可以通过享元模式减少策略的构造。
优点:不同策略之间易于切换,避免多重逻辑语句,方便扩展,符合开闭原则。
将一个请求封装为一个对象,使发出请求和执行请求的责任分隔开,降低程序耦合度。
包含以下组件:
抽象命令类:定义命令接口和执行方法。
具体命令类:实现命令接口,通常会持有接收者,并调用接收者的方法来执行操作。
接收者/执行者类:真正执行命令的对象,具有执行对应命令的相关功能。
请求者/调用者类:是调用命令的外部接口,持有一系列命令对象,负责要求命令执行。
Java中Runable接口就是典型的命令模式:
//抽象命令类
public interface Command {
void execute();
}
//具体命令类
public class CreateCommand {
private String info;//命令包含的具体信息
private Executor executor;//命令执行者
public CreateCommand(Executor executor, String info) {
this.info = info;
this.executor = executor;
}
void execute() {
this.executor.executeCommand(String Info);
};
}
//======================
//执行者类
public class Executor {
//仅负责命令的执行
void executeCommand(String Info) {...}
}
//调用者类
public class Invoker {
private List<Command> commands;//持有一系列命令
public void setCommand(Command cmd) {
commands.add(cmd);
}
public void createCommand() {
if (commands.length == 0) return;
for (Command cmd: commands) {
cmd.execute();
}
}
}
优点:
缺点:可能导致系统中有太多命令类。
如果一个请求的处理按先后顺序涉及一系列的系统,为了避免请求发送者与多个处理对象耦合在一起,可以将请求处理视作一道链条,链条上的每一个节点记录下一个节点对象的引用,请求发送者可以让请求沿着链条传递,最终解决问题。
应用实例:
iOS中的点击事件的处理就是责任链模式,当用户点击手机上的一个控件时,系统会先检查相应控件能否相应点击,如不能,则上溯到父控件,直到底层窗口。
DNS域名解析服务(旧版):访问网站时,输入域名后,本地DNS服务器将请求发给根域名服务器,并在各级DNS服务器之间转发,最终返回IP地址。(新版中为了降低DNS服务器的负载,将转发的指责放在了本地DNS服务器中,但本质相似)
包含以下组件:
抽象处理者:定义一个处理请求的接口,包括处理方法以及一个后继处理者
具体处理者:实现处理请求的方法,判断能否处理请求,能则处理,否则交给后继处理者。
客户类:创建处理链,只需要向链条的头部发送请求,不关心请求的传递过程。
代码略。
优点:降低耦合度,提高可扩展性,简化系统复杂度。
缺点:
对于有状态的对象,将不同的状态独立地封装到对象中,并设置对应动作。
是有限状态机的改进版本,将复杂的“状态判断”逻辑封装到一个个单独的对象中,允许不同状态对不同操作独立地改变行为,降低了程序的耦合度。
Flutter中有一种Stateful构件,允许在页面运行过程中动态改变页面内容,就是由状态模式实现的。
包含以下组件:
上下文类:定义客户接口,存储和维护状态信息,并将具体操作委托给状态对象来处理。
抽象状态:规定不同状态所需要执行的操作。
具体状态:实现特定状态下的操作细节。或许可以用享元模式或单例模式减少额外的状态对象的开销?
结构上类似代理模式,可以将状态看作是上下文对象的代理。
//案例:电梯运行,上下文即为电梯
public class Context {
//定义对应状态的常量,有三个状态:开门、关门/停止、运行
public final static OpenState OPEN_STATE = new OpenState();
public final static RunState RUN_STATE = new RunState();
public final static StopState STOP_STATE = new StopState();
//当前状态对象变量
private LiftState state;
public LiftState getState() {
return state;
}
public void setState(LiftState state) {
this.state = state;
this.state.setContext(this);//状态对象会反过来操作上下文
}
//外部接口,调用状态对象进行实际操作
public void open() {
this.state.open();
}
public void run() {
this.state.run();
}
public void stop() {
this.state.stop();
}
}
//============定义状态===========
//抽象状态类
public abstract class LiftState {
//需要持有一个上下文变量,类似代理模式
protected Context context;
public void setContext(Context context) {
this.context = context;
}
//有几个状态就要定义几个改变状态的方法
public abstract void open();
public abstract void run();
public abstract void stop();
}
//---------定义对应状态的类---------
//电梯门开启状态
public class OpenState extends LiftState{
//状态内动作
@Override
public void open() {
//做开门动作
System.out.println("电梯门开启完毕。");
}
@Override
public void run() {}//假设电梯门开启时不能运行
@Override
public void stop() {
context.setState(Context.STOP_STATE);
System.out.println("电梯把门关上。");
context.stop();//状态改变后再进行一次状态内动作
}
}
//电梯运行状态
public class RunState extends LiftState{
@Override
public void open() {}//假设电梯运行时不能开门
@Override
public void run() {
//做运行动作
System.out.println("电梯在升降。");
}
@Override
public void stop() {
context.setState(Context.STOP_STATE);
System.out.println("电梯逐渐停下。");
context.stop();
}
}
//电梯停止/关门状态,假设电梯停止状态是运行和开门两个状态的中介
public class StopState extends LiftState{
@Override
public void open() {
context.setState(Context.OPEN_STATE);
System.out.println("电梯开门。");
context.open();
}
@Override
public void run() {
context.setState(Context.RUN_STATE);
System.out.println("电梯从静止开始运行。");
context.run();
}
@Override
public void stop() {
//做静止动作
System.out.println("电梯静止。");
}
}
//============调用方法=============
Context lift = new Context();
lift.setState(Context.STOP_STATE);
lift.run();
lift.stop();
lift.open();
假设有n种状态,那么在抽象状态中就应当对应有改变状态的n条方法,那么总共就需要实现n*n个具体的方法。
优点:
缺点:
又称作发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者对象监听某一个主题对象。这个主题对象状态变化时,会通知所有的观察者,使他们做出相应动作。
iOS开发中的通知Notification组件就是观察者模式。
包含以下组件:
抽象被观察者:将任意多观察者保存在一个集合中;提供一个接口,可以增加和删除观察者
具体被观察者:当自己的内部状态发生改变时,给所有注册过的观察者发通知。
抽象观察者:是观察者的抽象类,定义一个更新接口,收到通知时调用。
具体观察者:实现上述更新接口。
//抽象被观察者
public interface Observed {
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public void notify(String message);
}
//具体被观察者
public class NotificationCenter implements Observed {
private List<Observer> observers;
public NotificationCenter() {
observers = new LinkedList<>();
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notify(String message) {
for (Observer observer: observers) {
observers.update(message);
}
}
}
//======================
//抽象观察者
public interface Observer {
public void update(String message);
}
//具体观察者
public class Listener implements Observer {
public void update(String message) {
//...
}
}
优点:降低了观察者和被观察者的耦合;可以实现广播机制。 缺点:当观察者很多,会很耗系统资源。
又叫调停模式,如果一组对象之间存在两两交互,而对象数量一多,这个交互关系就很复杂;此时可以定义一个中介介入,对象只和中介交互,可以简化管理,降低耦合。
MVC模式中,controller就可以看作是model与view的中介。
包含以下组件:
抽象中介:定义同辈对象的注册和消息转发接口。
具体中介:维护一个列表来管理同辈对象。
抽象同辈:保存中介者的一个引用,实现消息发送和处理的接口。
具体同辈:实现上述接口。
代码略,简单来说就是同辈对象和中介互相持有,同辈发送信息的方法会调用中介的转发方法,中介的转发方法又调用同辈的接收方法。
//以信息聊天室为例,聊天室是中介者,用户是同辈
//抽象中介者类
public interface MessageMediator {
void transform(String message, Peer sender);
void add(Peer peer);
}
//具体中介者类
public class ChattingRoom implements MessageMediator{
private List<Peer> users;
public ChattingRoom() {
this.users = new ArrayList<>();
}
@Override
public void add(Peer peer) {
users.add(peer);
peer.setMessageMediator(this);
}
@Override
public void transform(String message, Peer sender) {
//此处稍微破坏了依赖原则
System.out.println(((User)sender).getName() + ": " + message);
for (Peer user: users) {
user.receive(message);
}
}
}
//=====================
//抽象同辈类
public interface Peer {
void send(String message);
void receive(String message);
void setMessageMediator(MessageMediator chattingRoom);
}
public class User implements Peer{
private String name;
private MessageMediator chattingRoom;
public User(String name) {
this.name = name;
}
@Override
public void setMessageMediator(MessageMediator chattingRoom) {
this.chattingRoom = chattingRoom;
}
public String getName() {
return name;
}
//处理消息的方法
@Override
public void send(String message) {
this.chattingRoom.transform(message, this);
}
@Override
public void receive(String message) {
System.out.println(name + "收到消息:" + message);
}
}
//调用方法
ChattingRoom chattingRoom = new ChattingRoom();
User zhangsan = new User("张三");
User lisi = new User("李四");
chattingRoom.add(zhangsan);
chattingRoom.add(lisi);
zhangsan.send("大家好我是张三");
lisi.send("大家好我是李四");
优点:降低耦合度,将多对多的关系变为一对一的关系,多个同辈对象的交互被集中到中介中管理,当交互行为变化时只需要修改中介对象即可。
缺点:中介类可能会变得庞大且难以维护。
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
包含以下组件:
抽象容器:定义添加、删除、创建迭代器等接口
具体容器
抽象迭代器:定义访问元素的接口,通常包含hasNext()、next()等
具体迭代器
//抽象迭代器
public interface Iterator<T> {
boolean hasNext();
T next();
}
//具体迭代器
public class ListIterator<T> implements Iterator<T>{
private List<T> list;//需要持有对应容器或其底层数据结构的引用,这里仅为示意
private int index = -1;
public ListIterator(List<T> list) {
this.list = list;
}
@Override
public boolean hasNext() {
return index < list.size() - 1;
}
@Override
public T next() {
if (hasNext()) {
return list.get(++index);
}
return null;
}
}
迭代器是在各个语言的各种容器中都有广泛应用。
优点:支持以不同方式遍历一个容器,只需要定义不同的迭代器即可
封装了作用于某种数据结构中的各元素的操作,可以在不改变数据结构的前提下定义作用于这些元素的新操作。
包含以下组件:
抽象访问者:定义了对元素的访问行为,参数就是元素,方法数量理论上等于元素的种类数。
具体访问者:实现了对每一种元素的具体行为。
抽象元素:定义一个接受访问者的方法,即,每一个元素都必须能被访问者访问。
具体元素:实现被访问者访问时的方法。
数据结构类:是存储元素的容器。
代码略,使用场景不明。
又叫快照模式,在不破坏封装性的情况下,捕获一个对象的内部状态,并在对象之外保存这个状态,以便以后需要时恢复到原先保存的状态。主要用于实现撤销操作。
包含以下组件:
发起人:记录当前时刻的内部信息,提供创建备忘录和恢复数据的功能,可以访问备忘录中的所有信息。
备忘录:负责存储发起人的内部状态,在需要时提供这些状态。
管理者:对备忘录进行管理,提供保存和获取备忘录信息的功能,但不能修改。
备忘录通过两种接口实现上面的结果:窄接口和宽接口。
//以记事本为例
//抽象备忘录,用于定义宽接口
public interface Memento {
String getState();
}
//发起者类
public class NoteBook {
//将备忘录设计成私有内部类,防止外部对象随意修改
private class NoteBookMemento implements Memento {
private String state;
//窄接口,只有发起者类能调用
NoteBookMemento(String state) {
this.state = state;
}
//宽接口,由于在接口中定义了这个方法,所以外部也可调用
@Override
public String getState() {
return state;
}
}
private String content;
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
//保存和恢复备忘录的接口
public Memento saveState() {
return new NoteBookMemento(content);
}
public void recoverState(Memento memento) {
content = memento.getState();
}
}
//备忘录管理类
public class MementoCareTaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
//外部可以调用宽接口
public String getState() {
return memento.getState();
}
}
定义一种语言的文法表示,并定义解释这种语言的方法。一般使用抽象语法树实现。多少沾点编译原理。
包含以下组件:
抽象表达式:定义解释器接口,约定解释器的解释操作,主要包含interpret()方法。
原子表达式:抽象表达式的一个子类,涉及的表达式往往有独立语义。文法中每一个原子语义都有一个具体的原子表达式对应。
关系表达式:抽象表达式的一个子类,涉及的表达式反映原子表达式的某种关系,不能单独使用。文法中每条规则都对应于一个关系表达式。
上下文:包含解释器需要的数据或公共功能,一般用来传递被所有解释器共享的数据。
客户端:将分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的方法。也可以通过上下文间接访问解释器。
这玩意有点复杂,代码就不写了,到要用的时候再说吧。
优点:
缺点:
评论区