文章目录
  1. 1. mixin到底解决的是什么问题
    1. 1.1. 可选的解决方案
  2. 2. mixins 又是什么
  3. 3. 怎么用 mixins 来解决
    1. 3.1. Python - Django中的解决方案
      1. 3.1.1. model 中的共有属性
      2. 3.1.2. 使用 mixin 需要注意的部分
    2. 3.2. mixin 与 Java 语言
  4. 4. 参考

mixin到底解决的是什么问题

一个词来概括的话就是减少重复自己的代码(违反DRY设计原则)

可选的解决方案

  • 继承
  • 组合 + 依赖注入
  • 泛化

mixins 又是什么

一种通过在抽象类中实现共有实现的设计方法。
你会说既然是抽象类,内部怎么会有实现呢? 抽象方法中方法不都是抽象的么?
事实上这是 Java 程序员常见的疑问,即使是在 Java 中 Java8的一些抽象类中也使用了这样的设计(例如 List 容器中就有基于支持Lambda表达式的默认实现)

怎么用 mixins 来解决

Python - Django中的解决方案

model 中的共有属性

先举个例子,我们在创建模型和数据表实体时往往需要记录创建时间、更新时间,如果是在需要有并发的场景下还需要一个并发乐观锁的版本号。这些如果是进行定义的话,在 Django 项目中会是这样的。

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PublishBook(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
description = models.CharField(max_length=511)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class PublishedMobileApplication(models.Model):
id = models.AutoField(primary_key=True)
app_id = models.IntegerField()
app_version_code = models.IntegerField()
app_version_name = models.CharField(max_length=255)
app_version_description = models.CharField(max_length=511)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

我们可以看到,这两个实体中实际上有一部分属性是共有的,在这里进行了重复定义。
如果仅仅是因为重复代码就抽象出共有的基类其实这个决策还是做的有些仓促,还是需要看下之后实体定义的普遍性(这个在之前的描述中就已经有所描述), 所以使用 mixins 我们该如何定义这两个实体呢?

1
2
3
4
5
class TraceableMixins(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
1
2
3
4
5
6
7
8
9
10
11
class PublishBook(TraceableMixins):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
description = models.CharField(max_length=511)

class PublishedMobileApplication(TraceableMixins):
id = models.AutoField(primary_key=True)
app_id = models.IntegerField()
app_version_code = models.IntegerField()
app_version_name = models.CharField(max_length=255)
app_version_description = models.CharField(max_length=511)

使用 mixin 需要注意的部分

MRO,这想必是支持多根继承语言都需要注意的部分。

mixin 与 Java 语言

mixin 的语言模式在支持多根继承的编程语言下似乎是顺理成章的事情,但是在单根继承的语言(比如 Java)又会是怎样的支持呢?
事实上在 Java8 中引入的 default method 就从另一个角度支持了 mixin , 具体又是怎样支持的呢? 我们举个例子来说明。

我们都知道动物有捕食的能力特性(predatism),部分动物还具有飞行的特性(flyable)。

从OOA的角度,这两个特性与具体的动物之间是 like a 的关系, 从而是 implement 来实现更合适些, 当然也不是是说 is a 的关系不存在,主要是考虑如果使用 extends 的方式来实现那么就需要多定义一个抽象类用以实现这两个抽象的能力域,这样就使得继承关系又多了一层。
那么看来就只有用 interface 的实现来设计了。具体的设计代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

interface Predatism {

void eat();
}

interface Flyable {

void fly();
}

class Eagle implements Predatism, Flyable {

@Override
public void eat() {
System.out.println("Eat...");
}

@Override
public void fly() {
System.out.println("Fly...");
}
}

看似好像是完美解决了问题,别着急,好像在一些场景下这样的实现是有问题的:

场景: 有许多动物都具有这样的两个特性,但是这两个特性的内部实现又都是一样的
痛点: 按照 Java8 之前的实现方式,要么就是抽象出一个实现了这两个特性的抽象类,其他的类都继承这个抽象类,要么就是使用实现接口的方式来一!个!一!个!填充重复的内部实现。
可见第一个方案减少了重复代码的书写,但是增加了继承的层次,从长远来看并不是好的设计;
第二个方案虽然做了解耦,但是又有重复的代码逻辑出现,违反DRY设计原则,也不是好的设计;
如果在 Java8 之前,在工程上就需要做取舍了,但是我们有其他的选择了么?有的,default method 在这个场景下使用是再合适不过了,我们看下实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Predatism {
default void eat() {
System.out.println("Eat...");
}
}

interface Flyable {
default void fly() {
System.out.println("Fly...");
}
}

class Eagle implements Predatism, Flyable {}

参考

文章目录
  1. 1. mixin到底解决的是什么问题
    1. 1.1. 可选的解决方案
  2. 2. mixins 又是什么
  3. 3. 怎么用 mixins 来解决
    1. 3.1. Python - Django中的解决方案
      1. 3.1.1. model 中的共有属性
      2. 3.1.2. 使用 mixin 需要注意的部分
    2. 3.2. mixin 与 Java 语言
  4. 4. 参考