关于设计的一些思考

设计准则

1 单一职责原则
单一原则很简单,就是将一组相关性很高的函数、数据封装到一个类中。换句话说,一个类应该有职责单一。

2 开闭原则
开闭原则理解起来也不复杂,就是一个类应该对于扩展是开放的,但是对于修改是封闭的。我们知道,在开放的app或者是系统中,经常需要升级、维护等,这就要对原来的代码进行修改,可是修改时容易破坏原有的系统,甚至带来一些新的难以发现的BUG。因此,我们在一开始编写代码时,就应该注意尽量通过扩展的方式实现新的功能,而不是通过修改已有的代码实现。

3 里氏替换原则
里氏替换原则的定义为:所有引用基类的地方必须能透明地使用其子类对象。定义看起来很抽象,其实,很容易理解,本质上就是说,要好好利用继承和多态。简单地说,就是以父类的形式声明的变量(或形参),赋值为任何继承于这个父类的子类后不影响程序的执行。我们在抽象类设计之时就运用到了里氏替换原则。

4 依赖倒置原则
依赖倒置主要是实现解耦,使得高层次的模块不依赖于低层次模块的具体实现细节。怎么去理解它呢,我们需要知道几个关键点:

(1)高层模块不应该依赖底层模块(具体实现),二者都应该依赖其抽象(抽象类或接口)
(2)抽象不应该依赖细节
(3)细节应该依赖于抽象

其实,在我们用的Java语言中,抽象就是指接口或者抽象类,二者都是不能直接被实例化;细节就是实现类,实现接口或者继承抽象类而产生的类,就是细节。使用Java语言描述就简单了:就是各个模块之间相互传递的参数声明为抽象类型,而不是声明为具体的实现类;

5 接口隔离原则
接口隔离原则定义:类之间的依赖关系应该建立在最小的接口上。其原则是将非常庞大的、臃肿的接口拆分成更小的更具体的接口。

6 迪米特原则
描述的原则:一个对象应该对其他的对象有最少的了解。什么意思呢?就是说一个类应该对自己调用的类知道的最少。还是不懂?其实简单来说:假设类A实现了某个功能,类B需要调用类A的去执行这个功能,那么类A应该只暴露一个函数给类B,这个函数表示是实现这个功能的函数,而不是让类A把实现这个功能的所有细分的函数暴露给B。

23设计模式

我对6大原则的总结:
1.单一职责 (高内聚)
2.接口隔离 (低耦合)
3.依赖倒置 (依赖抽象)
我认为设计代码时单单做到以上3点, 写出来的代码都不会太差。 但是还可能有一些其他的问题, 比如代码复用性不高,业务对象间关系描述不够清晰等。
所以基于6大原则, 前人将一些行之有效的java设计case整理成更加具现的23设计模式,如图
关于设计的一些思考

设计模式与Java

这里需要注意的是, 我们通常讲的23种设计模式是和Java语言强相关的, 每种设计模式都会给出一或多种参考范式, 即与实现挂钩,非6大模式那种纯逻辑概念。 我认为在学习揣摩设计模式的过程里应该重意轻形。
这里有个单例模式的case,java与kotlin饿汉式单例的写法对比。
关于设计的一些思考
应该很难把 object xxx 这短短一行代码称作一种设计模式吧?( kotlin编译器将其范式隐藏在编译期,以下是decomplile字节码得到的kotlin饿汉式范式)
关于设计的一些思考
再比如观察者模式, 以下是Java中的设计范式。要在被观察者内部去提供注册观察者的接口再提供出去,看起来还是有点啰嗦的。

关于设计的一些思考
而在c#中, 语言本身就有 委托&事件 的设计, 可以说这个模式c#本身就提供给开发者。http://www.runoob.com/csharp/csharp-event.html (再说一句题外话, 个人认为c#在语言设计层面有许多亮点。比如委托, 这个设计本身比interface更轻量级, 更加符合"接口"这个定义, 如果开发者更倾向于使用委托而非接口,那么就更可能会设计出符合"最少暴露"原则的代码。)

所以一些固定的范式是可以用类似语法糖的方式隐藏或优化的, 在学习阶段理解设计的思想更为重要。一些说法比如"设计模式在一定程度上就是弥补语言的缺陷" 也是在说明这个问题。

设计模式重要吗?

如果你是一个Java程序员, 设计模式范式就是你跟同事合作生产时的一种交流方式(前提是大家都理解范式),还是最高效的交流方式之一。 比如别人写个observer那么我大概就知道这个类向外暴露各种回调,或者写一个adapter, 这就是在对接两个接口。所以对待设计模式的正确方式,我认为应该是学习时重意, 干活时用形。
除了23设计模式,分层设计也可以达到同样的效果,如mvc, mvp, mvvm 等。

但是设计模式本身就是完美的吗? 它就没有缺陷吗? 当然不是。 前面也说了, 设计模式只是前人对一些行之有效的设计case的总结, 而设计模式的本质, 我认为是"针对某些具体场景提供了一些效率较高的以代码复杂度换灵活性的手段"。所以它的第一个问题在于代码复杂度。

不要过度设计

很多时候我去看一些优秀开源库的实现,会发现要在源码流程里面跳很久才能找到我想找的那个功能节点的实现,中间还会经过很多抽象接口类型的代码跳转,看的很费劲。
为什么会设计的这么复杂呢?因为作者为那个功能节点提供了很多的灵活性/扩展性的设计。所以代码复杂度高又会带来额外的成本 (这个成本包括 学习api&理解作者的设计理念&可能带来的额外调试成本&可能带来的额外维护成本) 。
大部分的设计模式追求扩展性,也就是所谓的"依赖倒置"。那么为什么软件要追求扩展性呢? 我认为你主要有两点:

  1. 方便后续迭代,新的业务只需要按照规定好的接口类型实现即可, 不需要去更改以前的业务流程。 所以扩展性的设计也可以说是面向未来设计。那么这就又带来一个问题, 如果"未来没来",会怎么样?
  2. 方便改代码,(这里的非狭义pm的需求变更,指功能的扩展方向变化) 结合了大量的设计模式,设计出n种接口类型,增加了n层的代码复杂度, 最后只上了一个简单的业务, 没享受到设计模式的好处反受其害。
    这就是过度设计(过度设计本质上就是你为可能发生的变动付出了过多的复杂度代价)。所以不要在业务前景不清晰的情况下过早的追求极致的灵活性, 更熟悉业务才能做出更加符合当前业务场景的设计。

小结

设计模式是好东西,但也只是好东西,不要一味的追求完美,实际上能快速完成当前的功能,又能很好的扩展的才是一个好的设计方案。
代码只是工具,设计模式只是实现方案的结构选型,好的产品规划才是最重要的。