为什么要使用面向接口编程?

  按照HeadFirst设计模式的引子说起

  现在需要我们开发一个模拟鸭子的游戏,里面有各种各样的鸭子,一边在水上游,一边呱呱叫。按照OO设计思想,我们设计了一个鸭子的超类:

1
2
3
4
5
public class Duck {
public void quack() {...}
public void swim() {...}
public void display() {...}
}

  然后让各种鸭子继承这个超类:

1
2
3
4
5
public class GreenDuck extends Duck {
@Override
//绿鸭子
public void display() {...}
}
1
2
3
4
5
public class RedDuck extends Duck {
@Override
//红鸭子
public void display() {...}
}

  等等。。。

  而现在游戏策划想让你添加会飞的鸭子,如果还是按照OO思想,在超类上添加一个fly()方法,那就完蛋了,只要继承了超类的鸭子都会飞了,完全违背了当时的要求。

  我们想要赶紧去弥补这个错误,然后想到去覆盖fly(),然后什么事也不做。

1
2
3
public void fly() {
//覆盖,变成什么事也不做
}

  但是,细想一下,如果我们加入了会叫但是不会飞的橡皮鸭,不会飞不会叫的木头鸭,那么我们需要去覆盖每一个父类的方法,非常麻烦:

  • 代码会在多个子类中重复
  • 改变会牵一发而动全身,造成其他鸭子不想要的改变

  既然,继承不是一个解决办法,那么利用接口怎么样?

  我们将fly()quark()放到接口当中,控制“某些”鸭子类型的行为特点:

1
2
3
public interface Flyable {
public void fly() {}
}
1
2
3
public interface Quarkable {
public void quark() {}
}

  。。。。

  然后我们让每一个实例去实现这些接口。但是,有一个显而易见的问题出现了,如果鸭子的特性很多,我们设置的接口数量就极多,那么我们每创建一个类,就要去实现大量的接口,更为复杂。

  基本的继承和实现接口无法解决问题了,那么我们就去想有没有一种软件设计方法,让我们可以以一种对既有的代码影响最小的方式来修改软件呢?

设计原则一

  找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。同样,把会变化的封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。也就是系统某部分的改变不会影响其他部分。

  这个原则几乎是每个设计模式背后的精神所在。所有的设计模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。

  按照这个原则,我们就分开Duck类中变化和不会变化的部分。:

  把变化的和不变的分离之后,那么如何将鸭子类和鸭子行为联立在一起呢?换句话说吗,怎么去动态地实现鸭子的行为属性?

设计原则二

  针对接口编程,而不是针对实现编程

  按照这个原则,我们利用接口去代表每个行为,比如说,FlyBehavior与QuackBehavior,行为的每个实现都将实现其中的一个接口。

  这样,Duck类不会负责实现Flyable和Quackable接口,反而是我们制造一组其它的类去专门实现FlyBehavior和QuackBehavior接口,这些类为“行为”类。由行为类而不是Duck类实现行为接口。

“针对接口编程“真正的意思是“针对超类型(supertype)编程”

  可以更明确的说成变量的声明类型应该是超类型,通常是一个抽象类或者一个接口,如此,只要是具体实现此类型的类所产生的的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型。例如, 有一个抽象类Animal, 有两个实现类Dog与Cat继承了Animal:

1
2
3
4
5
6
// 针对实现编程
Dog d = new Dog();
d.bark();
// 针对接口(超类型)编程
Animal animal = new Dog();
animal.makeSound();

  如此一来,这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。并且我们可以添加新的行为,而不会对Duck类和其它接口有影响。

  面向接口编程,在代码中体现的是实例变量的类型是接口引用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Duck{
//行为变量被命名为接口类型
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display() {}
public void performQuack() {
//使用引用的对象处理
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
}

  创建quackBehavior接口

1
2
3
public Interface QuackBehavior {
public void quack();
}

  创建接口的实现类Quack

1
2
3
4
5
6
public Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("I can quack");
}
}

  创建接口的实现类Squeak

1
2
3
4
5
6
public class Squeak implements Quack {
@Override
public void quack() {
System.out.println("squeak....");
}
}

  创建flyBehavior接口

1
2
3
public Interface FlyBehavior {
public void fly() {}
}

  创建接口的实现类Fly

1
2
3
4
5
6
public class Fly() implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can fly");
}
}

  创建接口实现类FlyWithRocket

1
2
3
4
5
6
public class FlyWithRocket() implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can fly with rocket");
}
}

  创建接口的实现类FlyNoWay

1
2
3
4
5
6
public class FlyNoWay() implements FlyBehavior{
@Override
public void fly() {
System.out.println("I can not fly");
}
}

  在实现类中,实现的是抽象的方法和实例变量

1
2
3
4
5
6
7
8
9
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyBehavior();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
}

  创建另一个对象ModelDuck

1
2
3
4
5
6
7
8
9
10
11
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new QuackBehavior();
}

@Override
public void disply() {
System.out.println("I'm a model duck");
}
}

  如果想动态设定Duck实现类的属性呢? 

  在Duck类中创建设置属性的方法(为啥不直接在实现类中创建,实现复用)

1
2
3
4
5
6
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
public void setFlyBehavior(FLyBehavior fb) {
flyBehavior = fb;
}

  在调用的时候就可以动态改变实现对象的属性了

1
2
3
4
Duck modelDuck = new ModelDuck();
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyWithRocket());
modelDuck.performFly();

设计原则三

  “has-a”is better than “is-a”.多用组合,少用继承。

  当将两个类结合起来使用,如同本例一般,这就是组合,这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”而来的。

  使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。

联系我

评论