设计模式-命令模式

命令模式

1.命令模式的定义

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

1.1 使用场景

作为一个开发人员,掌握Git是一个必须的事情。一般来说,我们都是通过敲命令去实现我们的目的。
比如,我们敲如下一行代码:

git commit -m "init"

看这个没什么感觉,我们还是用常规代码去实现这一过程吧。

class LocalReceiver{

    fun init() {
        println("init:将这个目录变成git可以管理的仓库")
    }

    fun add(){
        println("add:将文件添加到仓库")
    }

    fun commit(){
        println("commit:将文件提交到仓库")
    }

    fun reset() {
        println("reset:版本回退操作")
    }

}
class ClientActivity:Activity() {

    fun initView() 
        btCommit.setOnClickListener {
            val receiver = LocalReceiver()
            receiver.commit()
        }
    }
}
  • 说实话,如果真的就像Button中写的那么简单就能实现命令,那真的没什么必要去使用什么命令模式!
  • 我们使用Git的时候,需要满足一定条件下才可以实现我们的命令。
  • 你可能会说,将条件封装到receiver当中不就好了吗?但是有时候receiver做的操作是比较细的,是高内聚的,纯粹一点的实现,我们就不要参杂那么多东西进去了

实际上,代码是这样:

class ClientActivity:Activity(){
    /**
     * -1 未初始化状态
     * 0 初始化状态
     * 1 添加状态
     * 2 干净状态
     */
    val status = -1
    
    fun initView() {
        btCommit.setOnClickListener {
            if (status == 1) {
                val receiver = LocalReceiver()
                print("提交前的操作")
                receiver.commit()
                print("提交后的操作")
            } else {
                print("请先进行添加操作")
            }
        }
    }
}
  • 有没有发现,我们将 逻辑(状态和操作),命令两者都放在了客户端去实现。

甚至有时候,我们将几种命令组合成一种命令,称为组合命令

class ClientActivity:Activity() {

    fun initView() 
        btIAC.setOnClickListener {
            val receiver = LocalReceiver()
            receiver.init()
            receiver.add()
            receiver.commit()
        }
    }
}
  1. 组合逻辑留在了客户端。这是不合理的,因为维护的时候,可能会存在不小心删除部分代码等因素。
  2. 迪米特原则。客户端应该只知道我可以调用一个组合命令,而不应该知道这些细节。
  3. 复用性低。有可能在其他地方也可能使用到该组合命令,移植性差
  4. 参杂逻辑时,会使得这部分代码变得更为复杂和难以维护

这时候命令模式就发挥了它的作用

1.2 角色介绍

  • Receiver:命令的执行者,是真正实现命令操作的。
  • Command:定义了具体命令类的抽象接口。
  • ConcreteCommand:具体命令类,实现了Command抽象接口。
  • Invoker:调用者 控制和负责具体命令类的调用
  • Client:客户端

Client,你可以理解为一种使用场景,拿我们上面的代码来说,就相当于一个按钮的回调方法。

Invoker,在客户端使用Command参数化Invoker,拿我们上面的代码来说,调用invoker.setCommand(command),然后去执行对应的方法。它作为客户端对命令的调用者,控制command的调用,内部处理了一些逻辑操作。

Command:规范具体命令类需要实现的方法,比如统一实现execute()方法。

ConcreteCommand:一般会实现setReceiver()方法,实现execute()方法,该方法中可能是一个命令或是多个命令的组合,也可能会包括一些其他操作,我们也可以扩展一些其他方法,比如撤销命令方法

1.3 命令模式的实现

让我们来看看比较完整的实现方案吧

/**
 * 本地Git的实现操作
 */
class LocalReceiver{

    fun init() {
        println("init:将这个目录变成git可以管理的仓库")
    }

    fun add(){
        println("add:将文件添加到仓库")
    }

    fun commit(){
        println("commit:将文件提交到仓库")
    }

    fun reset() {
        println("reset:版本回退操作")
    }

}

/**
 * 提交接口
 */
interface PushReceiver{
    fun push()
}

/**
 * 提交到Github
 */
class PushToGitHubReceiver:PushReceiver{
    override fun push(){
        println("PushToGitHubReceiver:从配置文件中拿到对应的GitHub仓库地址")
        println("PushToGitHubReceiver:绑定GitHub远程仓库")
        println("PushToGitHubReceiver:提交到GitHub远程仓库")
    }
}

/**
 * 提交到码云
 */
class PushToMaYunReceiver:PushReceiver{
    override fun push(){
        println("PushToMaYunReceiver:从配置文件中拿到对应的码云仓库地址")
        println("PushToMaYunReceiver:绑定码云远程仓库")
        println("PushToMaYunReceiver:提交到码云远程仓库")
    }
}

有没有发现,我这里的LocalReceiver和PushReceiver并没有抽象出一个统一的接口出来,这是为什么?

LocalReceiver类实现了四个方法·,那我可不可以分成四个类,然后跟PushReceiver类统一使用doSomething方法呢?

  • 没有必要,在功能不是那么复杂的情况下,分的如此之细,这属于优化过度,不是好的设计。而且每个功能都有自己的含义,我们一般通过方法名去查看,如果以doSomething为方法名,简直就是灾难。
  • 抽象一个统一的接口,需要它们有共同的特性。抽象出了PushReceiver接口,是因为它们都实现push的功能。

Command:


interface Command{
    fun execute()
}
/**
 * 初始化命令
 */
class InitCommnad : Command{

    var receiver:LocalReceiver?=null

    override fun execute() {
        receiver?.init()
    }
}

/**
 * 添加命令
 */
class AddCommand:Command{

    var receiver:LocalReceiver?=null

    override fun execute() {
        receiver?.add()
    }
}

/**
 * 提交命令
 */
class CommitCommand:Command{

    var receiver:LocalReceiver?=null

    override fun execute() {
        receiver?.commit()
    }
}

/**
 * 回退版本命令
 */
class ResetCommand :Command {

    var receiver: LocalReceiver? = null

    override fun execute() {
        receiver?.reset()
    }
}

/**
 * push到远程仓库
 */
class PushCommand:Command{

    var receiver:PushReceiver?=null

    override fun execute() {
        receiver?.push()
    }
}

/**
 * 提交到配置文件中所有的远程仓库
 */
class PushToAllCommand:Command{

    var receiverList=ArrayList<PushReceiver>();

    fun initReceiverList() {
        if (receiverList.size == 0) {
            receiverList.add(PushToGitHubReceiver())
            receiverList.add(PushToMaYunReceiver())
        }
    }

    override fun execute() {
        initReceiverList()
        receiverList.forEach {
            it.push()
        }
    }
}

Invoker:Git是一个饿汉式单例。

object Git {
    /**
     * 本来想着使用状态模式的,但为了方便,就简单使用int判断
     * 使用该状态,仅仅是为了体现调用者的控制作用
     * -1 未初始化状态
     * 0 初始化状态
     * 1 添加状态
     * 2 干净状态(未同步状态)
     * 3 同步状态 由于存在多个仓库,避免复杂,不讲究这个
     */
    var status = -1
    var mResetVersion: Int? = null
    var mCommitLog: String? = null
    var command: Command? = null

    fun init() {
        if (status < 0) {
            command?.execute()
            status = 0
        } else {
            println("该目录已经是git可以管理的仓库")
        }
    }

    fun add() {
        if (status >= 0) {
            command?.execute()
            status = 1
        } else {
            println("该目录还不是git可以管理的仓库,需要init操作")
        }
    }

    fun commit(commitLog: String) {
        if (status == 1) {
            command?.run {
                //这里仅仅为了告诉你一种当receiver对象方法参数的获取方法
                //具体的使用看你自己
                mCommitLog = commitLog
                execute()
                status = 2
            }
        } else {
            println("请添加已修改部分")
        }
    }

    fun push() {
        if (status == 2) {
            command?.execute()
        } else {
            println("该仓库存在问题")
        }
    }

    fun pushToAll() {
        if (status == 2) {
            command?.execute()
        } else {
            println("该仓库存在问题")
        }
    }

    fun reset(version: Int) {
        if (status > 0) {
            command?.run {
                mResetVersion = version
                execute()
                status = 2
            }
        } else {
            println("该目录还不是git可以管理的仓库,需要init操作")
        }
    }

}

Client


class ClientActivity : Activity() {
    
       fun initView() {

        var localReceiver = LocalReceiver()

        btInit.setOnClickListener {
            val command = InitCommnad()
            command.receiver = localReceiver
            Git.command = command
            Git.init()
        }
        btAdd.setOnClickListener {
            val command = AddCommand()
            command.receiver = localReceiver
            Git.command = command
            Git.add()
        }
        btCommit.setOnClickListener {
            val command = CommitCommand()
            command.receiver = localReceiver
            Git.command = command
            Git.commit("first commit")
        }
        btReset.setOnClickListener {
            val command = ResetCommand()
            command.receiver = localReceiver
            Git.command = command
            Git.reset(1)
        }
        btPushToGitHub.setOnClickListener {
            val command = PushCommand()
            command.receiver = PushToGitHubReceiver()
            Git.command = command
            Git.push()
        }
        btPushToMaYun.setOnClickListener {
            val command = PushCommand()
            command.receiver = PushToMaYunReceiver()
            Git.command = command
            Git.push()
        }
        btPushToAll.setOnClickListener {
            Git.command = PushToAllCommand()
            Git.pushToAll()
        }

    }

}
  • 客户端,关于命令的所有逻辑操作都消失了,看起来就跟如下代码一样
class ClientActivity:Activity() {

   fun initView() 
       btIAC.setOnClickListener {
           val receiver = LocalReceiver()
           receiver.commit()
       }
   }
}

你是否注意到receiver出现在了客户端上?

  • 客户端其实是不应该出现receiver的。

你可以选择封装到invoker中,没有问题,也应该这样做。

  • 当存在很多command, 你不要为每个command中创建一个receiver,因为可能会堆积很多receiver的情况,能共用同一个的就尽量共用。

1.4 适用场景

  1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。

  2. 命令需要进行各种管理逻辑。

  3. 需要支持撤消\重做操作

总结

判断标准

  • 命令模式的核心:命令。抓住这个关键字,它是否是一个命令?
  • 客户端去做Receiver类的方法外的一些逻辑处理(控制管理,组合命令)等
    这时候我们可以考虑使用命令模式。否则,客户端只是需要单纯的去调用receiver类,就没有这个必要去使用命令模式。

优点

  • 一定程度上,提高了代码的复用性。例如封装好的组合命令,可能在其他界面也需要该命令功能。
  • 扩展性好。 Command为抽象接口,非常容易扩展具体的命令类。
  • 将调用操作的请求对象与接收对象解耦。

缺点

  • 命令过多,将会产生大量的Command具体实现类
  • Command的抽象方法execute() 不够灵活。 也许我需要传参过去,但是受于限制,只能通过其他途径去完成传参这一行为。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值