前言
之前我们讲了对象的继承,并且有一个设计原则就是尽量避免复杂的继承关系,否则会降低代码可读性。并且会导致类的数量膨胀,最后无法收拾。
对于这种情况,我们简单举一个例子:
问题,有一个Bird类,他的子类有很多,比如鸵鸟(ostrich),乌鸦(crow)等等。他们分为会飞和不会飞,所以在抽象父类 Bird的基础上我们还要再实现两个抽象父类,会飞的鸟和不会飞的鸟。类图如下:
如果仅仅是这样,还不复杂,但是如果还有其他区分,比如会叫的鸟和不会叫的鸟,会迁徙和不会迁徙的鸟,这个时候就会有很多的组合,比如会飞,会叫,会迁徙的鸟;会飞,会叫,不会迁徙的鸟;。。。 类图如下:
可以看到这里类出现了指数级别的增长,每增加一种行为,类的数量就会增加一倍,所以复杂的继承会造成灾难性的后果。
解决
对于上面继承的问题,有人可能会说,可不可以用接口的方式实现呢。当然可以,这样我们就只需要实现两个接口,FlyI和Migrate 。然后对应的子类实现这个接口就可以解决问题。
public interface FlyI {
void fly();
}
public interface MigrateI {
void migrate();
}
public class Pigeon extends AbstarctBird implements FlyI, MigrateI {
@override
public void fly(){
System.out.println("fly");
}
@override
public void migrate(){
System.out.println("migrate");
}
}
但是这里还有一个问题就是代码的复用性,比如会飞的鸟类,有鸽子,老鹰,麻雀,黄鹂等等,这些子类都有实现一遍Fly接口的fly方法,就造成了代码的巨大重复,不符合Java代码的设计规范。那么怎么解决这个问题,那就是组合。
组合
组合顾名思义就是一些行为的按需求放在一起。上面问题实现如下:
// 飞能力类
public class Fly {
public void fly(){
System.out.println("fly");
}
}
// 迁徙能力类
public class Migrate {
public void migrate(){
System.out.println("migrate")
}
}
// 使用组合的实现
public class Pigeon extends AbstarctBird {
Fly fly = new Fly();
Migrate migrate = new Migrate();
public void fly(){
fly.fly();
}
public void migrate(){
migrate.migrate();
}
}
可以看到使用组合之后轻松解决了上面的问题,即没有出现类的指数级增长,也没有出现代码的重复,代码的可读性也很高。
原则
多组合,少继承。