多重继承?
多年来,在Java出现后,当世界被C++主导,而我是一个真正的C++粉丝(是的,我承认),在2000年代左右的许多编程书籍/手册中,你可能会遇到这句话。
如果你的项目需要多重继承,那么使用C++。
当然,Java被创建时带有接口,以便能够实现真正重要的多重继承,其余情况实际上都是糟糕设计的产物。我当时并没有意识到这一点,但是…那个评论让我皱起了眉头。什么叫做只在需要多重继承的情况下使用?总是使用C++!
当然,我从中恢复了过来。我学会了欣赏Java,开始接触Python,几个月后…突然…C++对我来说变得陈旧而繁琐。如今,在我看来,它不仅仍然带有那种陈旧的味道(不必要的复杂性…),而且,随着时间的推移,那个关于C++的笑话:
C++是一只由狗改造而成的章鱼,被粘上了四条触手。
比以往任何时候都更加贴切。事实上,我认为很少有程序员能了解这门语言的所有细节(也许Raymond Chen和那些参与ISO C++标准工作的人除外)。
但好吧,让我们放下C++,专注于多重继承,这才是重点。
例如,我们可以有一个Triatleta(铁人三项运动员)类。铁人三项运动员游泳、骑自行车和跑步,所以第一个尝试可能是使用多重继承。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
#include <iostream>
class Zapatillas {};
class Neopreno {};
class Bicicleta {};
class GafasCiclista {
public:
std::string get_nombre() const
{ return "gafas de ciclista"; }
};
class GafasNadador {
public:
std::string get_nombre() const
{ return "gafas de nadar"; }
};
class Corredor {
public:
Corredor(const Zapatillas &z):
zapatillas(z)
{}
void corre();
void ata();
const Zapatillas &get_equipacion() const
{ return zapatillas; }
private:
Zapatillas zapatillas;
};
class Nadador {
public:
Nadador(const Neopreno &n, const GafasNadador &g):
neopreno(n), gafas(g)
{}
void nada();
void ponte_traje();
void quita_traje();
const Neopreno &get_equipacion() const
{ return neopreno; }
const GafasNadador &get_gafas() const
{ return gafas; }
private:
Neopreno neopreno;
GafasNadador gafas;
};
class Ciclista {
public:
Ciclista(const Bicicleta &b, const GafasCiclista &g):
bicicleta(b), gafas(g)
{}
void pedalea() const;
void monta() const;
const Bicicleta &get_equipacion() const
{ return bicicleta; }
const GafasCiclista &get_gafas() const
{ return gafas; }
private:
Bicicleta bicicleta;
GafasCiclista gafas;
};
class Triatleta:
public Corredor,
public Nadador,
public Ciclista
{
public:
Triatleta(const Zapatillas &z,
const Neopreno &n,
const Bicicleta &b,
const GafasNadador &gn,
const GafasCiclista &gc):
Corredor(z),
Nadador(n, gn),
Ciclista(b, gc)
{
}
void compite()
{
ponte_traje();
nada();
quita_traje();
monta();
pedalea();
ata();
corre();
}
};
int main() {
GafasNadador gn;
GafasCiclista gc;
Neopreno n;
Zapatillas z;
Bicicleta b;
Triatleta t(z, n, b, gn, gc);
std::cout << t.get_gafas().get_nombre() << std::endl;
return 0;
}
|
…然后,一切都崩溃了。什么叫做get_gafas()调用不明确?我只是想显示眼镜的特性!让我们仔细看看Corredor、Nadador和Ciclista类。实际上,Ciclista和Nadador有它们自己的GafasNadador和GafasCiclista对象。问题是方法名称相同:get_gafas()。我们能做什么?
好吧,这只是个小麻烦。毕竟,方法可以重命名。另一方面,C++允许限定方法所属的类。说做就做。
1
2
3
4
5
6
7
8
9
10
11
12
|
int main() {
GafasNadador gn;
GafasCiclista gc;
Neopreno n;
Zapatillas z;
Bicicleta b;
Triatleta t(z, n, b, gn, gc);
std::cout << t.Nadador::get_gafas().get_nombre() << std::endl;
std::cout << t.Ciclista::get_gafas().get_nombre() << std::endl;
return 0;
}
|
…但这看起来像是个临时解决方案…
从设计的角度来看。这个解决方案正确吗?好吧,一个铁人三项运动员无疑是一个跑步者,一个游泳者,和一个骑自行车的人。从这个角度来看无可指责。但让我们继续完善。它符合Liskov替换原则吗?一个铁人三项运动员能否在没有变化且有意义的情况下,在与我们使用跑步者、游泳者或骑自行车者相同的情况下使用?实际上不能。想想眼镜!
最终,确实,这些出现的问题源于有问题的设计。实际上,除了继承多个没有属性的抽象类之外,永远不应该使用多重继承。也就是说,在Java中,这将被实现为接口。
那么,如果我们使用组合会怎样呢?让我们看看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class Triatleta {
public:
Triatleta(const Zapatillas &z,
const Neopreno &n,
const Bicicleta &b,
const GafasNadador &gn,
const GafasCiclista &gc):
corredor(z),
nadador(n, gn),
ciclista(b, gc)
{}
void compite()
{
nadador.ponte_traje();
nadador.nada();
nadador.quita_traje();
ciclista.monta();
ciclista.pedalea();
corredor.ata();
corredor.corre();
}
private:
Corredor corredor;
Nadador nadador;
Ciclista ciclista;
};
|
有些问题我们不能直接解决。现在没有get_gafas()方法,实际上,我们必须为每种类型创建一个适当的方法。也就是说,所选择的设计已经引导我们走向正确的道路,正如我们在下面看到的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class Triatleta {
public:
Triatleta(const Zapatillas &z,
const Neopreno &n,
const Bicicleta &b,
const GafasNadador &gn,
const GafasCiclista &gc):
corredor(z),
nadador(n, gn),
ciclista(b, gc)
{}
void compite()
{
nadador.ponte_traje();
nadador.nada();
nadador.quita_traje();
ciclista.monta();
ciclista.pedalea();
corredor.ata();
corredor.corre();
}
const GafasNadador& get_gafas_nadador() const
{ return nadador.get_gafas(); }
const GafasCiclista& get_gafas_ciclista() const
{ return ciclista.get_gafas(); }
private:
Corredor corredor;
Nadador nadador;
Ciclista ciclista;
};
|
此外,现在代码是自文档化的:毫无疑问我们调用的是哪个运动员的哪个方法。并且调用get_gafas()时的歧义已经消失。
1
2
3
4
5
6
7
8
9
10
11
12
|
int main() {
GafasNadador gn;
GafasCiclista gc;
Neopreno n;
Zapatillas z;
Bicicleta b;
Triatleta t(z, n, b, gn, gc);
std::cout << t.get_gafas_nadador().get_nombre() << std::endl;
std::cout << t.get_gafas_ciclista().get_nombre() << std::endl;
return 0;
}
|
最好尽可能远离多重继承。而且,最好远离继承本身。太多的代价。