继承还是聚合,设计模式的取舍?

1 分钟读完

从JDK的一个经典bug说起继承(inherit)的不足

JDK实现了观察者设计模式,

  • java.util.Observable:可被观察的对象,主要函数:
    • addObserver()/deleteObserver():增删观察者
    • notifyObservers():通知观察者数据变动
  • java.util.Observer:观察调用的callback,函数:
    • update(Observable observable, Object obj)

假设我要做一个UI类,在内部资源内容发生变动时候,需要通知别的模块,观察者模式是最好的选择。
我们期望的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MgrView extends View {
    private Observable monitor = new Observable();
    
    public Mgr(Observer callback){
        monitor.addObserver(callback);
    }

    public void do1(Object res){
        // ... 处理业务 ...
        
        // 处理完了后发送通知 
        monitor.update(res);
    }
}

但是我们在查看JDK自带的包,发现了一个问题:
Observable中的关键代码:控制Observable是否启用的#setChanged()/#clearChanged(),可见性是protected
因此要使用这个类,只能继承

从概念上说:我的类的基础特性是UI View;观察者模式只是它的一个分支特性,不是根本特性。
因此,继承Observable就是一个错误的方式。

但是,JDK的观察者模式默认了是继承模式。这个给我们的使用带来了很大的麻烦。是一个完全错误的考虑。

从技术层面来看,它后续还不能把那个protected升级为public,因为这样的话,现有的子类如果有protected @Override这个方法的,就会出错了。

简单结论:何时适合做继承或者组合

从上面的例子延伸考虑:

  • UIView是我们的组件的主要特性,组件“是一个(IsA)”UIView
  • 组件还有可观察的次要属性,组件“有一个(HasA)”Observable

把一个HasA的特性变成了IsA,就会让组件多出来很多莫名其妙的功能。

这个是最简单也是最基础的分辨。但是有时候可以模糊执行:
如果没有IsA,而且这个需求没有后续发展,HasA可以用IsA来冒充

例如:要做一个临时的小工具,这个工具不是基于别的工具的基础上来做,而有“可观察”特性。这个时候,就可以直接继承Observable,也告诉使用的人,里面其他的函数别管了。

从SpringBoot的一个例子

SpringBoot里面可以很方便的配置对redis的集成,但是美中不足的是:
如果我们要同时访问多个redis,那就麻烦了

扩展的方式在别的地方有写,这里只是讨论这个设计的得失:

一个应用,应该是有一个多个redis数据源,而不是只有一个。在spring-boot的设计中,很明显的感觉到,他把数据源的集成做成了IsA,而不是HasA。

留下评论