阅读更多

4顶
0踩

编程语言

转载新闻 使用高阶函数实现类的扩展设计

2015-12-29 16:20 by 副主编 mengyidan1988 评论(3) 有3654人浏览
本文转自:http://efe.baidu.com

在不少框架中,都会对“扩展”这一概念有需求。所谓扩展,即一个可组合的组件,用于嵌入到目标的生命周期中,对目标的行为进行额外的处理使得目标拥有不同的表现。

一个非常简单的案例即日志的记录。通常框架自身并不会有业务相关的日志记录的功能,而业务代码也不希望混入并非业务逻辑的日志记录部分。那么使用一个扩展,在合适的点进行日志的收集和存储是很合适的设计。

在以往,比较流行的扩展通常有几种形式:

1.Mixin形式。这种形式下扩展与目标形成完全的覆盖关系,属于暴力而简单的方法。
class Component {
    constructor({mixins}) {
        mixins.forEach(mixin => Object.assign(this, mixin));
    }

    doWork() {
        // ...
    }
}

let logMixin = {
    doWork(...args) {
        console.log('Start do work');
        Component.prototype.doWork.apply(this, ...args);
        console.log('Finish do work');
    }
};

let foo = new Component({mixins: [logMixin]});
foo.doWork();

2.生命周期形式。这种模式在框架设计之初就定义多个扩展可运作的点,在生命周期的特定阶段激活扩展,同时给予扩展足够的事件以及可重写的方法来完成其功能:
class Component {
    constructor({extensions}) {
        for (let extension of extensions) {
            extension.target = this;
            extension.enable();
        }
    }

    doWork() {
        this.fire('beforedowork');
        // ...
        this.fire('dowork');
    }
}

let logExtension = {
    enable() {
        this.target.on('beforedowork', () => console.log('Start do work'));
        this.target.on('finishdowork', () => console.log('Finish do work'));
    }
};

let foo = new Component({extensions: [logExtension]});
foo.doWork();

但是这两种方式都存在着一些固有的缺陷:
  • 目标需要有非常精细的设计来支持扩展的运作,如果事件不够则扩展需要重写特定方法。虽然JavaScript确实是一个弱类型的动态语言,但是否应该放任一段外部逻辑重写任意方法,在设计上是值得商榷的。
  • 类的保护(protected)方法是否对扩展开放,在概念上难以权衡。如果不开放保护方法则很可能扩展没有足够的信息来完成工作,而开放保护方法则破坏了面向对象本身封装性的概念。
  • 多个扩展都对同一个方法的重写时存在冲突,设计不合理导致相互覆盖很可能让扩展产生不可预期的结果。
  • 重写方法较为复杂,需要先保留原有方法函数引用再进行重写,重写过程中需要使用.apply或.call进行调用,无法使用如super等ES6的语言特性。
  • 如果扩展应用的对象不幸经过了Object.freeze等方法的处理,则扩展很大概率将无法工作。
  • 扩展启用/销毁的生命周期难以设计,过早介入可能导致扩展在启用时没有足够的信息判断自己需要做的工作,过晚介入则可能错过一些阶段。

总结以上的问题,我们发现很多问题在于目标成员的可访问性上,而可访问性是应用于“继承”这一概念上的。

那么,一个很好的方案是让扩展也在“继承”上进行体现,而不是以“组合”的关系工作。虽然我们一直说“组合优于继承”,但是在可访问性限制等种种因素下,在扩展这一场景下,继承恰恰能给予更好的支持。

在JavaScript中,类实际上就是一个函数,那么对于类进行转换的所谓“扩展”,我们也称其为一个高阶函数,其范式为:
F(class1) => class2

即我们的扩展接受一个类的构造函数(也是类本身),返回另一个类,其作用是通过继承对类进行一定的转换。在这种设计下,我们上面的代码可以实现为:
class Component {
    constructor() {
    }

    doWork() {
        // ...
    }
}

let log = (Target) => {
    return class extends Target {
        doWork() {
            console.log('Start do work')
            super.doWork();
            console.log('Finish do work')
        }
    }
};

let create = (Class, extensions) {
    let TargetClass = extensions.reduce((Raw, extension) => extension(Raw), Class);
    return new TargetClass();
};

let foo = create(Component, [log]);
foo.doWork();

通过继承我们可以很好地实现方法的重写,也可以利用如super这样的关键字,同时也不需要考虑doWork是保护方法还是公开方法,使得Component类完全不需要为了扩展而进行额外的设计,所有的扩展均在外部的工厂(create函数)实现,更好地进行了逻辑的解耦。

同时,这一方案也与JavaScript Class Decorator的功能相兼容,其微小的区别在于:
  • 由于扩展生效时类的prototype已经封闭,因此扩展必须返回一个子类,而不能直接对prototype进行修改。
  • 扩展可在创建实例时动态定义。

由于扩展的限制比装饰器更为严格,因此一个扩展同时可以静态地在定义类时通过装饰器的形式使用,也可以在工厂生产实例时动态地使用,这也保证了更好的代码复用性。
4
0
评论 共 3 条 请登录后发表评论
3 楼 amly0615 2016-01-02 22:28
amly0615 写道
amly0615 写道
xxxxxxxxxxx 你好,看看怎么样

还不错

确实不错。
2 楼 amly0615 2016-01-02 22:28
amly0615 写道
xxxxxxxxxxx 你好,看看怎么样

还不错
1 楼 amly0615 2016-01-01 03:47
xxxxxxxxxxx 你好,看看怎么样

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 如何给DataGrid添加自动增长列

    如何给DataGrid添加自动增长列我想我们都知道在数据库中如何添加自增长列,我们可以将这个自增长列绑定到DataGrid上使得用户方便的知道现在是第几行,今天我介绍一种不用数据库就可以简单显示出自增长列的方法,有人可能会说既然数据库支持我们为什么这样做?我想有如下的两个理由:1、不是所有的表都有自增长列。2、当自增长列不自动复制的时候会出现断号的现象,即使复制这也是有可能发生的。但是先要说明

  • datagrip设置自增

    //查看当前序列值 select currval('patrol_team_id_seq'); //设置当前序列 select setval('patrol_team_id_seq', 3 ); Tip: befor select we should set current value

  • 修改MYSQL表的下一个自增ID

    将xxx替换成表名 alter table xxx AUTO_INCREMENT=10000; 例: alter table t_user AUTO_INCREMENT=100; 该语句执行后, t_user表的下一条数据的自增ID将变为100

  • 【金仓数据库设置主键自增】

    金仓数据库主键自增

  • DataGrid的序号列(自增长)

    方法一:                       方法二:*.cs页public int no = 1;//题目序号,全局变量*.aspx页 其他:1.DataGrid2.GridView

  • datagrid中的自动增长列

    下面是自动增长datagrid的三种方法。1.反向生一个列(既第一行显是编号最大的一个)  //加一个列DataColumn   col=new   DataColumn("RowNumber",Type.GetType("System.Int32"));   ds.Tables[0].Columns.Add(col); for(int i=ds.Tables[0].Rows.Count-1;i>

  • 物联网设备流水入库TDengine改造方案

    首先,个人赞成1.3方案,建议应用组增加消息队列服务。从稳定性、数据一致性考虑,方案1.3可以保证数据不丢失、且实时性高。

  • 常见几种数据库中自增字段的设置方法(转)

    常见几种数据库中自增字段的设置方法开发中经常会用到自增字段,而且在不同的环境下(使用不同的数据库),设置自增字段的方法也就存在一定的差异:Microsoft Access中设置自增字段的方法:可以说这是最简单的一种方法——只需要在在设计视图下将数据类型选择为“自动编号”即可,方便快捷。SQL Server中设置自增字段的方法的方法:相对比前边的难了一点:创建数据表时使用这样

  • springboot mybatis-plus tdengine 创建超级表,动态创建子表,动态查询子表

    springboot mybatis-plus tdengine 创建超级表,动态创建子表,动态查询子表mybatis-plus do not support TDengine, use postgresql Dialect paginationInterceptor . setDialectType("postgresql");}

  • DataGridView操作之添加自增列

    1 //冻结将自增长列 2 this.DataGridView.Columns[0].Frozen = true; 3 4 private void DataGridView_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e) 5 { 6 SolidBrush b = new So...

  • PowerDesigner安装使用(主键设置、日期默认值、主键自增等问题)

    最近准备写毕业设计,在设计数据库时使用到了PowerDesigner和DataGrip,写一下使用PowderDesigner遇到的问题以及安装(16.5的安装包以及汉化补丁,安装教程参考https://www.fujieace.com/software/powerdesigner.html) 百度网盘的地址:链接:https://pan.baidu.com/s/13j1cEqlpJ82UzL4mIomszA 提取码:6smi 需要的自取 常见问题: 工具栏找不到 或者在工具栏中找到自定义工具栏 点进

  • MySQL入门(5)——基于datagrip的SQL语句学习

    目录一、什么是SQL二、SQL约束1、主键约束(1)添加主键约束方式一:创建表时,在字段描述处,声明指定字段为主键方式二:创建表时,在constraint约束区域,声明指定字段为主键方式三:创建表之后,通过修改表结构,声明指定字段为主键(2)删除主键约束2、自动增长列3、不同指令删除表的过程4、非空约束(1)添加非空约束方式一:创建表时,在字段描述处,声明指定字段为非空约束方式二:修改表时(2)删除非空约束5、唯一约束(1)添加唯一约束方式一:创建表时,在字段描述处,声明唯一方式二:创建表时,在约束区域,声

  • DataGrip 2021 使用设置,让你使用起来更丝滑

    记录DataGrip一些使用小技巧

  • DataTable数据批量写入数据库三种方法比较

    1) insert循环插入;2) sqldataadapter.update(dataset,tablename);3) sqlbulkcopy.WriteToServer(datatable);1、生成测试的datatable表,表结构如下:UniqueID(主键,自动增长) | CompanyName | CompanyCode | Addr...

  • easyui中的datagrid设置自动增长列

    编写项目过程中,遇到要在datagrid数据网格中显示自动增长的序列的需求 探索后实现如下图效果: 虽然datagrid自己有排序号在最左边,但是需求中有这个自动增长列,那就肯定要探索出来并实现的啦! 在datagrid的列属性中使用formatter属性,这是单元格的格式化函数,需要三个参数: value:字段的值。 rowData:行的记录数据。 rowIndex:行的索引。 我们只需要...

  • DataGrid和GridView自动增加序号(三种实现方式)

    DataGrid自动增加序号(三种实现方式) 方法一:,直接在Aspx页面DataGrid模板列中. 缺点是到第二页分页时又重新开始了.  asp:TemplateColumn HeaderText="序号" FooterText="  " HeaderStyle-Height="20px" FooterStyle-Height="20px" HeaderStyle-Horizo

  • 为datatable添加自增列

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; namespace dataTable自增列 { class DataTableID { /// <summary> /...

  • DataGrip使用入门-常用操作(三)

    接下来,我们来使用DataGrip完成数据库的常用操作,包括查询数据、修改数据,创建数据库、表等。 左上区域显示了当前数据库连接,展开后会显示数据库表等信息,如果展开后没有任何信息,需要选中数据库连接,点击上面的旋转图标同步一下,下方有个More Schema选项,点击可以切换不同的schema。 右键选中的数据库连接,选择open console,就可以在右侧的控制台中书写sql语句了。...

Global site tag (gtag.js) - Google Analytics