设计模式-命令模式在Java中的使用示例-桌面程序自定义功能键

news2024/9/23 11:19:59

场景

欲开发一个桌面版应用程序,该应用程序为用户提供了一系列自定义功能键,用户可以通过这些功能键来实现一些快捷操作。

用户可以将功能键和相应功能绑定在一起,还可以根据需要来修改功能键的设置,而且系统在未来可能还会增加一些新的功能或功能键。

如果不使用命令模式,可能这样实现。

功能键类FunctionButton充当请求的发送者,帮助文档处理类HelpHandler充当请求的接收者,在发送者FunctionButton的onClick()

方法中将调用接收者HelpHandler的display()方法。

FunctionButton:

public class FunctionButton {
    //帮助文档处理类,请求接收者
    private HelpHandler helperHandler;

    public void onClick(){
        helperHandler = new HelpHandler();
        //显示帮助文档
        helperHandler.display();
    }
}

HelpHandler:

public class HelpHandler {
    public void display(){
        System.out.println("显示帮助文档");
    }
}

以上存在的问题:

(1) 由于请求发送者和请求接收者之间存在方法的直接调用,耦合度很高,更换请求接收者必须修改发送者的源代码,

如果需要将请求接收者HelpHandler改为WindowHanlder(窗口处理类),则需要修改FunctionButton的源代码,违背了“开闭原则”。

(2) FunctionButton类在设计和实现时功能已被固定,如果增加一个新的请求接收者,如果不修改原有的FunctionButton类,

则必须增加一个新的与FunctionButton功能类似的类,这将导致系统中类的个数急剧增加。

由于请求接收者HelpHandler、WindowHanlder等类之间可能不存在任何关系,它们没有共同的抽象层,

因此也很难依据“依赖倒转原则”来设计FunctionButton。

(3) 用户无法按照自己的需要来设置某个功能键的功能,一个功能键类的功能一旦固定,在不修改源代码的情况下无法更换其功能,

系统缺乏灵活性。

命令模式概述

在软件开发中,我们经常需要向某些对象发送请求(调用其中的某个或某些方法),但是并不知道请求的接收者是谁,

也不知道被请求的操作是哪个,此时,我们特别希望能够以一种松耦合的方式来设计软件,使得请求发送者与请求接收者

能够消除彼此之间的耦合,让对象之间的调用关系更加灵活,可以灵活地指定请求接收者以及被请求的操作。

命令模式为此类问题提供了一个较为完美的解决方案。

命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,

而不必知道如何完成请求。

命令模式(Command Pattern):

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

命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

命令模式结构图

命令模式包含的各角色:

Command(抽象命令类):

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

通过这些方法可以调用请求接收者的相关操作。

ConcreteCommand(具体命令类):

具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,

将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。

Invoker(调用者):

调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,

因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,

再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。

Receiver(接收者):

接收者执行与请求相关的操作,它具体实现对请求的业务处理。命令模式的本质是对请求进行封装,

一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:

请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。

命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,

更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。

注:

博客:
霸道流氓气质_C#,架构之路,SpringBoot-CSDN博客

实现

使用命令模式实现以上用户自定义功能键。

1、新建抽象命令类

//抽象命令类
abstract class Command {
    public abstract void execute();
}

2、新建具体命令类:帮助命令类

//帮助命令类:具体命令类
public class HelpCommand extends Command{

    //维持对请求接收者的引用
    private HelpHandler helpHandler;

    public HelpCommand(){
        helpHandler = new HelpHandler();
    }

    //命令执行方法,将调用请求接收者的业务方法
    public void execute() {
        helpHandler.display();
    }
}

其中维持着对请求接收者的引用。

3、新建请求接收者,帮助文档的处理类

//帮助文档处理类:请求接受者
public class HelpHandler {
    public void display(){
        System.out.println("显示帮助文档");
    }
}

4、同理新建具体命令类:最小化命令类

//最小化命令类:具体命令类
public class MinimizeCommand extends Command{

    //维持对请求接收者的引用
    private WindowHandler windowHandler;

    public MinimizeCommand(){
        windowHandler = new WindowHandler();
    }

    //命令执行方法,将调用请求接收者的业务方法
    public void execute() {
        windowHandler.minimize();
    }
}

其中维持着对请求接收者最小化窗口处理类的引用

5、新建最小化窗口处理类

//窗口处理类:请求接收者
public class WindowHandler {
    public void minimize(){
        System.out.println("将窗口最小化");
    }
}

6、新建请求发送者:功能键类

//功能键类:请求发送者
public class FunctionButton {
    //功能键名称
    private String name;
    //维持一个抽象命令对象的引用
    private Command command;

    public FunctionButton(String name) {
        this.name = name;
    }

    public String getName(){
        return this.name;
    }

    //为功能键注入命令
    public void setCommand(Command command){
        this.command = command;
    }

    public void onClick(){
        System.out.println("点击功能键:");
        command.execute();
    }
}

7、新建功能键设置窗口类

import java.util.ArrayList;

//功能键设置窗口类
public class FBSettingWindow {
    //窗口标题
    private String title;
    //定义一个ArrayList来存储所有功能键
    private ArrayList<FunctionButton> functionButtons = new ArrayList<FunctionButton>();

    public FBSettingWindow(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void addFunctionButton(FunctionButton fb){
        functionButtons.add(fb);
    }

    public void removeFunctionButton(FunctionButton fb){
        functionButtons.remove(fb);
    }

    //显示窗口及功能键
    public void display(){
        System.out.println("显示窗口:"+this.title);
        System.out.println("显示功能键:");
        for (Object obj : functionButtons) {
            System.out.println(((FunctionButton)obj).getName());
        }
        System.out.println("-------------------------");
    }
}

8、客户端调用方式

public class Client {
    public static void main(String[] args) {
        FBSettingWindow fbsw = new FBSettingWindow("功能键设置");

        FunctionButton fb1,fb2;
        fb1 = new FunctionButton("功能键1");
        fb2 = new FunctionButton("功能键2");

        Command command1,command2;
        //通过读取配置文件或其它方式生成具体命令对象
        command1 = new HelpCommand();
        command2 = new MinimizeCommand();

        //将命令对象注入功能键
        fb1.setCommand(command1);
        fb2.setCommand(command2);

        fbsw.addFunctionButton(fb1);
        fbsw.addFunctionButton(fb2);
        fbsw.display();

        //调用功能键的业务方法
        fb1.onClick();
        fb2.onClick();

    }
}

9、总结

如果需要修改功能键的功能,例如某个功能键可以实现“自动截屏”,只需要对应增加一个新的具体命令类,

在该命令类与屏幕处理者(ScreenHandler)之间创建一个关联关系,然后将该具体命令类的对象通过配置文件注入到某个功能键即可,

原有代码无须修改,符合“开闭原则”。在此过程中,每一个具体命令类对应一个请求的处理者(接收者),

通过向请求发送者注入不同的具体命令对象可以使得相同的发送者对应不同的接收者,从而实现“将一个请求封装为一个对象,

用不同的请求对客户进行参数化”,客户端只需要将具体命令对象作为参数注入请求发送者,无须直接操作请求的接收者。

命令模式的主要优点:

(1) 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,

相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。

(2) 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,

无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。

(3) 可以比较容易地设计一个命令队列或宏命令(组合命令)。

(4) 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。

命令模式的主要缺点如下:

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,

因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。

适用场景

在以下情况下可以考虑使用命令模式:

(1) 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,

接收者也无须关心何时被调用。

(2) 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,

换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,

而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。

(3) 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

(4) 系统需要将一组操作组合在一起形成宏命令。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/801553.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

做外贸工厂跳过我联系了客户

在我十多年外贸创业历程里&#xff0c;遇过不少想跳过我直接和客户联系的供应商。 譬如给客户发邮件&#xff0c;悄咪咪将我从抄送里删掉。又或者偷偷将名片塞在货柜里&#xff0c;期望客户一开箱就能看到。 然而这么多年过去&#xff0c;一次让他们得逞的机会都没有。每一次…

C++多线程编程(第三章 案例1,使用互斥锁+ list模拟线程通信)

主线程和子线程进行list通信&#xff0c;要用到互斥锁&#xff0c;避免同时操作 1、封装线程基类XThread控制线程启动和停止&#xff1b; 2、模拟消息服务器线程&#xff0c;接收字符串消息&#xff0c;并模拟处理&#xff1b; 3、通过Unique_lock和mutex互斥方位list 消息队列…

【力扣每日一题】2023.7.28 并行课程3

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们课程表以及每门课需要学习的时间&#xff0c;我们可以同时学任意数量的课程&#xff0c;但是学习的条件是先修课程都已经学完了…

【雕爷学编程】MicroPython动手做(02)——尝试搭建K210开发板的IDE环境6

#尝试搭建K210的Micropython开发环境&#xff08;Win10&#xff09; #实验程序之六&#xff1a;测试Microphone阵列算法 #尝试搭建K210的Micropython开发环境&#xff08;Win10&#xff09; #实验程序之六&#xff1a;测试Microphone阵列算法from Maix import MIC_ARRAY as mi…

MySQL运维:从全备sql文件中提取指定表的数据并恢复

目录 一、运行环境 二、需求说明 三、思路分析 五、具体方案 六、恢复表数据 一、运行环境 系统&#xff1a;CentOS7.3 数据库&#xff1a;MySQL 8.0.21 二、需求说明 线上有个表的数据被误操作了很多&#xff0c;无法通过bin-log进行具体的恢复。所以当前我们需要从全…

Spring Boot 应用程序生命周期扩展点妙用

文章目录 前言1. 应用程序生命周期扩展点2. 使用场景示例2.1 SpringApplicationRunListener2.2 ApplicationEnvironmentPreparedEvent2.3 ApplicationPreparedEvent2.4 ApplicationStartedEvent2.5 ApplicationReadyEvent2.6 ApplicationFailedEvent2.7 ApplicationRunner 3. 参…

产品能力|AIRIOT可视化组态引擎如何应用于物联业务场景中

在物联网的业务应用场景中&#xff0c;可视化组态是一个必不可少的功能需求。不同的行业场景&#xff0c;都需要将物联设备采集的数据和业务场景状态进行直观的可视化展示&#xff0c;供使用者进行分析或决策。如工艺流程用能监测、3D场景构建、能耗趋势场景报警联动、重点设备…

RAD-NeRF模型

问题1&#xff1a; 添加在以下的参数里添加bin_size0 问题2&#xff1a; 更行GLIBC_2.29_glibc_2_29.so_xihuanyuye的博客-CSDN博客

如何使用Crank给我们的类库做基准测试

背景 当我们写了一个类库提供给别人使用时&#xff0c;我们可能会对它做一些基准测试来测试一下它的性能指标&#xff0c;好比内存分配等。 在 .NET 的世界中&#xff0c;用 BenchmarkDotNet 来做这件事是非常不错的选择&#xff0c;我们只要写少量的代码就可以在本地运行基准…

【小白必看】Python爬取NBA球员数据示例

文章目录 前言导入需要的库和模块设置请求头和请求地址发送HTTP请求并获取响应处理响应结果解析数据将结果保存到文件完整代码详细解析 运行效果结束语 前言 使用 Python 爬取 NBA 球员数据的示例代码。通过发送 HTTP 请求&#xff0c;解析 HTML 页面&#xff0c;然后提取出需要…

imgcat命令行查看图片

背景 昨天在哔哩哔哩上看到了mac控制台工具imgcat 可以实现在控制台查看图片&#xff0c;我觉得太酷炫了&#xff01;于是手动的安利一下。 下载工具 curl "https://iterm2.com/utilities/imgcat" > imgcat执行权限 chmod x imgcat第一次使用 ./imgcat ~/img…

RocketMQ发送消息还有这种坑?遇到SYSTEM_BUSY不重试?

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 RocketMQ版本 5.1.0 背景 最近线上的RocketMQ集群遇到了如下问题&#xff0c;业务方的小伙伴反馈问题&#xff0c;说出现了 MQBrokerException&#xff1a;CO…

C++数据结构笔记(10)递归实现二叉树的三序遍历

对于三种遍历方式来说&#xff0c;均为先左后右&#xff01;区别在于根结点的位置顺序 先序遍历&#xff1a;根——左——右 中序遍历&#xff1a;左——根——右 后序遍历&#xff1a;左——右——根 &#xff08;所谓先中后的顺序&#xff0c;是指根结点D先于子树还是后于…

力扣算法练习(四)

1.盛水最多的容器(11) 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;…

Vue 常用指令 v-on 自定义参数和事件修饰符

自定义参数就是可以在触发事件的时候传入自定义的值。 文本框&#xff0c;绑定了一个按钮事件&#xff0c;对应的逻辑是sayhi&#xff0c;现在无论按下什么按钮都会触发这个sayhi。但是实际上不是所有的按钮都会触发&#xff0c;只会限定某一些按钮&#xff0c;最常见的按钮就…

linux下nginx的安装和使用

文章目录 &#x1f4d2;安装nginx1️⃣上传到对应目录2️⃣解压nginx3️⃣检查是否启动成功 &#x1f4d2;使用nginx1️⃣简单的反向代理2️⃣介绍location配置中root和alias的区别 &#x1f4d2;安装nginx 官网下载: https://nginx.org/ 我这里自己下载的是稳定版本 nginx-1.…

手撕顺序表

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1…

标准IO_文件读写_fgetc,getchar,ungetc,fgets,fputs,fread,fwrite

目录 1.单字符文件读写 1.1 单字符读文件 1.1.1 fgetc函数 1.1.2 getc函数 1.1.3 getchar函数 1.1.4 ungetc函数 1.1.5 单字符读文件综合示例代码 1.2 单字符写文件 1.2.1 fputc函数 1.2.2 putc函数 1.2.3 putchar函数 1.2.4 单字符写文件综合示例代码 2.多字符文…

c++11 标准模板(STL)(std::basic_filebuf)(七)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_filebuf : public std::basic_streambuf<CharT, Traits> std::basic_filebuf 是关联字符序列为文件的 std::basic_streambuf 。输入序…

python核心-面向对象-三大特性:封装,继承,多态

封装 继承 # class Animal: # pass # # class xxx: # pass # # class Dog(Animal,xxx): # pass # # # d Dog() # print(d.__class__) # print(Dog.__class__) # # # print(Dog.__bases__) # print(Animal.__bases__) # # object# ---------------------继承-资源…