前言

本文学习自极客的课程——设计模式之美。

看着课程的理论篇觉得很虚,想总结一些例子出来都无从下笔,但是都看了一遍之后,感觉大有所获,原来都是日常开发时的事啊,而且我还踩了很多坑,汗......

现在先记录一些能摸得着的理论,以后开发时有新的想法再来补充。

【SRP】单一职责原则

单一职责原则:Single Responsibility Principle。

A class of module should have a single responsibility.

一个类或模块只负责一个职责。

可以有两种理解:把模块看成比类更抽象的概念,类也可以看作模块;把模块看作比类更粗粒度的代码块,模块中包含多个类,多个类组成一个模块。

简单地说,就是不要设计大而全的类,而是要设计粒度小,功能单一的类

如果一个类包含了两个或两个以上业务不相干的功能,我们就说它职责不够单一,应该所它拆分成多个功能单一、粒度更细的类。

所以平常开发时,我们可以先写一个粗粒度的类,满足业务需求。随着业务发展,如果粗粒度的类起来越庞大,代码越来越多,这时我们可以把这个类拆分成几个更细粒度的类

怎样判断职责单一 ?

上面说的话就很模糊,需要一些更有指导意义,更可行的方式:

  • 类中代码、行数、属性过多,影响代码的可读性和可维护性,可以考虑拆分
  • 类依赖其它类过多,或依赖类的其它类过多,不符合高内聚、低耦合的设计思想
  • 私有方法过多,可以考虑能否把它拆分到别的类,设置成public,让别的类使用
  • 起名困难,很难用一个业务名词概括,只有一些笼统的词命名,可能类的职责定义不清。
  • 类中大量方法都是集中操作类中的某几个属性,可以考虑把它们分出来。

当然,上面的话也有一些模糊的地方,比如什么叫做代码过多,300行过多吗?ummmmm当你开发时想翻看,而又有些别扭(?)的时候,感受到不舒服了,这应该就是有问题了,得改了。

【OCP】开闭原则

开闭原则:Open Closed Principle

software entities(modules, classes, functions, etc.) should be open for extension, but closed for modification

软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”

添加一个新功能应该是在已有代码的基础上进行扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)

就是需要改的东西很少,就能实现一个新的功能。文章里面举了个例子,为一个API接口监控代码扩展一个功能。那按我以前的想法,直接在函数上改,请求加个参数,具体执行的逻辑加一段。。。。

但是例子里面却把参数封装成对象,以后要加参数的话可以在对象里面改;然后把执行的逻辑改成handler,每次都执行这些handler,以后需要改的时候再加,这样就满足了。

【LSP】里式替换原则

里式替换原则:Liskov Substitution Principle

If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。

子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破 坏。

其实和多态有点像,不过多态是面向对象编程的一大特性,也是Java的一种语言特性,是代码实现的思路。而里式替换是一种设计原则,用来指导继承关系中子类如何去设计,子类的设计要保证在替换父类时,不改变原有程序的逻辑和不破坏原有程序的正确性。

怎样做会违反里式替换原则?

  1. 子类违背父类声明要实现的功能。

    假如父类提供一个 sortOrdersByAmount() 订单排序函数,按金额从小到大排序,而子类重写后,是按照创建日期排序的,这就不对。

  2. 子类违背父类对输入、输出、异常的约定

    在父类中约定,运行出错返回null,获取数据为空返回空集合,但是子类重载函数后,出错返回异常,获取不到数据返回空。

  3. 子类违背父类注释中所罗列的任何特殊说明

    做了父类所限制的事情

【ISP】接口隔离原则

接口隔离原则:Interface Segregation Principle

Clients should not be forced to depend upon interfaces that they do not use。

客户端不应该强迫依赖 它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

接口可以理解为:

一组API接口集合

比如,有一个用户操作相关的UserService,可以执行注册、登录、获取信息等功能。现在有一个删除用户的功能需要实现。这个看着很简单啊,直接加上去就是,但是有、隐患,因为这是一个需要慎重的接口,不希望被其它业务系统调用到。

从代码设计的层面看,我们可以把这类需要注意的接口放到另一个类中实现,这样就能隔离。

把“接口”理解为单个 API 接口或函数

函数的设计要功能单一,不要将多个不同的功能逻辑在一个函数中实现。

#### 把“接口”理解为 OOP 中的接口概念

接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

【KISS】KISS原则

Keep It Simple and Stupid.

Keep It Short and Simple.

Keep It Simple and Straightforward

这条原则只是告诉我们,要保持代码“Simple and Stupid”,但怎样才是呢?

  • 不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编 程语言中过于高级的语法等。
  • 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出 bug 的概率会更高,维护的成本也比较高。
  • 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件 语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。

【YAGNI】YAGNI原则

You Ain’t Gonna Need It

你不会需要它.

不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。

(我好像犯了这个错误。。。。)

比如,我们的系统暂时只用 Redis 存储配置信息,以后可能会用到 ZooKeeper。根据 YAGNI 原则,在未用到 ZooKeeper 之前,我们没必要提前编写这部分代码。当

再比如,我们不要在项目中提前引入不需要依赖的开发包。

【LOD】迪米特法则

迪米特法则:Law of Demeter

也叫最小知识原则:The Least Knowledge Principle

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.

每个模块(unit)只应该了解那些与它关系密切的模块(units: only units “closely” related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己 的朋友“说话”(talk),不和陌生人“说话”(talk)。

与“高内聚,松耦合”的设计思想相辅相成。

高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。

而松耦合是说,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一 个类的代码改动不会或者很少导致依赖类的代码改动。