在进行类设计时,更多时候我们习惯于针对属性和方法来设计,我们会先找出类的共有属性和方法,然后用子类来扩展和区分不同类型的属性和 行为。比如一款飞车游戏,有不同的车,我们的第一反应会是建立一个 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、车的部分会被分散,与普通设计不同,理解成本更高