EnriqueMark
设计模式与架构

组合

2026-03 简体中文

能用「有一个」描述就用组合,只有「是一个」才用继承。

我对这句话的理解是,如果彼此之间没有实际意义上的继承关系和依赖关系,而只是为了代码复用,那么直接采用组合是最好的方法。「是一个」本身指的就是有某种特征的涵盖,你需要满足这个特征,因此必须继承。子类必须和父类在任何情况下「类型等同」,这就是里氏替换原则。但「有一个」却没有这种涵盖在,因此不必要强用继承,我只是为了借用某个类里的能力就去继承了,这种继承就是没必要的,反而会导致不必要的耦合。未来如果需要修改父类,那所有的子类都可能会牵连——你根本不知道哪个子类继承了某个父类功能,然后现在被你改掉了。

具体来说,组合就是一种委托模式,我们之前说到DI,依赖注入正是组合的完美搭配。组合的委托正是「只关心结构」的体现,那就是我需要用到你的功能,但我不关心你具体怎么实现,我只知道你有这样的结构(或者可操作的方法与类型),我用了你的方法(必然规定输入和输出),用了你的类型,到此为止。你怎么实现我根本不在意,这点我「委托」给了你——而我只接受一个外部DI进来的实现。

这种思想就是 控制反转(IoC)的体现,那就是我对自己所依赖的一些东西不做关心,而交给外部来实现。我不需要在内部去完成它的具体是实现,而只关心我要用的部分——这是结构意义上的契约,我只对接口负责。因此也可以说,IoC就是面向接口编程最极致的体现,其抽象层都比面向对象还要高——对象可以是一个实现的具体类,但接口就只是结构,可以连具体实现都没有。

这里的极致体现在「控制反转」的字面意义上,那就是各个实际的业务类本身完全不关心具体什么时候实现或者用到什么东西,这是DI容器负责的。它关心的只是自己的范围,也只对自己负责,其他的全部不管——或者说,交给外部管理。就像一块块积木,积木不对自己要搭到哪里负责,一堆乐高你可以搭成任何样子。而哪个搭乐高的人就是DI容器。

软件设计里面的各种设计思想很明显的都受到工业的影响——或者说,有共通的地方。标准化、可替换化、去耦合——都是在将具体的功能打包成零件,随时可替换可组装。应该说这是两个不同领域的共同收敛,为了解决复杂的现实生产问题,无数人配合、彼此不认识,各种动态的业务场景、需求随时变,对于一个稳健性的系统来说,模块化似乎是目前可行的最佳的方向。

这背后有一个更一般的原理——复杂系统要维持可控性,必须在某个粒度上实现信息隐藏。零件不需要知道整机的设计,函数不需要知道整个系统的架构,细胞不需要知道整个生物体的意图。复杂性只能通过分层屏蔽来管理,这几乎是普遍规律。 by claude

我赞同这个看法,本质上来说,复杂系统下的各个主体正是只对自己负责——或者说它只能对自己负责,因为任何人都没有全局信息。而如果我们参考社会的话,就会知道这是一个嵌套的模块化结构。人组成组织,无数组织组成国家,无数国家组成人类社会——每一层都是一个模块,但这个模块在不同层的运作规律可能是截然不同的。但社会和软件工程有一个最大的不通,那就是后者是自上而下设计的,而社会是涌现的——但是,也不尽然。有时候我们会说shi山代码,本质上就是说,没有谁能真正的设计完美架构,一个持续运行的系统总会趋向于混沌,到最后它也会变成生长出来而非设计的产物。

恐怕连微软都不可能说自己百分百了解自己那庞大到系统到底如何精密运作,他们做的只是有问题补问题,保证整体上的一致性——但谁知道底层是不是变成了靠bug运行,只是五百个bug链正好让一切正常。这就是大型系统的复杂性——换言之,动态的场景会让一切设计失效,因为你没有全局信息,也无法预知需求。所以没有最好的设计,只有够用的设计,降低出问题的概率,但保不齐还是会出问题——剩下的,等真出问题再说,补就是了。既然大家都会出错,那我们就需要知道设计的重要性:设计了可能出错,但至少是可修复、可定位的;而不设计下的出错,到时候恐怕连问题在哪儿都找不到。