命令模式与访问者模式
参考教程:https://www.bilibili.com/video/BV1G4411c7N4
代码实现 Github:https://github.com/yaokuku123/pattern
命令模式
- 案例
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。 2) 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。 3) 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时就可以考虑使用命令模式。 4) 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来. 5) 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品。
- 命令模式
解释:在软件设计中,我们经常需要 向某些对象发送请求,但是并不知道请求的接收者是谁,也不知 道被请求的操作是哪个。我们只需在程序运行时指定具体的请求接收者即可,此时,可以 使用命令模式来进行设计。
通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色: 将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
- 代码实现
对原理类图的说明-即(命名模式的角色及职责)
-
Invoker 是调用者角色
-
Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
-
Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
-
ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
package com.yqj.pattern.command;
//具体执行者
class LightReceiver{
public void on(){
System.out.println("开灯");
}
public void off(){
System.out.println("关灯");
}
}
//命令接口
interface Command{
//执行命令
public void execute();
//撤销操作
public void undo();
}
//具体命令,开灯
class LightOnCommand implements Command{
//聚合具体的执行者
private LightReceiver lightReceiver;
//传入执行者
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
//调用执行者的方法
lightReceiver.on();
}
@Override
public void undo() {
//调用执行者的方法
lightReceiver.off();
}
}
//具体命令,关灯
class LightOffCommand implements Command{
private LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.off();
}
@Override
public void undo() {
lightReceiver.on();
}
}
//具体命令,空实现,方便初始化按钮。调用的时候省掉对空的判断
class NoCommand implements Command{
@Override
public void execute() {
}
@Override
public void undo() {
}
}
class RemoteController{
//按钮命令数值
private Command[] onCommands;
private Command[] offCommands;
//撤销命令,记录上次的操作
private Command undoCommand;
public RemoteController() {
//初始化按钮
onCommands = new Command[5];
offCommands = new Command[5];
undoCommand = new NoCommand();
for (int i=0 ; i<5 ; i++){
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
//给按钮设置具体的命令
public void setCommand(int index,Command onCommand,Command offCommand){
onCommands[index] = onCommand;
offCommands[index] = offCommand;
}
//按下开按钮
public void onButtonWasPushed(int index){
//找到按下的按钮,并调用该按钮的方法
onCommands[index].execute();
//记录操作,用于撤销
undoCommand = onCommands[index];
}
//按下关按钮
public void offButtonWasPushed(int index){
offCommands[index].execute();
undoCommand = offCommands[index];
}
//按撤销按钮
public void undoButtonWasPushed(){
undoCommand.undo();
}
}
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();
//设置按钮对应的命令
remoteController.setCommand(0,lightOnCommand,lightOffCommand);
//按下按钮
remoteController.onButtonWasPushed(0);
remoteController.offButtonWasPushed(0);
remoteController.undoButtonWasPushed();
}
}
- 小结
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制
访问者模式
- 案例
将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如 成功、失败 等)。
- 传统方案
- 分析
- 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则, 不利于维护 2) 扩展性不好,比如:增加了新的人员类型或者评价种类都不好做 3) 引出我们会使用新的设计模式 – 访问者模式
- 访问者模式
解释:封装一些作用于某种数据结构的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。
原理:在被访问的类里面加一个对外提供接待访问者的接口。
场景:需要对一个对象结构中的对象进行很多不同操作 (这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。
- 代码实现
对原理类图的说明即(访问者模式的角色及职责)
-
Visitor 是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
-
ConcreteVisitor :是一个具体的访问者实现每个有Visitor声明的操作,是每个操作实现的部分.
-
ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
-
Element 定义一个accept 方法,接收一个访问者对象
-
ConcreteElement 为具体元素,实现了accept 方法
package com.yqj.pattern.visitor;
import java.util.ArrayList;
import java.util.List;
abstract class People{
private String name;
public People(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract void accept(Action action);
}
class Man extends People{
public Man(String name) {
super(name);
}
//双分派,首先将具体状态action作为参数传递到man中(第一次分派)
//然后man类调用作为参数的action的具体方法getManResult(),同时将自己this作为参数传入(第二次分派)
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
class Woman extends People{
public Woman(String name) {
super(name);
}
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
abstract class Action{
//得到男的评价
public abstract void getManResult(Man man);
//得到女的评价
public abstract void getWomanResult(Woman woman);
}
class Success extends Action{
@Override
public void getManResult(Man man) {
System.out.println("男的赞同 "+ man.getName());
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女的赞同 " + woman.getName());
}
}
class Fail extends Action{
@Override
public void getManResult(Man man) {
System.out.println("男的反对" + man.getName());
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女的反对" + woman.getName());
}
}
//数据结构,维护和管理很多人
class ObjectStructure{
//维护集合
private List<People> elements = new ArrayList<>();
//添加一个人
public void add(People p){
elements.add(p);
}
//删除一个人
public void remove(People p){
elements.remove(p);
}
//显示测评情况
public void display(Action action){
for (People element : elements) {
element.accept(action);
}
}
}
public class Client {
public static void main(String[] args) {
//创建两个评价标准
Action success = new Success();
Action fail = new Fail();
//创建访问者对象
Man bob = new Man("bob");
Man tom = new Man("tom");
Woman alice = new Woman("alice");
//高层遍历访问者的对象
ObjectStructure objectStructure = new ObjectStructure();
//加入集合
objectStructure.add(bob);
objectStructure.add(tom);
objectStructure.add(alice);
//评价
objectStructure.display(success);
objectStructure.display(fail);
}
}
双分派:所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。 双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码。
- 小结
优点 :
-
访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
-
访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
缺点 :
-
具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
-
违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素 3) 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。
网友评论