什么是 mixin,它有什么用处?

1333
在《Python编程》一书中,马克·卢茨提到了术语mixin。我来自C/C++/C#背景,之前从未听说过这个术语。什么是mixin?
这个例子(我链接它是因为它相当长)中看来,我认为它是使用多重继承来扩展类而不是正常的子类化。这样理解对吗?
为什么我要这样做而不是将新功能放入子类中?同样,为什么mixin /多重继承方法比使用组合更好?
mixin和多重继承有什么区别?这只是一个语义问题吗?

2
对于C++(在Windows上),Active Template Library利用CRTP(“Curiously Recurring Template Pattern”)将多个基类功能添加到您的类中。这类似于混合提供的功能。调试过程非常有趣。 - JBRWilkinson
Mixin在面向对象编程中是一种反模式(在我个人看来):特质和Mixin不是面向对象编程 - yegor256
18个回答

10

这不是Python的例子,而是在D编程语言中,术语mixin用于指称一种类似地添加大量内容到一个类中的构造。

在没有多重继承(MI)的D语言中,这可以通过在作用域中插入模板(将其视为语法感知和安全性较高的宏,您就会接近它)来完成。这允许在类、结构体、函数、模块或其他任何位置使用一行代码扩展到任意数量的声明。


2
Mixin是一个通用术语,在D、Ruby等编程语言中使用。根据维基百科的说法,它们起源于旧学派lisp系统,并于1983年首次被记录下来:http://en.wikipedia.org/wiki/Flavors_%28computer_science%29#cite_note-0 - Lee B

10
OP提到他/她从未听说过C++中的mixin,也许这是因为在C++中它们被称为Curiously Recurring Template Pattern(CRTP)。此外,@Ciro Santilli提到,在C++中mixin是通过抽象基类实现的。虽然抽象基类可以用来实现mixin,但由于可以使用模板在编译时实现运行时的虚函数功能而无需运行时的虚表查找开销,因此它是一种过度设计。
CRTP模式在这里有详细描述。
我已经使用模板类将@Ciro Santilli答案中的python示例转换为C++,如下所示:
    #include <iostream>
    #include <assert.h>

    template <class T>
    class ComparableMixin {
    public:
        bool operator !=(ComparableMixin &other) {
            return ~(*static_cast<T*>(this) == static_cast<T&>(other));
        }
        bool operator <(ComparableMixin &other) {
            return ((*(this) != other) && (*static_cast<T*>(this) <= static_cast<T&>(other)));
        }
        bool operator >(ComparableMixin &other) {
            return ~(*static_cast<T*>(this) <= static_cast<T&>(other));
        }
        bool operator >=(ComparableMixin &other) {
            return ((*static_cast<T*>(this) == static_cast<T&>(other)) || (*(this) > other));
        }
        protected:
            ComparableMixin() {}
    };

    class Integer: public ComparableMixin<Integer> {
    public:
     Integer(int i) {
         this->i = i;
     }
     int i;
     bool operator <=(Integer &other) {
         return (this->i <= other.i);
     }
     bool operator ==(Integer &other) {
         return (this->i == other.i);
     }
    };

int main() {

    Integer i(0) ;
    Integer j(1) ;
    //ComparableMixin<Integer> c; // this will cause compilation error because constructor is protected.
    assert (i < j );
    assert (i != j);
    assert (j >  i);
    assert (j >= i);

    return 0;
}

编辑:在ComparableMixin中添加了受保护的构造函数,以便只能继承而不是实例化。更新示例以展示当创建ComparableMixin对象时,受保护构造函数将导致编译错误。


在C++中,Mixins和CRTP并不完全相同。 - ashrasmun

9

如果可能的话,我建议在新的Python代码中不要使用mix-ins(混入),而是采用其他方式(例如组合代替继承或将方法添加到自己的类中),这样做并不需要更多的努力。

在旧式类中,您可以使用mix-ins作为从另一个类中获取几个方法的一种方式。但在新式世界中,即使mix-in也会继承自object。这意味着任何多重继承的使用都会自然引入MRO问题

有方法可以使Python的多重继承MRO工作,最 notable 的是super()函数,但这意味着您必须使用super()完成整个类层次结构,并且它的流程控制难度相当大。


3
自从2.3版本起,Python使用了“C3方法解析”,详见Python 2.3方法解析顺序方法解析顺序 - webwurst
12
在大多数情况下,我个人更喜欢使用 mixin 而不是猴子补丁;这样更容易理解并能够跟随代码。 - tdammers
5
被踩了。虽然你的回答表达了一种有价值的开发风格观点,但你并没有真正回答实际问题。 - Ryan B. Lynch

8

mixin提供了一种在类中添加功能的方法,即通过将模块包含在所需类中来与定义在模块中的方法交互。虽然Ruby不支持多重继承,但提供mixin作为实现多重继承的替代方案。

下面是一个例子,说明如何使用mixin实现多重继承。

module A    # you create a module
    def a1  # lets have a method 'a1' in it
    end
    def a2  # Another method 'a2'
    end
end

module B    # let's say we have another module
    def b1  # A method 'b1'
    end
    def b2  #another method b2
    end
end

class Sample    # we create a class 'Sample'
    include A   # including module 'A' in the class 'Sample' (mixin)
    include B   # including module B as well

    def S1      #class 'Sample' contains a method 's1'
    end
end

samp = Sample.new    # creating an instance object 'samp'

# we can access methods from module A and B in our class(power of mixin)

samp.a1     # accessing method 'a1' from module A
samp.a2     # accessing method 'a2' from module A
samp.b1     # accessing method 'b1' from module B
samp.b2     # accessing method 'a2' from module B
samp.s1     # accessing method 's1' inside the class Sample

4
这个和一般的多重继承有什么不同? - Ciro Santilli OurBigBook.com
区别在于您无法从模块创建实例,但如果一般类和模块之间没有区分,则混合是不明确的事情,很难理解哪个是一般类,哪个是混合。 - ka8725
那么在 Ruby 中,mixin 只是不能被实例化的类,但必须用于多重继承吗? - Trilarion

7

我刚刚使用了一个 Python mixin 来为 Python milter 实现单元测试。通常,milter 会与 MTA 进行通信,这使得单元测试变得困难。测试 mixin 覆盖了与 MTA 通信的方法,并创建了一个由测试用例驱动的模拟环境。

因此,您可以像这样将未修改的 milter 应用程序(例如 spfmilter)和 TestBase mixin 结合使用:

class TestMilter(TestBase,spfmilter.spfMilter):
  def __init__(self):
    TestBase.__init__(self)
    spfmilter.config = spfmilter.Config()
    spfmilter.config.access_file = 'test/access.db'
    spfmilter.spfMilter.__init__(self)

然后,在milter应用的测试用例中使用TestMilter:
def testPass(self):
  milter = TestMilter()
  rc = milter.connect('mail.example.com',ip='192.0.2.1')
  self.assertEqual(rc,Milter.CONTINUE)
  rc = milter.feedMsg('test1',sender='good@example.com')
  self.assertEqual(rc,Milter.CONTINUE)
  milter.close()

http://pymilter.cvs.sourceforge.net/viewvc/pymilter/pymilter/Milter/test.py?revision=1.6&view=markup


6
也许来自Ruby的例子可以帮助你理解:
你可以包含mixin Comparable并且定义一个函数"<=>(other)",这个mixin会提供以下所有函数:
<(other)
>(other)
==(other)
<=(other)
>=(other)
between?(other)

它通过调用<=>(other)并返回正确结果来实现此目的。 "instance <=> other"如果两个对象相等,则返回0,如果instance大于other,则返回小于0的值,如果other大于instance,则返回大于0的值。

这里有一篇关于Python的类似mixin的帖子。虽然建议将__lt__定义为基础,而不是已被弃用且不建议使用的__cmp__。对我来说,使用那个mixin似乎比使用相当复杂的装饰器(functools的一部分)更简单 - 尽管这个 可以更动态地响应提供的比较... - Tobias Kienzler

3

我了解到您有一定的C#背景,因此一个不错的起点可能是.NET的mixin实现。

您可以查看这个codeplex项目:http://remix.codeplex.com/

观看lang.net Symposium链接以获得概述。在codeplex页面上仍有更多文档说明。

祝好! Stefan


3

以上所有优秀答案的粗略总结如下:

                状态       /     方法 具体方法 抽象方法
具体状态 抽象类
抽象状态 混合 接口

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接