复合优于继承

继承打破了封装性(子类依赖父类中特定功能的实现细节)

合理的使用继承的情况:

  • 在包内使用
  • 父类专门为继承为设计,并且有很好的文档说明,存在is-a关系

只有当子类真正是父类的子类型时,才适合用继承。

对于两个类A和B,只有两者之间存在"is-a"关系,类B才能拓展类A。

继承机制会把父类API中的所有缺陷传播到子类中,而复合允许设计新的API来隐藏这些缺陷。

复合(composition):不扩展现有的类,而是在新的类中增加一个私有域,引用现有类的一个实例。

转发(fowarding):新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回结果。

 public class FowardSet<E> implements Set<E> {   #转发类,被装饰类

     //引用现有类的实例,增加私有域
private final Set<E> set; public FowardSet(Set<E> set){
this.set = set;
} /*
*转发方法
*/
@Override
public int size() {
return set.size();
} @Override
public boolean isEmpty() {
return set.isEmpty();
} @Override
public boolean contains(Object o) {
return set.contains(o);
} @NotNull
@Override
public Iterator<E> iterator() {
return set.iterator();
} @NotNull
@Override
public Object[] toArray() {
return set.toArray();
} @NotNull
@Override
public <T> T[] toArray(T[] a) {
return set.toArray(a);
} @Override
public boolean add(E e) {
return set.add(e);
} @Override
public boolean remove(Object o) {
return set.remove(o);
} @Override
public boolean containsAll(Collection<?> c) {
return set.containsAll(c);
} @Override
public boolean addAll(Collection<? extends E> c) {
return set.addAll(c);
} @Override
public boolean retainAll(Collection<?> c) {
return set.retainAll(c);
} @Override
public boolean removeAll(Collection<?> c) {
return set.removeAll(c);
} @Override
public void clear() {
set.clear();
} @Override
public boolean equals(Object obj) {
return set.equals(obj);
} @Override
public String toString() {
return set.toString();
} @Override
public int hashCode() {
return set.hashCode();
}
} /*
* 包装类(wrapper class),采用装饰者模式
*/
public class InstrumentedSet<E> extends FowardSet<E> {
private int addCount=0; public InstrumentedSet(Set<E> set) {
super(set);
} @Override
public boolean add(E e) {
addCount++;
return super.add(e);
} @Override
public boolean addAll(Collection<? extends E> c) {
addCount+=c.size();
return super.addAll(c);
} public int getAddCount() {
return addCount;
}
}

上面的例子中,FowardingSet是转发类,也是被包装类,而InstrumentedSet是包装类,它采用的是装饰者模式,而不是委托模式。

装饰者模式:

装饰者模式挺像一种组合、而且是可以任意搭配、制定的。当我们有新的需求的时候、添加一个装饰器就ok。必要的时候可以添加组件、这样就实现了不用修改现有代码就可以扩展和修改新的功能的一个目的。还是那个设计原则——open for extension, close for modification.

动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。

尽量使功能独立拆分解耦,每种新的功能分离开来称为一个装饰者,当需要的时候,就可以组合在一起使用,而不是像单独使用继承那样将多个功能耦合在一起,阅读和使用不方便,并且不利用扩展,比如 有接口A 有功能1 2 3 4 5 如果单单使用继承,那么为了使结构符合面向对象编程,将会组合成10个子类,当功能进一步扩展的时候,数量时恐怖的,并且将是很难被使用者记忆和理解的。当我们使用了装饰者模式之后,仅仅需要实现数个装饰者,然后根据需要进行组合使用就可以了。

包装类不适合用在回调框架(callback framework)中,会出现SELF问题

在回调框架中,对象把自身的引用传递给其他对象,用于后续的调用(回调)

SELF问题:被包装的对象并不知道它外面的包装对象,所以它传递一个指向自身的引用(this),回调时却避开了外面的包装对象。

简而言之,继承的功能非常强大,但也存在诸多问题,因为违背了封装原则。只有当子类和超类确实存在子类型关系时,使用继承才是恰当的,但如果子类和超类在不同包中,并且超类并不是为了继承而设计的,那么继承会导致脆弱性,为了避免这种脆弱性,可以用符合和转发机制来代替继承,尤其是当存在适当的接口实现包装类的时候。包装类不仅比子类更加健壮,而且功能更加强大。

如何从继承和复合之间做出选择?
比较抽象的说法是,只有子类和父类确实存在"is-a"关系的时候使用继承,否则使用复合。
或者比较实际点的说法是,如果子类只需要实现超类的部分行为,则考虑使用复合。

最新文章

  1. linux(六)__进程与任务控制
  2. sqlserver text/ntext 字段读取
  3. div在不固定高度的情况下垂直或者水平居中
  4. poj1733(种类并查集+离散化)
  5. Xamarin Studio支持TypeScript开发
  6. CSS 元素垂直居中的 6种方法
  7. 17、手势(Gesture)
  8. 解决JSP中,类无法被编译的问题(XX cannot be resolved to a type)
  9. Android 下得到 未安装APK包含信息 等
  10. Tree--RedBlackTree详解(2 - 3 - 4Tree)(红黑树)
  11. 关于win7+VS2017环境下的opencv-contirb配置的一个坑
  12. Mysql 源码:关于innodb中两次写的探索
  13. context-param和init-param的区别
  14. document.write的用处!
  15. jeecg框架解决跨域问题
  16. LOJ2613 NOIP2013 华容道 【最短路】*
  17. Dubbo限制大数据传输的解决方案
  18. iis支持asp.net4.0的注册命令使用方法及部署网站注意事项
  19. Iview 表单提交: Cannot read property &#39;validate&#39; of undefined
  20. android系统各种音量的获取与设置

热门文章

  1. koa01
  2. spring boot actuator服务监控与管理
  3. C#如何实现大小写转换
  4. Robotutor Scratch3.0 在线编程平台升级啦!
  5. svn 追责神器 blame vscode - SVN Gutter
  6. 【分布式锁】03-使用Redisson实现RedLock原理
  7. WPF 启动缓慢问题
  8. Journal of Proteomics Research | 自动的、可重复的免疫多肽数据分析流程MHCquant
  9. 关于Quartz .NET(V3.0.7)的简要说明
  10. kerberos系列之hive认证配置