在进行类设计时,更多时候我们习惯于针对属性和方法来设计,我们会先找出类的共有属性和方法,然后用子类来扩展和区分不同类型的属性和 行为。比如一款飞车游戏,有不同的车,我们的第一反应会是建立一个 Car 的超类,然后定义车的共有属性,最后使用子类继承扩展出不同类型的车,代码的如下
public class Car {
String name; //名字颜色
Integer speed; //速度
String driveType; //驱动方式
//如何移动
public void move(){
}
}
class Truck extends Car{
public Truck() {
}
public Truck(Integer speed, String driveType){
this.speed = speed;
this.driveType = driveType;
}
@Override
public void move() {
System.out.println("卡车的驱动方式是" + driveType + "以" + speed + "速度行驶");
}
}
一般来说这种设计并没有问题,不同车可以根据需要定义不同的行驶方式。但是车的类型会不断的增多,与行驶相关的属性会变多, move方法也可能需要更多的信息。
比如后期游戏为了更加符合现实,需要添加每公里耗油量。按照以上的设计,就需要在Car 类中添加 耗油量属性, 然后每个已经实现的子类都必须要修改move方法,添加上耗油量。
在上面的情境下,使用继承扩展车的类型显然很麻烦,使用接口也会面对一样的问题,一旦move 方法需要添加新的属性,所有的已经实现类必须要修改源码。为了解决这种扩展性差的问题,我们可以把可能变化的部分封装起来。
//将行驶方法设计成接口
public interface Moveable {
void move();
}
//每个车具体的行驶方式和设计的属性使用具体的类定义
public class TrunkMoveable implements Moveable{
Integer speed; //速度
String driveType; //驱动方式
public TrunkMoveable(Integer speed, String driveType) {
this.speed = speed;
this.driveType = driveType;
}
@Override
public void move() {
System.out.println("卡车的驱动方式是" + driveType + "以" + speed + "速度行驶");
}
}
public class Car {
String name; //名字颜色
//行驶方动态的传递进来
public void move(Moveable moveable){
moveable.move();
}
}
class Truck extends Car{
}
以上就是将类中会变化的部分封装起来,Car 的 move 方法接收一个 Moveable 类,在运行时根据需要传递进去
这种设计方式有几个好处
1、无论以后行驶方式如何变化,我们的关注点只是 TrunkMoveable 这种具体实现类
2、当move 方式需要变化时,我们可以不必修改源码,可以重新写一个Moveable 实现类,使用时传递新的实现类即可
3、Moveable还可以设计一个超类实现,Moveable 相关属性可以放在超类中,代码更简单,处理一起统一修改也更方便
当然这种设计也有坏处
1、类的数量比原来多了一倍,后期如果有修改,同一类型的 Moveable 会有多个实现,引起类爆炸,管理起来比较麻烦
2、将行驶方式定义成一个类比较别扭
3、车的部分会被分散,与普通设计不同,理解成本更高