持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
HeadFirst设计模式中的例子: 开发不同种类的鸭子。(有会飞的,会叫的,不会飞的,不会叫的等等)
继承有什么缺点:
- 代码在多个子类中重复:父类的很多方法,其实在子类中并不需要,但是也要复写。
- 运行时的方法不容易改变:设计时可以通过修改或者覆写父类的方法来改变方法,但是在运行时就不好改变了。
- 很难知道子类的所有行为:对于哪些行为需要放在父类或者子类中很难确定。
- 改变会导致牵一发而动全身:父类只要改变了,子类就必须跟着改变。
利用接口代替继承呢?
- 虽然可以解决“一部分”问题,有些子类中可能不需要父类的一些方法的行为(橡皮鸭子不会飞),但是会造成代码的无法复用,实际上还是会造成“代码在多个子类中重复”的问题,而且可能会导致子类行为不一致的问题。
- 上述的其他的三个问题也没有解决。
总结: 现在我们知道继承不能很好的解决问题,因为鸭子的行为在子类中不断改变,并且让所有的子类都拥有一样的行为也是不恰当的。使用飞(fly)或者叫(Quack)的接口一开始似乎可以解决问题(只有会飞的鸭子才实现飞的接口)。但是接口不具有默认的实现,所以无法达到代码的复用。这就意味着我们如果要修改一个行为就必须追踪实现这个行为的所有接口,一个个去修改,这太不恶心了吧。
那我们应该怎么办?
软件需要不断的改变,否则它就会“死亡”。驱动它改变的因素有:
- 新功能或者新需求
- 兼容性问题
- 设计缺陷,导致的健壮性,扩展性等问题。
- 功能缺陷或者性能缺陷
- 工具或者库的重构
在改变中需要遵循的原则:
- 设计原则1: 将可变的部分取出并“封装”起来,使其与不变的部分相互独立。 -** 设计原则2**:针对接口编程,而不是针对实现编程。
-
- 用接口定义一个或者一组可变的行为,然后子类去使用接口的某个实现,而不是在父类中去做实现,或者继承接口。
-
- 接口其实指的是超类,包括抽象类,抽象类可以选择强制子类去实现行为或者提供默认实现。
如何在实践中体现呢?
- 鸭子飞和叫的行为是可变的,因此我们可以设计flyBehavior和QuackBehavior两个接口,然后通过对用的类负责具体的实现,鸭子可以选择对应动作实现。
- 这样设计既可以达到对象服用,而且这些行为都和鸭子子类独立,进行改变时也不会影响到鸭子类 思考:
- 设计系统的时候,需要考虑到可能会变化的的部分,在代码中加入一定的弹性
- Duck类不需要设计为接口,因为它继承结构中可变的部分被删掉了;而且设计成具体的类可以让子类获取共同的属性和方法。
- 用类表示行为在OO系统时也是正常的,因为行为也用状态和方法
//叫的行为接口
public interface QuackBehavior {
public void quack();
}
//具体的叫的行为
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Quack");
}
}
//飞的行为接口
public interface QuackBehavior {
public void quack();
}
//具体的飞的行为类
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying!!");
}
}
整合鸭子的行为
- 在鸭子类中加入flyBehavior和QuackBehavior两个接口的实例变量,用这个两个变量去实现飞行和呱呱叫的行为,
- 可以在鸭子类的子类构造器中直接初始化这两个变量。
- 可以通过set方法给这两个变量赋值,可以在其运行的时候改变子类的动作。
//抽象鸭子类
public abstract class Duck {
//鸭子飞的行为(接口类型的实例变量)
FlyBehavior flyBehavior;
//鸭子叫的行为(接口类型的实例变量)
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
//委托给行为类
public void performFly() {
flyBehavior.fly();
}
//委托给行为类
public void performQuack() {
quackBehavior.quack();
}
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
}
//具体的鸭子
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I'm a real duck");
}
}
封装行为的大局观:类之间的关系可以是IS-A(是一个),HAS-A(有一个)或者IMPLEMENT(实现)
设计原则3: 多用组合(HAS—A),少用继承(IS-A)
策略模式的定义:
定义算法族,分别封装起来,让他们之间相互可以替换,此模式让 算法的变化 独立于 使用算法的客户。