【SDU Chart Team - Core】组件的模块化设计和命令式接口

本文探讨了一种组件的模块化设计,允许用户自定义行为并通过命令式接口注册新功能。组件基于公共接口实现可插拔,支持多继承以实现多样化特性。同时,文章介绍了命令行设计模式,包括初始化、注册、运行和终止命令等,强调其高自由度和低接口成本,但也指出潜在的安全性、效率和并发性问题。
摘要由CSDN通过智能技术生成

组件的模块化设计和命令式接口

组件模块

组件是个相当自由的内容,一切可移动可修改的图形在本项目中都被称为组件。由于组件的交互方式可以千变万化,行为也可以千差万别,所以要提出一种通用的框架来适应全部的变化很难。

于是决定把修改方式和对应行为的实现交给用户,因此使用了一套非常自由的体系,这使得模块都是可插拔的。我们定义了一个基本的架构,实现了一些基本功能。用户需要的其他功能可以基于或部分基于这些功能,在这个架构上继续派生。 这个模型是增量的。

框架

框架图

模块框架图

这是一个自底向上的设计思路。最底层是一个公共的接口,功能向上逐渐累积,最上层是用户自定义内容。

类图

组件类图
模块的可拔插一是体现在均继承自公共接口,满足依赖倒置原则;二是通过命令式接口来注册新功能,使得自定义内容生效,这个将在后面介绍;三是利用多继承,使得组件拥有多种模块的特性。

各模块概览

模块名虚类?说明
ComponentBasicsN实现了组件抽象接口中的全部内容
ComponentRotatableN在组件基础模块上添加了旋转操作
ComponentScalableY描述组件如何缩放,不具有通用性
ComponentFlippableY描述组件如何翻转,不具有通用性
ComponentWritableN设置组件内容,内容为HTML
ComponentStylizedN设置组件样式,内容为CSS

具体内容及实现将在以后给出。

优点和缺点

  • 优点

    1. 高自由度

      用户可在任意层定义新内容,只需要实现公共接口即可被接收使用。这使得组件功能得以无限延伸。

    2. 高复用性

      模块间允许存在依赖关系,因此新的模块可以基于现有模块实现,从而完成复用。

    3. 可拔插

      采用依赖倒置、命令式接口、多继承,使得模块可拔插。这使得组件的特性多样化,且定义组件更简便。

  • 缺点

    1. 管理复杂

      高自由度牺牲了管理的成本,尽管目前只有五个模块,尚未体现出此问题。当模块数量增多后,模块的功能界定则越来越复杂,管理成本也必然会逐渐提高。

    2. 耦合度问题

      允许模块间的依赖关系,尽管降低了开发时的编写代价,但增加了耦合程度。若设计不当,则依赖现存的模块,只有了解被依赖模块对应方法的细节,才能保证新模块能正确实现,使得依赖倒置原则被打破。

    3. 菱形继承问题

      对于一个模块,它的模块拔插是基于多继承的。尽管多继承对非重复的功能取并集,但对于冲突的功能并不能很好地处理。菱形继承问题会产生冲突,这是因为基类的方法被多次实现。设计中尽量需要避免冲突,除非一个模块直接依赖另一个模块。当涉及冲突时,需要手动处理冲突。实际上基于菱形继承,冲突几乎难以避免:例如抽象接口中定义了初始化,每个模块都各自实现了初始化,则组件在继承了这些模块后,需要手动覆盖初始化,使得这些模块的初始化能有序进行。


命令式接口

命令式接口采取的是命令行设计模式,旨在进一步降低功能的声明成本和调用成本;并且,命令行设计能消除分支。

接口原语

  • Init 初始化

    命令行的初始化。

  • Register 注册命令

    注册一个新的命令。提供命令标识符、参数表、以及对应的脚本,然后将其放入命令映射中。

  • Run 运行命令

    运行一个命令。根据命令标识符,在命令映射中找到该命令,将参数传入对应的脚本,运行后返回运行结果。

  • Terminate 结束

    终止命令行。

函数式实现

注意到命令行的注册命令和运行命令中需要用到脚本,那么在运行时环境下如何体现脚本?不妨将这个脚本理解为函数 f f f,参数理解为 x x x:那么运行命令就是根据命令映射找到 f f f,然后执行 f ( x ) f(x) f(x) 并返回结果。C++11中使用函数式编程,可利用 std::function 将这个函数实例化,保存到命令映射中。所以具体的实现如下:

  • 命令映射

    static std::map<std::string, std::function<PARAMETERS>> events;
    

    就是一个“命令标识符-脚本”的Map。

  • 基于Json的命令标识符、参数

    命令标识符、参数被序列化到一段字符串中。例如命令格式可以是:

    command [param1] [param2] ...
    

    这种格式下需要将参数解析工作交给具体的脚本,通过类似$1,$2的形式使用参数。并且语义上不直接支持其他结构,如元组、列表;若需要支持,则必须在脚本中实现。

    于是考虑采用Json的方式传递命令,一段命令格式如下:

    {
        "command": "命令类型",
        "parameter": "参数",
        "parameter": [列表],
        //...
    }
    

    依托Json中完备的数据结构,可以支持大部分类型的参数,并且可将实现成本交给第三方库。

  • 注册命令

    void Register::addEvent(const std::string &interface_name,
        std::function<const json(json &)> interface_body) {
        events[interface_name] = interface_body;
    }
    

    即按命令名,将对应的脚本添加到命令映射中。

  • 运行命令

    以下表达式描述了如何执行一条命令:

    std::function<void(json &, json &)> run_cmd = [](json &in, json &out) {
        std::string cname = in["command"];
        try {
            if (!events.count(cname)) { // 不存在的命令
                in["status"] = NULLCMD;
                out.push_back(in);
            } else {
                out.push_back(events[cname](in));
            }
        } catch (const std::exception& e) { // 无法处理的异常
            in["error"] = "unhandled exception";
            in["error_msg"] = e.what();
            out.push_back(in);
        }
    };
    

    in 即作为输入的命令Json,out 是作为命令输出的Json。命令的输出允许多值返回,事实上就是在输入命令Json的基础上添加返回值。

  • 多发射命令

    允许一次传递一个命令Json表,然后依次执行每条命令:

    auto _in = json::parse(cmd);
    json _out; json _ret;
    if (_in.is_array()) { // 执行每条
        for (auto _i : _in) run_cmd(_i, _out);
    } else if (_in.is_object()) { // 执行一条
        run_cmd(_in, _out);
    }
    _ret["returns"] = _out;
    _ret["domcmd"] = Canvas::commit(); // 更新
    return _ret.dump();
    

    输入和输出均为Json字符串。

    多发射只是相对前端而言的,前端可以同时发送多条命令,并在同一时间得到结果。但事实上后端仍然是按顺序执行,只是优化了多条命令反复传输所造成的额外开销

  • 初始化和终止

    事实上命令行是静态单例的,命令行的生命周期约等于整个程序的生命周期。可以定义并实现自定义的初始化方法;而对于终止,则可以不显式实现。

优缺点

优点
  • 高自由度

    几乎支持任意类型的参数,可以随意添加命令。

  • 低接口

    只需调用运行命令的接口。所有具体的功能全部以命令方式被执行。

缺点
  • 安全性

    没有显式类型检查,类型检查必须在脚本中完成。虽然有异常处理机制,但不保证错误被及时处理。(由于是纯客户端程序,所以不考虑身份验证的问题)

  • 效率

    采用字符串和Json序列化方式,增加了解析时间;并且执行命令需要额外的开销。

  • 并发

    脚本执行、多发射都是基于串行顺序,不提供并发机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值