A Strory of Mixin

Mixin

Mixin是一种设计思想, 主要内容是: Mix some MixinClass into a CoreClass, so the CoreClass can get some new feature, or get enhanced.

与其他设计模式相比, Mixin的主要特点是它没有固定的编程pattern, 只是一种指导思想, 所以可能会有各种各样的具体设计自称为"Mixin", 因此, 当你看到一个"Mixin"设计时, 常常会想"我用XX不也可以实现这样的效果吗?", 这是非常正常的.

一般而言, 当设计实现了Mixin时, 常常会有以下特点:

  1. 使用多继承或链式继承, 因为我们常常需要把多个MixinClass混合到CoreClass中,使用多继承或链式继承是非常直观的方法.
  2. 尽管使用了继承, 但是一般不进行"覆写", 因为CoreClass和MixinClass并没有is-a的逻辑关系.
  3. MixinClass一般不能实例化, 其自身往往必须要Mix到其他类型后才会有意义.
  4. MixinClass一般不带有任何数据成员, 只用于提供新的method.
  5. Mix总是在class的层面进行的,不会涉及到instance,Mix的产物总是一个新的class

注意,上面的这些特点仅仅是常见的特点, 并不是一定都要符合. 在实际的实践中, 我们也常常会希望得到一些看起来fancy的魔法特性, 从而写出不符合上面特征的代码.

最后, Mixin 对 Python 这样的动态语言而言, 仅仅是一种设计思想, 收益可能并不是特别明显. 但是对于C++而言,则可以通过Mixin实现完全的Concept多态, 这种基于Concept的多态完全绕开了虚函数表,所有函数调用都是静态绑定的,可以带来明显的性能收益, 已经在LLVM中广泛应用.

Mixin的两种Pattern: 链式继承与多继承

对Mixin而言, 链式继承和多继承从技术层面上说并不冲突, 两种方法可以混合使用, 不过一般而言, 一个良好的设计总应该stick to one, 从而保证整体上的结构清晰且简单.

多继承

使用多继承的Mixin,其功能没有链式继承强大,只能应用在部分场景中,不过其形式简单,也常常被使用. 下面是Python和cpp对这种mixin的典型应用场景.

多继承场景中, MixinClass仅用于提供额外的API, 这些额外的API可以基于MixedClass自身已有的API或数据成员实现, 换言之, 要使用这些Mixin时, MixedClass需要保证相关的API及数据是可用的.

class MixedClass(Mix0,Mix1,Mix2,...Base):
  pass
template<class DerivedT>
class Mix0{

};

...

class MixedClass: public Mix0<MixedClass>,
                  public Mix1<MixedClass>,
                  public Mix2<MixedClass>,
                  ...
                  public Base{};

链式继承

链式继承的Mixin功能是最为强大的, 它不但可以提供额外的API, 还可以利用链式调用, 增强已有的API(效果类似于装饰器,不过是在类型上实现的,而不是在对象上实现)

MixedClass = mix0(mix1(mix2(mix3(Base))))
obj = MixedClass()
template<class TargetT>
class Mix0 : public TargetT{

};

using MixedClass = Mix0<Mix1<Mix2<Mix3<Base>>>>;

Example

参考该Github Repo, 该仓库中展示了典型的多继承/链式继承使用场景.

Other Notes

  1. 在C++中应用Mixin时, AdapterMixin可能是一个非常有用的Mixin, 它是一个基于链式继承的Mixin, 可以实现和类型擦除类似的功能.
template<class SourceT, class TargetT>
class AdapterMixin : public SourceT, public TargetT{

  void SomeMethod(){
    return SourceT::SomeMethod()
  }

}
  1. 在C++中应用Mixin时,所有的MixinClass最好都不要创建任何数据成员. 数据成员均应由用户来添加,这样可以保证对象的内存布局不会被Mixin影响. 例如, 某个Mixin需要使用名为ID的数据成员的地址, 那么int ID这个数据成员就应该由用户来其自定义类中添加,而不是定义在MixinClass中.

Read more

Coroutine

从一般概念上说, 协程是特殊的函数调用: 被调用的函数可以在可控的位置被中断,然后在下一次调用时,继续从上次中断的位置继续执行。 本文主要通过Python的协程来介绍协程, 这是我唯一熟悉的一种协程实现. Classic Coroutine 下面的python代码很好的说明了协程的核心功能 def co_routine(): recv0 = yield 996 # hangs here after first coro.send assert recv0 == "Second" yield 711 # hangs here after second coro.send return def main(): coro = co_routine() # Create a new coroutine object value = coro.send(None)

By Edimetia3D

GDB with Python

这篇文章的主要应用场景是调试Python的C/C++ Extension 1. 同时使用pdb / gdb 进行调试. 通俗点说, 既可以break在 .py 文件中,也可以break在 .cc 文件中 2. 在gdb中不但可以获得常规的调试信息, 还可以获得python VM 的调试信息, 例如获得python的调用栈, 访问Python局部变量等. 这将会在调试exception时(如Segmentfalut)非常有用, 这种场景下, 定位 Python VM 正运行到哪一行代码往往可以提供一些直观的重要信息. 第一步: 编译源码以获得一些辅助数据. 我们并不真的需要使用从源码编译的Python, 但是一些调试相关的辅助文件需要从源码中获得, 包括 python-gdb.py及debug symbol等. 在 https://www.python.org/ftp/python/ 或 https://github.com/python/cpython

By Edimetia3D

Bazel Notes

这是一篇2019年左右的记录, 内容可能过时, 也不太全面 杂谈 Bazel是Google为Monorepo服务而开发的构建工具. 首先是巨大,当问题的规模变大,事情总是会变得更复杂. 而Google面对的"巨大Monorepo",应该是世间罕有的. 然后是Monorepo,这极大的影响了代码的组织风格.例如,你要写一个操作系统内核ProjectOS,还要写一个游戏ProjectGame.在传统的开发习惯中,这两个项目会组织到两个不同的Repo里,PorjectOS和ProjectGame之间无法直接相互引用,例如,你在ProjectOS里写了一个高级的数据结构,想要在Game里也使用,要么直接复制粘贴,要么是创建一个新的CommonRepo,把可公用的代码都放在Common里,然后两个项目各自引入Common作为依赖. 使用MonoRepo则不存在这个问题,Game可以直接依赖OS内的组件,按照Bazel的语法描述,就是在Game中可以直接使用@ProjectOS//path/to/package:AdvancedStruct.当然,你仍然可以选择重构一

By Edimetia3D

Unix related things

这是一篇2017年左右的记录, 仅用作分享 杂 * 在shell内能干的事,我们都可以比较简单地通过系统调用实现. * `称为反引号,^称为脱字符,常用来表示CTRL * windows的系统调用是不开放的,windows下只能直接使用windows.h里的windows API. * /dev目录下的设备是供用于程序直接使用的,主要由block,char,pipe,socket类型 * 并不是所有设备都能映射为这种形式 * /sys/device/目录称为sysfs,他下面存放了所有设备的信息.(不能直接从/dev获得任何设备信息) * udevadm info --query=all --name="/dev/sda1"可以用于查询/dev下某个设备对应的sysfs路径 权限系统 * 权限系统由两部分组成 * 文件属性:用于标注文件owner,所属组,以及权限的设定(默认只有owner和root可以修改权限设置) *

By Edimetia3D