Mixin是什么概念?

在浏览tornado的代码时,auth中的类都以Mixin命名,这个词好奇怪啊,查了一下资料,有人解释Mixin为mix in,混入的意思,类似于多重…
关注者
662
被浏览
280,172

22 个回答

Mixin 实质上是利用语言特性(比如 Ruby 的 include 语法、Python 的多重继承)来更简洁地实现

组合模式

以如下 Java 伪码为例,实现一个可复用的“打标签”组件(Taggable),并且应用到帖子(Post)模型上:

import java.util.List;
import java.util.ArrayList;

interface Entity {
    public int getId();
    public int getKind();
}

interface Taggable {
    public void addTag(int tagId);
    public List<Integer> getTags();
}

class TaggableImpl implements Taggable {
    private Entity target;

    public TaggableImpl(Entity target) {
        this.target = target;
    }

    public void addTag(int tagId) {
        int id = target.getId();
        int kind = target.getKind();
        System.out.println("insert into ... values "
                + id + ", "
                + kind + ", "
                + tagId + ")");
    }

    public ArrayList<Integer> getTags() {
        // query from database
        return new ArrayList<Integer>();
    }
}

class Post implements Entity, Taggable {
    public final static int KIND = 1001;

    private Taggable taggable;
    private int id;
    private String title;

    public Post(int id, String title) {
        this.id = id;
        this.title = title;
        this.taggable = new TaggableImpl(this);
    }

    public int getId() {
        return id;
    }

    public int getKind() {
        return KIND;
    }

    public void addTag(int tagId) {
        taggable.addTag(tagId);  // delegate
    }

    public ArrayList<Integer> getTags() {
        return taggable.getTags();  // delegate
    }
}

这里使用组合模式,在 TaggableImpl 中实现打标签的逻辑,然后让 Post 类和 TaggableImpl 类都实现 Taggable 接口,Post 类中创建一个 TaggableImpl 实例并在实现 Taggable 时将相应方法调用委托过去。

如果在 Python 中应该怎么做呢?因为 Python 允许多重继承,所以“委托方法”的过程可以简化为直接将实现混入宿主类中:

class TagMixin(object):
 
    def add_tag(self, tag_id):
        sql = ('insert into target_tagged'
               ' (target_id, target_kind, tag_id, creation_time) '
               'values (?, ?, ?, CURRENT_TIMESTAMP)')
        params = (self.ident, self.kind, tag_id)
        storage.execute(sql, params)
        storage.commit()
 
    def get_tags(self):
        sql = ('select tag_id, creation_time from target_tagged '
               'where target_id = ? and target_kind = ?')
        params = (self.ident, self.kind)
        cursor = storage.execute(sql, params)
        return cursor.fetchall()
 
 
class Post(Model, TagMixin):

    kind = 1001
 
    def __init__(self, ident, title):
        self.ident = ident
        self.title = title
 
    def __repr__(self):
        return 'Post(%r, %r)' % (self.ident, self.title)

可以看出这里多重继承的用法是非常谨慎的:

  • TagMixin 类是单一职责的
  • TagMixin 类对宿主类(Post)一无所知,除了要求宿主类有 ident 和 kind 这两个属性(等价于 Java 中要求宿主类实现 Entity 接口)
  • 宿主类的主体逻辑不会因为去掉 TagMixin 而受到影响,同时也不存在超类方法调用(super)以避免引入 MRO 查找顺序问题

所以这样比 Java 中的组合模式实现方式更加简洁。同时因为使用得当,钻石调用、MRO 查找顺序等多重继承的弊病也没有被踩到。当然,这种 Duck Type 的设计也比显式的接口约束对开发者有更高的要求,要求代码中无 interface 而开发者脑海中有清晰的 interface。

Ruby 的 include 语法实现的 Mixin 也同理。

趁着午休来答一个。

如楼上很多答主一样,谈到Mixin就不得不谈到多重继承,因为Mixin的出现就是为了解决多重继承的问题,那么多重继承有什么问题呢?

在《松本行弘的程序世界》一书中,作者列举了以下三点:

  1. 结构复杂化:如果是单一继承,一个类的父类是什么,父类的父类是什么,都很明确,因为只有单一的继承关系,然而如果是多重继承的话,一个类有多个父类,这些父类又有自己的父类,那么类之间的关系就很复杂了。
  2. 优先顺序模糊:假如我有A,C类同时继承了基类,B类继承了A类,然后D类又同时继承了B和C类,所以D类继承父类的方法的顺序应该是D、B、A、C还是D、B、C、A,或者是其他的顺序,很不明确。
  3. 功能冲突:因为多重继承有多个父类,所以当不同的父类中有相同的方法是就会产生冲突。如果B类和C类同时又有相同的方法时,D继承的是哪个方法就不明确了,因为存在两种可能性。

当然你可以说有些语言解决了这个问题,但是并不是所有语言都想要去纠结这个问题。

所以为能够利用多继承的优点又解决多继承的问题,提出了规格继承和实现继承这两样东西。

简单来讲,规格继承指的是一堆方法名的集合,而实现继承除了方法名还允许有方法的实现。

Java 选择了规格继承,在 Java 中叫 interface(不过Java8中已经有默认方法了),而 Ruby 选择了实现继承,也可以叫Mixin,在 Ruby 中叫 module。

从某种程度上来说,继承强调 I am,Mixin 强调 I can。当你 implement 了这个接口或者 include 这个 module 的时候,然后就你行你上。

所以这又可以扯到 duck typing 去了,不细说。要想了解具体的可以看一下《松本行弘的程序世界》这本书。