一:模板方法模式
什么是模板模式
模板模式首先要有一个抽象类,这个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
定义:「定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。」
比如在造房子一样,地基,铺线,房子户型都是一样的,由开发商决定,但是在交房之后,室内的装修风格和场景布置却是由业主决定,在这个场景中,开发商其实就是一个抽象类,地基,铺线,房子户型都是可以复用的,但是装修却是不可复用的,必须由业主决定,此时的每一个业主的房子就是一个实现的子类。
模板方法的实现条件注意:
必须是一个抽象类。
抽象类有一个模板方法,其中定义了算法骨架。
为了防止恶意操作,模板方法必须加上final关键词。
模板方法中除了复用的代码,其他的关键代码必须是抽象的,子类可以继承实现。
优点
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
它在父类中提取了公共的部分代码,便于代码复用。
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
简单演示
比如游戏的运行需要如下几个步骤:
初始化游戏
开始游戏
结束游戏
上述的三个步骤可以是模板类的抽象方法,由具体的子类实现,比如足球游戏。
定义模板类,必须是一个抽象类,模板方法必须是final修饰。
public abstract class Game {
//抽象方法
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板方法
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
定义实现类,足球游戏,继承模板类,实现其中的三个抽象方法
public class Football extends Game {
@Override
void endPlay() {
System.out.println("足球游戏结束......");
}
@Override
void initialize() {
System.out.println("足球游戏初始化中......");
}
@Override
void startPlay() {
System.out.println("足球游侠开始了......");
}
}
此时写一个测试方法,运行足球游戏,如下:
public class TemplatePatternDemo {
public static void main(String[] args) {
//创建足球游戏实例
Game game = new Football();
//开始游戏
game.play();
}
}
输出结果如下:
足球游戏初始化中......
足球游侠开始了......
足球游戏结束......
模板抽象类
AbstractPlatformTransactionManager
是Spring中的模板抽象类
,来看看它的继承关系图:
总结
模板模式是一个很重要,易扩展的模式,提高了代码复用性,在Spring中有着广泛的应用,比如JDBCTemplate
,AbstractPlatformTransactionManager
,这些实现都用到了模板模式。
二:备忘录模式
定义
备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
备忘录结构图
角色:
-
Originator: 原发器。负责创建一个备忘录,用以记录当前对象的内部状态,通过也可以使用它来利用备忘录恢复内部状态。同时原发器还可以根据需要决定Memento存储Originator的那些内部状态。
-
Memento: 备忘录。用于存储Originator的内部状态,并且可以防止Originator以外的对象访问Memento。在备忘录Memento中有两个接口,其中Caretaker只能看到备忘录中的窄接口,它只能将备忘录传递给其他对象。Originator可以看到宽接口,允许它访问返回到先前状态的所有数据。
-
Caretaker: 负责人。负责保存好备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。
在备忘录模式中,最重要的就是备忘录 Memento
了。备忘录中存储的就是原发器的部分或者所有的状态信息,而这些状态信息是不能够被其他对象所访问了,也就是说我们是不可能在备忘录之外的对象来存储这些状态信息,如果暴漏了内部状态信息就违反了封装的原则,故备忘录是除了原发器外其他对象都是不可以访问的。
所以为了实现备忘录模式的封装,我们需要对备忘录的访问做些控制:
-
对原发器:可以访问备忘录里的所有信息。
-
对负责人:不可以访问备忘录里面的数据,但是他可以保存备忘录并且可以将备忘录传递给其他对象。
-
其他对象:不可访问也不可以保存,它只负责接收从负责人那里传递过来的备忘录同时恢复原发器的状态。
所以就备忘录模式而言理想的情况就是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
优缺点
优点:
-
给用户提供了一种可以恢复状态的机制。可以是用户能够比较方便地回到某个历史的状态。
-
实现了信息的封装。使得用户不需要关心状态的保存细节。
缺点:
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存
实例
以游戏存档为例
备忘录对象:
/**
* 备忘录
*/
public class Memento {
/**
* 血量
*/
private int bloodFlow;
/**
* 魔力值
*/
private int magicPoint;
public int getBloodFlow() {
return bloodFlow;
}
public void setBloodFlow(int bloodFlow) {
this.bloodFlow = bloodFlow;
}
public int getMagicPoint() {
return magicPoint;
}
public void setMagicPoint(int magicPoint) {
this.magicPoint = magicPoint;
}
public Memento(int bloodFlow,int magicPoint){
this.bloodFlow = bloodFlow;
this.magicPoint = magicPoint;
}
}
角色对象
/**
* 角色
*/
public class Role {
/**
* 血量
*/
private int bloodFlow;
/**
* 魔力值
*/
private int magicPoint;
public Role(int bloodFlow,int magicPoint){
this.bloodFlow = bloodFlow;
this.magicPoint = magicPoint;
}
public int getBloodFlow() {
return bloodFlow;
}
public void setBloodFlow(int bloodFlow) {
this.bloodFlow = bloodFlow;
}
public int getMagicPoint() {
return magicPoint;
}
public void setMagicPoint(int magicPoint) {
this.magicPoint = magicPoint;
}
/**
* @desc 展示角色当前状态
* @return void
*/
public void display(){
System.out.println("用户当前状态:");
System.out.println("血量:" + getBloodFlow() + ";蓝量:" + getMagicPoint());
}
/**
* @desc 保持存档、当前状态
* @return
* @return Memento
*/
public Memento saveMemento(){
return new Memento(getBloodFlow(), getMagicPoint());
}
/**
* @desc 恢复存档
* @param memento
* @return void
*/
public void restoreMemento(Memento memento){
this.bloodFlow = memento.getBloodFlow();
this.magicPoint = memento.getMagicPoint();
}
}
管理:
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
输出结果:
----------游戏存档时----------
用户当前状态:
血量:100;蓝量:100
----------游戏进行中----------
用户当前状态:
血量:20;蓝量:20
----------游戏读档后----------
用户当前状态:
血量:100;蓝量:100
总结:资源消耗大,如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
三:命令模式
定义:
命令模式(Command Pattern),在软件设计中,经常需要向某些对象发送请求,但是不知道请求的接收者是谁,也不知道被请求的操作是谁。只需要在程序运行时指定具体的请求接受者即可。此时,可以使用命令模式来进行设计
命令模式使得请求发送者与请求接受者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦
在命令模式中,会将一个请求封装为一个对象,以便使用不用参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作
可以理解为,将军发布命令,士兵去执行。其中有几个角色,将军(命令发布者),士兵(命令的具体执行者),命令(连接将军和士兵)
Invocker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
- Invoke是调用者角色
- Command是命令者角色,需要执行的所有命令都在这,可以是接口或者抽象类
- Receive是接受者角色,知道如何实施和执行一个请求相关的操作
- ConcreteCommand:将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
2.命令模式的应用(解决智能生活项目)
Command.java
package command;
/**
* 创建命令接口
* @author Admin
*
*/
public interface Command {
// 执行动作(操作)
public void execute();
// 撤销动作(操作)
public void undo();
}
LightReceive.java
package command;
public class LightReceiver {
public void on() {
System.out.println("电灯打开了");
}
public void off() {
System.out.println("电灯关闭了");
}
}
LightOnCommand.java
package command;
public class LightOnCommand implements Command {
// 聚合LightReceiver
LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver=lightReceiver;
}
@Override
public void execute() {
// 调用接收者的方法
lightReceiver.on();
}
@Override
public void undo() {
// 调用接收者的方法
lightReceiver.off();
}
}
LightOffCommand.java
package command;
public class LightOffCommand implements Command {
// 聚合LightReceiver
LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver=lightReceiver;
}
@Override
public void execute() {
// 调用接收者的方法
lightReceiver.off();
}
@Override
public void undo() {
// 调用接收者的方法
lightReceiver.on();
}
}
NoCommand.java
package command;
/**
* 没有任何命令,即空执行,用于初始化按钮
* 当调用空命令时,对象什么也不做,
* 这也是一种设计模式,可以省掉对空判断
* @author Admin
*
*/
public class NoCommand implements Command{
@Override
public void execute() {
}
@Override
public void undo() {
}
}
RemoteController.java
package command;
public class RemoteController {
// 开按钮命令
Command[]onCommands;
// 关按钮
Command[]offCommands;
// 撤销命令
Command undoCommand;
// 构造器,初始化
public RemoteController() {
onCommands=new Command[5];
offCommands=new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i]=new NoCommand();
offCommands[i]=new NoCommand();
}
}
// 给按钮设置需要的命令
public void setCommand(int no,Command onCommand,Command offCommand) {
onCommands[no]=onCommand;
offCommands[no]=offCommand;
}
// 按下开按钮
public void onButtonWasPushed(int no) {
// 找到按下的开按钮,并调用对应方法
onCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand=onCommands[no];
}
// 按下关按钮
public void offButtonWasPushed(int no) {
// 找到按下的关按钮,并调用对应方法
offCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand=offCommands[no];
}
// 按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}
}
此时,扩展其他按钮功能
TVReceive.java
package command;
/**
* 电视机
* @author Admin
*
*/
public class TVRecevice {
public void on() {
System.out.println("电视机打开了");
}
public void off() {
System.out.println("电视机关闭了");
}
}
TVCommand.java
package command;
public class TVOnCommand implements Command {
// 聚合LightReceiver
TVRecevice tvRecevice;
public TVOnCommand(TVRecevice tv) {
this.tvRecevice=tv;
}
@Override
public void execute() {
// 调用接收者的方法
tvRecevice.on();
}
@Override
public void undo() {
// 调用接收者的方法
tvRecevice.off();
}
}
TVOffCommand.java
package command;
public class TVOffCommand implements Command{
// 聚合LightReceiver
TVRecevice tvRecevice;
public TVOffCommand(TVRecevice tv) {
this.tvRecevice=tv;
}
@Override
public void execute() {
// 调用接收者的方法
tvRecevice.off();
}
@Override
public void undo() {
// 调用接收者的方法
tvRecevice.on();
}
}
Client.java
package command;
public class Client {
public static void main(String[] args) {
// 使用命令设计模式,完成遥控器对电灯操作
// 创建电灯对象
LightReceiver lightReceiver = new LightReceiver();
// 创建电灯相关的开关,命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
// 需要一个遥控器
RemoteController remoteController = new RemoteController();
// 给遥控器设置命令,入no=0是电灯的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
System.out.println("===========按下电灯开按钮============");
remoteController.onButtonWasPushed(0);
System.out.println("===========按下电灯关按钮============");
remoteController.offButtonWasPushed(0);
System.out.println("===========按下撤销按钮============");
remoteController.undoButtonWasPushed();
//
System.out.println("============使用遥控器操作电视机============");
TVRecevice tvRecevice = new TVRecevice();
// 设置电视机的开关按钮
TVOnCommand tvOnCommand = new TVOnCommand(tvRecevice);
TVOffCommand tvOffCommand = new TVOffCommand(tvRecevice);
remoteController.setCommand(1, tvOnCommand, tvOffCommand);
// 按下电视机的开关按钮
System.out.println("===========按下电视机开按钮============");
remoteController.onButtonWasPushed(1);
System.out.println("===========按下电视机关按钮============");
remoteController.offButtonWasPushed(1);
System.out.println("===========按下撤销按钮============");
remoteController.undoButtonWasPushed();
}
}
输出效果
===========按下电灯开按钮============
电灯打开了
===========按下电灯关按钮============
电灯关闭了
===========按下撤销按钮============
电灯打开了
============使用遥控器操作电视机============
===========按下电视机开按钮============
电视机打开了
===========按下电视机关按钮============
电视机关闭了
===========按下撤销按钮============
电视机打开了
4.命令模式总结
命令模式的优点:
将发起请求的对象与执行请求的对象解耦。发起请求的对象时是调用者,调用者只要调用命令对象的execute()方法就可以让接受者工作,不必知道具体的接受者是谁,是如何实现。命令对象会负责让接受者执行请求的动作,就是说,请求发起者和请求执行者之间的解耦是通过命令对象实现的,命令对象起到了纽带的作用
容易设计一个命令队列,只要把命令对象放到队列,就可以多线程的执行命令
容易实现对请求的撤销和重做
命令模式不足:
可能导致某些系统有过多的具体命令类,增加系统的复杂度
空命令也是一种设计模式,省去了判空操作
命令模式的应用场景:
界面的每一个按钮都是一条命令,模拟CMD(DOS命令),订单的撤销,触发-反馈机制