设计模式 -- 命令模式

场景

在饭店里吃饭经常会出现上餐错误的问题,比如上菜顺序不对或上菜上错桌的情况

问题来了
这种情况在编程中就是常说的紧耦合,客人与厨师之间存在直接关系,当客人要修改菜单时便需要修改厨师的内容,这遍违背了“开闭原则”

问题改进
降低客人和厨师之间的耦合度,客人是点餐的请求者,厨师是烧菜的执行者,在客人和厨师之间需要一个中介服务员,客人不需要认识厨师是谁,饭菜怎么做,客人只需要将点的菜单给服务员就好,他负责去通知厨师,根据不同的订单上不同的菜

表述 (行为型模式)

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令和执行命令分割开,命令模式允许请求方和接收方独立开来,使得请求方不必知道接收方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的

命令模式类图

  • Command(抽象命令类):一般是一个抽象类或接口,在其中声明了execute()方法用于执行请求

  • ConcreteCommand(具体命令类):抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,在实现execute()方法时,将调用接收者对象的相关操作(Action)

  • Invoker(调用者):请求发送者,它通过命令对象来执行请求。调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象传入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作

  • Receiver(接收者):具体实现对请求的业务处理

优点

  • 降低耦合度
  • 比较容易设计一个命令队列
  • 需要的情况下,可以较为容易的将命令记入日志
  • 可以容易地实现对请求的撤销和重做
  • 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易

缺点

使用命令模式可能会导致某些系统有过多的具体命令类

使用场景

  • 想让程序支持撤销与恢复
  • 想用对象参数化一个动作已执行操作,并用不同命令对象来代替回调函数
  • 想要在不同时刻对请求进行指定、排列和执行
  • 想记录修改日志,这样系统故障时,这些修改可在后来重做一遍

示例

  • 需求1:命令模式
  • 需求2:命令模式--迭代版
  • 需求3:命令模式--命令队列
  • 需求4:命令模式--撤销操作
  • 需求5:命令模式--请求日志
  • 需求6:命令模式--宏命令

需求V1:客人在饭店点了热菜

抽象命令类

class Command {
    func execute() {
        
    }
}

具体命令类

class HotFoodCommand : Command{
    var hotCook : HotCook
    
    override init() {
        self.hotCook = HotCook.init()
    }
    
    override func execute() {
        self.hotCook.makeHotFood()
    }
}

发布者

class Waiter {
    var command:Command
    init(command:Command) {
        self.command = command
    }
    func notify() {
        self.command.execute()
    }
}

接收者

class HotCook {
    func makeHotFood() {
        print("热菜")
    }
}

客户端

//具体命令
let hotFoodCom = HotFoodCommand.init()
//发布者
let waiter = Waiter.init(command: hotFoodCom)
waiter.notify()

//log:
//热菜

需求V2:客人又加了凉菜
增加一个新的具体命令类ColdFoodCommand和对应的接收者类ColdCook即可

class ColdFoodCommand : Command {
    var coldCook : ColdCook
    
    override init() {
        self.coldCook = ColdCook.init()
    }
    
    override func execute() {
        self.coldCook.makeColdFood()
    }
}

class ColdCook {
    func makeColdFood() {
        print("凉菜")
    }
}

客户端

let coldFoodCom = ColdFoodCommand.init()
let waiter = Waiter.init(command: coldFoodCom)
waiter.notify()
//log
//凉菜

需求V3:客人在饭店点了热菜和凉菜(命令队列的实现)

有时需要将多个请求排队,当一个发送者发送请求后,将有一系列接收者对请求作出响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。此时,我们可以通过命令队列来实现,如果请求接收者的接收次序没有严格的先后次序,我们还可以使用多线程技术来并发调用命令对象的execute()方法,从而提高程序的执行效率。

命令队列的实现方法有多种形式,其中最常用、灵活性最好的一种方式是增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者

增加一个CommandQueue类

class CommandQueue {
    var commands = [Command]()
    
    func addCommand(command:Command) {
        commands.append(command)
    }
    func remove(command:Command) {
        for i in 0..<commands.count {
            if commands[i] === command {
                commands.remove(at:i )
            }
        }
    }
    func execute() {
        for command in commands {
            command.execute()
        }
    }
}

在增加了命令队列类CommandQueue以后,请求发送者类Invoker将针对CommandQueue编程

class Waiter {
    var commandQueue:CommandQueue
    init(commandQueue:CommandQueue) {
        self.commandQueue = commandQueue
    }
    func notify() {
        self.commandQueue.execute()
    }
}

客户端

let hotFoodCom = HotFoodCommand.init()
let coldFoodCom = ColdFoodCommand.init()

let queue = CommandQueue.init()
queue.addCommand(command: hotFoodCom)
queue.addCommand(command: coldFoodCom)

let waiter = Waiter.init(commandQueue: queue)
waiter.notify()

//log:
//热菜
//凉菜

需求V4:一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作(撤销操作的实现)

抽象命令类

class Command {
    func execute(value:Int) -> Int {
        return 0
    }
    func undo() -> Int {
        return 0
    }
}

具体命令类

class AddCommand : Command {
    
    var adder = Adder.init()
    var value = 0
    
    var commands = [Int]()
    
    override func execute(value: Int) -> Int {
        self.value = value
        commands.append(value)
        return adder.add(value: value)
    }
    
    override func undo() -> Int {
        
        var value = 0
        for _ in 0..<commands.count {
            value = commands[commands.count - 1]
            commands.remove(at: commands.count-1)
            return adder.add(value: -value)
        }
        return 0
    }
}

接收者

class Adder {
    var num = 0
    func add(value:Int) -> Int {
        num += value
        return num
    }
}

发布者

class CalculatorForm {
    var command : Command
    
    init(command:Command) {
        self.command = command
    }
    
    func compute(value:Int) {
        let i = self.command.execute(value: value)
        print("执行运算,结果为:\(i)")
    }
    
    func undo() {
        let i = self.command.undo()
        print("执行撤销,结果为:\(i)")
    }
}

客户端

let commandd = AddCommand.init()
let form = CalculatorForm.init(command: commandd)
form.compute(value: 1)
form.compute(value: 1)
form.compute(value: 1)
form.compute(value: 1)
form.compute(value: 1)
print("----")
form.undo()
form.undo()

//执行运算,结果为:1
//执行运算,结果为:2
//执行运算,结果为:3
//执行运算,结果为:4
//执行运算,结果为:5
//    ----
//执行撤销,结果为:4
//执行撤销,结果为:3

需求5:数据库支持增删改查的功能,在此基础上添加日志(请求日志)

抽象命令类

class Command {

    func execute(args:String) {

    }
    
    func execute() {
        
    }
}

具体命令类

class InsertCommand : Command {
    var oper : Operator
    var name : String
    var args : String?
    
    init(name:String) {
        self.name = name
        self.oper = Operator.init()
    }

    override func execute(args: String) {
        self.args = args
        self.oper.insert(args: args)
    }
    override func execute() {
        self.oper.insert(args: self.args!)
    }
}

class UpdataCommand : Command {
    var oper : Operator
    var name : String
    var args : String?
    
    init(name:String) {
        self.name = name
        self.oper = Operator.init()
    }
    
    override func execute(args: String) {
        self.args = args
        self.oper.updata(args: args)
    }
    override func execute() {
        self.oper.updata(args: self.args!)
    }
}

class DeleteCommand : Command {
    var oper : Operator
    var name : String
    var args : String?
    
    init(name:String) {
        self.name = name
        self.oper = Operator.init()
    }
    
    override func execute(args: String) {
        self.args = args
        self.oper.delete(args: args)
    }
    override func execute() {
        self.oper.delete(args: self.args!)
    }
}

接收者

class Operator {
    func insert(args:String) {
        print("新增数据 : \(args)")
    }
    func updata(args:String) {
        print("修改数据 : \(args)")
    }
    func delete(args:String) {
        print("删除数据 : \(args)")
    }
}

发布者

class ExecuteTool {
    var commands = [Command]()
    var command : Command?
    
    func setCommand(command:Command) {
        self.command = command
    }
    
    func call(args:String) {
        self.command!.execute(args: args)
        self.commands.append(self.command!)
    }
    func save() {
        print("保存数据")
    }
    func recover() {
        print("恢复数据")
        for obj in commands {
            obj.execute()
        }
    }
}

客户端

var execute = ExecuteTool.init()

let insertComA = InsertCommand.init(name: "增加")
execute.setCommand(command: insertComA)
execute.call(args: "insert A")
let insertComB = InsertCommand.init(name: "增加")
execute.setCommand(command: insertComB)
execute.call(args: "insert B")
let insertComC = InsertCommand.init(name: "增加")
execute.setCommand(command: insertComC)
execute.call(args: "insert C")
print("---")

let updataB = UpdataCommand.init(name: "更新")
execute.setCommand(command: updataB)
execute.call(args: "updata B")
print("---")

let deleteC = DeleteCommand.init(name: "删除")
execute.setCommand(command: deleteC)
execute.call(args: "delete C")
print("---")

execute.save()
print("---")
execute.recover()

//新增数据 : insert A
//新增数据 : insert B
//新增数据 : insert C
//---
//修改数据 : updata B
//---
//删除数据 : delete C
//---
//保存数据
//---
//恢复数据
//新增数据 : insert A
//新增数据 : insert B
//新增数据 : insert C
//修改数据 : updata B
//删除数据 : delete C

需求6:一个APP上线简要概括为,需求、研发、上线(宏命令)

宏命令又称为组合命令,是组合模式和命令模式联用产物

宏命令是一个具体命令类,它拥有一个集合属性,该集合中包含了对其他命令的引用,通常宏命令不直接与请求接受者交互,而是通过它的成员来调用接受者的方法,当调用宏命令的execute()方法时,将递归调用他所包含的每个成员命令的execute()方法

抽象命令类

class Command {
    func execute() {
        
    }
}

宏命令类

class MacroCommand : Command {
    func addCommand(command:Command)  {
        
    }
    func removeCommand(command:Command) {
        
    }
}

class APPMacroCommand : MacroCommand {
    var commands = [Command]()
    
    override func addCommand(command: Command) {
        commands.append(command)
    }
    override func removeCommand(command: Command) {
        
        for i in 0..<commands.count {
            if commands[i] === command {
                commands.remove(at: i)
                break
            }
        }
    }
    
    override func execute() {
        for com in commands {
            com.execute()
        }
    }
}

具体命令类

class DemandCommand : Command {
    
    var develop : Developer
    
    override init() {
        self.develop = Developer.init()
    }

    override func execute() {
        self.develop.demand()
    }
}
class DevelopmentCommand : Command {
    
    var develop : Developer
    
    override init() {
        self.develop = Developer.init()
    }
    
    override func execute() {
        self.develop.development()
    }
}
class UploadCommand : Command {
    
    var develop : Developer
    
    override init() {
        self.develop = Developer.init()
    }
    
    override func execute() {
        self.develop.upload()
    }
}

接收者

class Developer {
    func demand() {
        print("需求")
    }
    func development() {
        print("研发")
    }
    func upload() {
        print("上传")
    }
}

客户端

let app = APPMacroCommand.init()

let demandCommand = DemandCommand.init()
app.addCommand(command: demandCommand)
let developmentCommand = DevelopmentCommand.init()
app.addCommand(command: developmentCommand)
let uploadCommand = UploadCommand.init()
app.addCommand(command: uploadCommand)

app.execute()

//需求
//研发
//上传
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容