目录
一、模版方法模式(template )
概念
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
比如定义一个操作中的算法的骨架,将步骤延迟到子类中。模板方法使得子类能够不去改变一个算法的结构即可重定义算法的某些特定步骤。
模式中的角色
1)AbstractClass 抽象类实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法ConcreteClass
2)实现抽象方法 , 以完成算法中特点子类的步骤
模板模式UML类图
案例
需求:统计某一代码运行时间
使用前
package com.javaxl.design.template.before;
/**
* @author 周大福ye
* @site www.javaxl.com
* @company
* @create 2020-02-24 15:42
*/
public class CodeTotalTime {
public static void template(){
long start = System.currentTimeMillis();
// 检测Operation_1方法运行的时长======33
Operation_1();
// 检测Operation_2方法运行的时长======616
// Operation_2();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
public static void Operation_1(){
for (int i = 0; i<1000 ;i++){
System.out.println("模拟耗时操作...");
}
System.out.print("检测Operation_1方法运行的时长======");
}
public static void Operation_2(){
for (int i = 0; i<20000 ;i++){
System.out.println("模拟耗时操作...");
}
System.out.print("检测Operation_2方法运行的时长======");
}
}
public class Client {
public static void main(String[] args) {
CodeTotalTime.template();
}
}
使用后
abstract class CodeAbstractClass {
public void template() {
long start = System.currentTimeMillis();
method();
long end = System.currentTimeMillis();
System.out.println("当前方法执行时长:" + (end - start));
}
public abstract void method();
}
class ConcreteClassA extends CodeAbstractClass {
@Override
public void method() {
for (int i = 0; i < 1000; i++) {
System.out.println("模拟耗时操作...");
}
System.out.print("检测ConcreteClassA.method方法运行的时长======");
}
}
class ConcreteClassB extends CodeAbstractClass {
@Override
public void method() {
for (int i = 0; i < 20000; i++) {
System.out.println("模拟耗时操作...");
}
System.out.print("ConcreteClassB.method方法运行的时长======");
}
}
public class Client {
public static void main(String[] args) {
//检测ConcreteClassA.method方法运行的时长======当前方法执行时长:
new ConcreteClassA().template();
//ConcreteClassB.method方法运行的时长======当前方法执行时长:
new ConcreteClassB().template();
}
}
钩子函数应用场景
public abstract class CodeAbstractClass {
public void template() {
long start = System.currentTimeMillis();
if (callback()) method();
long end = System.currentTimeMillis();
System.out.println("当前方法执行时长:" + (end - start));
}
public abstract void method();
public boolean callback() {
return true;
}
}
从上面可以看出:template方法默认是用作统计method方法的执行时长,但是有的时候我们无需统计代码时长,template函数中有一些其它逻辑要执行,在这里我们可以考虑采用钩子函数;钩子函数被子类覆写,覆写成false,那么method方法就不会被调用,不再统计代码时长了;前端框架Vue的生命周期就有多处用到钩子函数;
注意事项和细节
- 钩子函数在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”
- 算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 一般模板方法都加上 final 关键字, 防止子类重写模板方法
应用
Spring IOC容器加载
优点
模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
子类实现算法的某些细节,有助于算法的扩展。
通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
模板方法模式与开闭原则
什么是“开闭原则”?
开闭原则是指一个软件实体应该对扩展开放,对修改关闭。也就是说软件实体必须是在不被修改的情况下被扩展。模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用,同时也遵守了开闭原则。
父类通过顶级逻辑,它通过定义并提供一个具体方法来实现,我们也称之为模板方法。通常这个模板方法才是外部对象最关心的方法。所以它必须是public的,才能被外部对象所调用。同时,因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。
二、命令模式
概念:
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化
术语
-
Command:命令
-
ConcreteCommand:具体的命令
-
Invoker:调用者
-
Receiver:接受者
标准UML类图
角色
-
Command抽象命令 执行命令 撤销命令
-
ConcreteCommandLightOnCommand 开灯 LightOffCommand 关灯
-
NonCommand
-
空命令
-
Invoker调用者 遥控器聚合所有命令 Command[] ons Command[] offs Command undo
-
Receiver接受者 电灯、空调、电视
案例
需求:万能遥控器的制作
使用前
class AirConditioner {
public void on() {
System.out.println("空调打开...");
}
public void off() {
System.out.println("空调关闭...");
}
}
class Television {
public void on() {
System.out.println("电视打开...");
}
public void off() {
System.out.println("电视关闭...");
}
}
public class Invoker {
private Light light = new Light();
private AirConditioner airConditioner = new AirConditioner();
private Television television = new Television();
public void lightOn() {
light.on();
}
public void lightOff() {
light.off();
}
public void airOn() {
airConditioner.on();
}
public void airOff() {
airConditioner.off();
}
public void tvOn() {
television.on();
}
public void tvOff() {
television.off();
}
}
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
invoker.lightOn();
invoker.lightOff();
System.out.println("=============");
invoker.airOn();
invoker.airOff();
System.out.println("=============");
invoker.tvOn();
invoker.tvOff();
}
}
使用后
public class Light {
public void on() {
System.out.println("电灯打开...");
}
public void off() {
System.out.println("电灯关闭...");
}
}
class AirConditioner {
public void on() {
System.out.println("空调打开...");
}
public void off() {
System.out.println("空调关闭...");
}
}
class Television {
public void on() {
System.out.println("电视打开...");
}
public void off() {
System.out.println("电视关闭...");
}
}
interface Command {
void execute();
void undo();
}// 空命令
class NonCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
class LightOnCommand implements Command {
private Light light = new Light();
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
class LightOffCommand implements Command {
private Light light = new Light();
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
class TvOnCommand implements Command {
private Television tv = new Television();
@Override
public void execute() {
tv.on();
}
@Override
public void undo() {
tv.off();
}
}
class TvOffCommand implements Command {
private Television tv = new Television();
@Override
public void execute() {
tv.off();
}
@Override
public void undo() {
tv.on();
}
}
public class Invoker {
Command[] ons;
Command[] offs;// 记录上一个命令
Command command;
public Invoker(int n) {
ons = new Command[n];
offs = new Command[n];
command = new NonCommand();
for (int i = 0; i < n; i++) {
setCommand(i, new NonCommand(), new NonCommand());
}
}
public void setCommand(int no, Command on, Command off) {
ons[no] = on;
offs[no] = off;
}
public Command getOnCommand(int no) {
return ons[no];
}
public Command getOffCommand(int no) {
return offs[no];
}
// 执行命令
public void invoke(Command command) {
// 执行当前命令
command.execute();// 保存当前执行命令
this.command = command;
}// 撤销命令(上个操作的反操作)
public void undo() {// 这里就能体现定义一个空命令的好处了,如果第一次按撤销命令,那么应该什么都不做;// 如果没有定义空命令的话,此时就需要判断空处理了
command.undo();
}
}
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker(2);
invoker.setCommand(0, new LightOnCommand(), new LightOffCommand());
invoker.setCommand(1, new TvOnCommand(), new TvOffCommand());
System.out.println("电灯打开关闭操作===========");
invoker.invoke(invoker.getOnCommand(0));
invoker.invoke(invoker.getOffCommand(0));
// invoker.undo();
System.out.println("电视打开关闭操作===========");
invoker.invoke(invoker.getOnCommand(1));
invoker.undo();
}
}
注意事项和细节:
将发起请求的对象与执行请求的对象解耦 容易实现对请求的撤销和重做 空命令也是一种设计模式,它为我们省去了判空的操作
命令模式不足: 可能导致某些系统有过多的具体命令类,增加了系统的复杂度
与外观模式相似:都是将多个功能聚合在一起 外观模式更多适用于维护;命令模式更多应用于设计
总结
命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。
主要优点
(1) 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
(2) 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
(3) 可以比较容易地设计一个命令队列或宏命令(组合命令)。
(4) 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
主要缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
适用场景
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
系统需要将一组操作组合在一起形成宏命令。
应用:Spring框架中的JdbcTemplate类 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令 界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发- 反馈机制