软件设计模式是一套被反复使用的,多数人知晓的、经过分类编写、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案,它是解决特定问题的一系列套路,是前辈们代码设计经验的总结,具有一定的普遍性,可以反复使用。软件设计模式的目的是为了提高代码的可重用性,代码的可读性和代码的可靠性。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点:
在阅读Java设计模式中,我曾经摘抄道:“世界上80%的人,都在默默无闻中渡过自己的一辈子,都在抱怨中过着每天的日子,都在对社会以及对周围的亲人和朋友不满足来打发日志。”自己也是一个喜欢抱怨的人,经常抱怨自己的工作、抱怨身边的人, 抱怨真的很可怕。曾国藩曾经说过:“牢骚太甚者,其后必多抑塞。盖无故而怨天,则天必不许,无故而尤人,则人必不服,感应之理然也。”抱怨太多,就是折磨自己。而且抱怨解决不了问题。与其抱怨,不如改变。有时间抱怨,不如想办法改变,让自己摆脱困境,很多时候,我们虽然无法改变已经发生的事实,但是我们可以转变自己的心态。
2.1 行为型设计模式的特征
在Java设计模式及实践中,一共有12种设计模式的实践属于此类。
由于一些较为常见的设计模式已经习得,便不再此处赘述简要阐述,在之后的行文中,主要阐述如下的行为型设计模式:
责任链,通过其名称,我们可以认识到,有一个对象的链的存在,通过链表的方式串联起来,而请求的处理则是沿着这条链进行,直到有一个对象能处理这个请求。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,
并沿着这条链传递该请求,直到有一个对象处理它为止。
这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接受者.
Handler
定义一个处理请求的接口。
(可选)实现后继链。
ConcreteHandler
处理它所负责的请求。
可访问它的后继者。
如果可处理该请求,就处理之;否则将该请求转发给它的后继者。
Client
向链上的具体处理者(ConcreteHandler)对象提交请求。在实际使用中,由Client负责对象链的构建,实例化一个处理器的链,然后在第一个对象中调用handleRequest,其实如果想要实现的简单一点,只要把具体处理者放在一个列表中,然后让请求轮流走过找到合适的处理着直到请求得到处理也行的。
protected Handler successor;
public void setSuccessor(Successor successor) {
this.successor = successor;
}
public void handleRequest(Request request) {
if (canHandle(request)) {
// code to handle the request
} else {
successor.handleRequest()
}
}
责任链模式之实现
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
1.抽象出待执行的动作以参数化某对象。
2.在不同的时刻指定、排列和执行请求。
3.支持撤销和重做操作操作。
4.支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。
5.用构建在原语操作上的高层操作构造一个系统。
6.异步方法调用。
直观的想法
public void performAction(ActionEvent e) {
Object obj = e.getSource();
if (obj = fileNewNameMenuItem) {
doFileNewAction();
} else if (obj = fileOpenMenuItem) {
doFileOpenAction();
} else if (obj = fileOpenRecentMenuItem) {
doFileOpenRecentMenuAction()
} else {
doFileSaveAction();
}
}
上图是用来处理一个客户端菜单的按钮的处理逻辑。起初想法是在一个大的if-else中处理所有可能出现的命令。
之后决定进行如下的修改,使用命令模式:
public interface Command {
public void execute();
}
public class OpenMenuItem extends JMenuItem implements Command {
public void execute() {
// code to open a document
}
}
publicvoid performAction(ActionEvent e) {
Command command = (Command)e.getSource();
command.execute();
}
可以看到代码消灭了冗长的if-else,使得代码更加紧凑,可读性也更突出。
在《重构2》中,Martin也阐述了使用命令模式来取代函数的用法。可以学习。
命令模式实践
这种模式主要是用来解释句子或表达式。首先要知道的是句子和表达式的结构,要有一个表达式或句子的内部表示。可以使用解释器模式来处理逆波兰表达式
解释器模式一般使用组合模式来定义对象结构的内部表示。
public interface Expression {
public void interpret();
}
public class Number implements Expression {
private float number;
public Number(float number) {
this.number = number;
}
public void interpret() {
return number;
}
}
public class Plus implements Expression {
Expression left;
Expression right;
public void interpret() {
return left.interpret() + right.interpret();
}
}
因此,通过这些类型我们建立了一课语法树:操作是节点,变量和数字是叶子。结构非常复杂,可用于解释表达式。
提供一种方法顺序遍历对象元素而不暴露其内部实现的方法。
其实迭代器非常常用,几乎每天都要使用。
public interface Iterator
{
public Object next();
public boolean hasNext();
}
只不过要在实现的容器中实现该迭代器接口,以某种策略遍历容器中的元素。
迭代器的理解与实现
我们不断提到解耦的重要性,当减少依赖时,我们可以扩展、开发和测试不同的模块,而无须了解其他模块的实现细节。
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
Subject(目标)
目标知道它的观察者。可以有任意多个观察者观察同一个目标。
提供注册和删除观察者对象的接口。
Observer(观察者)
为那些在目标发生改变时需获得通知的对象定义一个更新接口。
ConcreteSubject(具体目标)
将有关状态存入各ConcreteObserver对象。
当它的状态发生改变时,向它的各个观察者发出通知。
ConcreteObserver(具体观察者)
维护一个指向ConcreteSubject对象的引用。
存储有关状态,这些状态应与目标的状态保持一致。
实现Observer的更新接口以使自身状态与目标的状态保持一致
观察者模式
本质是解耦了多个同事之间的关系,每个对象都有中介者对象的引用,只跟中介者对象打交道,我们通过中介者对象统一管理这些交互关系。
即同事与同事之间并不交互,而每个同事都保存了一个中介者对象,由该对象与其他同事进行交互。
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
Mediator
中介者定义一个接口用于与各同事(Colleague)对象通信。
ConcreteMediator
具体中介者通过协调各同事对象实现协作行为。
了解并维护它的各个同事。
Colleagueclass
每一个同事类都知道它的中介者对象。
每一个同事对象在需与其他的同事通信的时候,与它的中介者通信
参见GOF23设计模式之中介者模式的实现
在跨部门协作时,比如与算法组沟通,要开发一个新的算法,我们是要与算法组长沟通的,而不会直接和算法组具体的某个人进行沟通的。此时组长,就相当于一个中介者。
public interface Mediator {
public void register(String dname, Deparment d);
public void command(String dname);
}
/** * 时间:2015年4月12日09:59:50
* 抽象同事类:抽象出所有部门的共同之处。
* */
package com.bjsxt.cn.mediator;
public interface Deparment {
public void selfAction();
public void outAction();
}
封装是面向对象设计的基本原则之一。我们知道类都承担一项职责。当向对象添加功能时,我们可能意识到需要保存其内部状态,以便能够在以后阶段恢复它。这很常见,尤其在Notepad++和IDEA这类工具中,撤销操作和取消撤销等动作便使用了备忘录模式。
备忘录模式的目的是用于保存对象的内部状态而不破坏其封装结构,并在以后阶段恢复其状态。执行的是类似还原现场的工作
Memento
备忘录存储原发器对象的内部状态。
Originator
类似于客户端
原发器创建一个备忘录,用以记录当前时刻它的内部状态。
使用备忘录恢复内部状态.
Caretaker
负责保存好备忘录。
不能对备忘录的内容进行操作或检查。
package com.chapter3.memento;
public class CarOriginator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return this.state;
}
public Memento saveState() {
return new Memento(this.state);
}
public void restoreState(Memento memento) {
this.state = memento.getState();
}
/**
* Memento class
*/
public static class Memento {
private final String state;
public Memento(String state) {
this.state = state;
}
private String getState() {
return state;
}
}
}
state表示测试运行时汽车的参数,这是我们想要保存的对象的状态。
package com.chapter3.memento;
public class CarCaretaker {
public static void main(String s[]) {
new CarCaretaker().runMechanicTest();
}
public void runMechanicTest() {
CarOriginator.Memento savedState = new CarOriginator.Memento("");
CarOriginator originator = new CarOriginator();
originator.setState("State1");
originator.setState("State2");
savedState = originator.saveState();
originator.setState("State3");
originator.restoreState(savedState);
System.out.println("final state:" + originator.getState());
}
}
只要需要执行回滚操作,就会使用备忘录。
状态模式知识面向对象设计中的有限状态机的实现。
状态模式定义:对象行为的变化是由于状态的变化引入,那么即当内部状态发生变化的时候,就会改变对象的行为,而这种改变视乎就改变了整个类。
一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
这个状态通常用一个或多个枚举常量表示。
通常,有多个操作包含这一相同的条件结构。
State模式将每一个条件分支放入一个独立的类中。
这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
Java重构-策略模式、状态模式、卫语句
阿里巴巴出品的Java开发手册提出,如果if-else分支过多,则可以使用卫语句、状态模式、策略模式替换。
行为模式的一个特定情况,是我们需要改变解决一个问题与另外一个问题的方式。本质是分离算法,选择实现。策略模式对应于解决某一个问题的一个算法组,允许用户从该算法族中任意选择一个算法解决某一问题,同时可以方便的更换算法或增加新的算法,并由客户端决定调用哪个算法。
策略模式与状态模式非常相似,状态模式多用于有限状态机,状态之间的跃迁影响到行为,而策略则多对应于某一个问题的具体算法。以禅道Bug而言,对于一个Bug而言有三种状态,已激活,延迟处理和已关闭。
Strategy
定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。
ConcreteStrategy
以Strategy接口实现某具体算法。
Context
用一个ConcreteStrategy对象来配置。
维护一个对Strategy对象的引用。
可定义一个接口来让Stategy访问它的数据。
策略模式+工厂模式 去除if-else
解锁新姿势:探讨复杂的 if-else 语句“优雅处理”的思路
使用模板方法模式的目的是避免编写重复的代码,以便开发人员可以专注于核心逻辑。
模板方法模式现实的最好方式是抽象类。抽象类可以提供给我们所知道的实现区域,默认实现和为实现而保持开发的区域即为抽象。
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
这个模板方法已经得到了充分的实践了。在智慧营区发过程中Task、EventResponse频繁使用的就是模板方法。maven的生命周期如下:
模拟类具有相同的结构,但是什么也不做。
智慧营区中NullServiceEntity扮演了类似的结构。在JDK8引入的Optional中,有一个单例的成员empty也起到了类似的作用。
访问者模式的目的是将操作与其操作的对象结构分开,允许添加新操作而不更改结构类。
访问者模式在单个类中定义了一组操作:它为每个类型的对象定义一个方法,该方法来自它必须操作的结构。只需创建另一个访问者即可添加一组新操作。
命令模式与访问者有很大的相似性。
如果将一个抽象的save方法添加到基本形状类中,并且为每个形状扩展它,我们就解决了这个问题。这个解决方案是最直观的,但不是最好的。首先,每个类都应该只承担一项责任。汽车,如果需要更改我们想要保存每个形状的格式会发生什么?如果是想相同的方法来生成XML,那么是否必须更改为JSON格式?这种设计绝对不遵循开放/闭合原则。
因此可以把这一组操作聚拢成一个访问者,而操作的对象不需要发生变化,需要新的操作时,只需要添加一个新的访问者即可。
2019-12-21 22:15周六于湖墅新村
责任链实现
观察者模式迭代器的理解与实现