Is-A 和 Has-A 的取舍

问题

组合和继承是允许在新建的类中放入子对象。其中“Is-A (是一个)”的关系是用继承来表达的,而“Has-A(有一个)”的关系则是用组合来表达的。
组合是显示的这样做的,而继承是隐式的做。组合一般是将现有的类型作为新类型底层实现的一部分来加以复用,在一个类中引用另一个类,而继承是拥有了父类的非私有方法。
二者是之间有何区别?或者怎样在二者之间做出选择呢?

优缺点分析

继承的优点:

  1. 子类可以重写父类的方法来方便地实现对父类的扩展。

继承的缺点:

  1. 父类的内部细节对子类是可见的。
  2. 子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
  3. 如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。

组合的优点:

  1. 当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。
  2. 当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。
  3. 当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。

组合的缺点:

  1. 容易产生过多的对象。
  2. 为了能组合多个对象,必须仔细对接口进行定义。

组合关系和继承关系相比,前者的最主要优势是不会破坏封装,在软件开发阶段,组合关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于组合关系使系统具有较好的松耦合性,因此使得系统更加容易维护。从软件构架来说,组合,耦合度比继承弱,继承是对父类方法和数据成员的兼收并蓄,而组合,可以有选择的使用某一种方法。

由此可见,组合比继承更具灵活性和稳定性,所以在设计的时候优先使用组合。只有当下列条件满足时才考虑使用继承:

  • 子类是一种特殊的类型,而不只是父类的一个角色
  • 子类的实例不需要变成另一个类的对象
  • 子类扩展,而不是覆盖或者使父类的功能失效

举例

以前的做法,要么是子类实现继承于父类的抽象方法,要么是子类实现定义的接口。这两种方法都是依赖于“实现”。现在,把易变部分抽离出来后,就由行为类来实现行为接口,而不是依赖子类实现。

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

在不涉及Interface 时,“针对接口编程” ,关键就在于多态。 针对超类型编程,执行时就可以根据实际情况执行到真正的行为,不会被绑死在超类的行为上。
“针对超类型编程”,这句话更明确地可以说成:“变量的声明类型应该是超类型的,通常是一个抽象类或者一个接口,因此,只要是这个超类型的子类,都可以作为参数传入。这就意味着声明类时无需关心以后传入什么参数。”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 定义
public interface Animal {
void makeSound();
}

class Dog implements Animal{

@Override
public void makeSound() {
System.out.println("bark.");
}
}

class Cat implements Animal{

@Override
public void makeSound() {
System.out.println("meow.");
}
}
// 调用
...
Animal a = new Dog();
a.makeSound();
...

以上的行为换句话说,就是原先需要父类定义的行为,“委托”给了别处实现。

请我喝杯咖啡吧~

支付宝
微信