Android 设计模式之六大原则

如何理解设计模式的六大基本原则

设计模式是一套理论,由软件界的先辈们总结出的一套可以反复使用的经验,它可以提高代码的可重用性,增强系统的可维护性,以及解决一系列的复杂问题。在做软件的时候,需求是最难把握的,我们可以分析现有的需求,预测可能发生的变更,但是我们不能控制需求的变更。既然需求的变更是不可控的,那如何拥抱变化呢?幸运的是,设计模式给了我们指导,出现了 6 大设计原则。

这六大设计原则分别是:

  • 单一职责原则 (Single Responsibility Principle,简称SRP )
  • 里氏替换原则 (Liskov Substitution Principle,简称LSP)
  • 依赖导致原则 (Dependence Inversion Principle,简称DIP)
  • 接口隔离原则 (Interface Segregation Principle,简称ISP)
  • 迪米特原则 (Law of Demeter,简称LoD)
  • 开闭原则 (Open Close Principle,简称OCP)

单一职责原则

这是一个备受争议的原则,原因在于实际项目中使用这一原则的时候,需要考虑很多因素,如:项目工期、成本、人员技术水平、硬件情况、网络情况、甚至有时候还要考虑政府政策、垄断协议等因素。

从理论上讲,单一职责原则是要保证类的专一性,它的原话是这样的

There should never be more than one reason for a class to change.

从字面上讲就是「对于一个类的变化来说,绝对没有超过一个因素」。而实际上单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情。

同样的,方法也遵循单一职责原则,比如在修改用户信息的时候,可以设置多个方法区分别修改用户的多个信息,而不是在一个方法修改多个用户信息。

单一职责原则的好处是:

  • 类的复杂性降低;
  • 可读性提高;
  • 可维护性提高;
  • 变更引起的风险降低;

最后,对于单一职责原则的建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。

里氏替换原则

里氏替换原则比较通俗易懂的定义是:

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

所有引用基类的地方必须能透明地使用其子类的对象。

这里一共包含 4 层意思:

  • 子类必须完全实现父类的方法。如果子类不能完全实现父类的方法,需要脱离这一父类,委托其他父类实现
  • 子类可以有自己的个性。因此,子类出现的地方,父类未必可以出现。
  • 覆盖或实现父类的方法时输入参数可以被放大。这句话我觉得描述的不够准确,因此我换了一种说法,如果输入参数相同就是覆盖,输入参数可以比父类输入参数范围要大,输入参数绝对不能比父类的输入参数还要小(PS:感觉啰嗦了,但思路很清晰)
  • 覆写或实现父类的方式时输出结构可以被缩小。(原理同上)

就我个人看来,这是一个防止程序在扩展时出现错乱的原则。

依赖倒置原则

这一原则先来陈述什么是倒置:正常人的思维方式是类间的依赖是实现类中的依赖关系,人开奔驰车,那么人就直接依赖奔驰车,而程序是对现实世界的抽象,而程序中为了便于适应多变的需求,多数都是依赖于抽象,这很违背人类的思维方式,因此,此为倒置。

那什么是依赖于抽象呢?就拿刚才说的人开奔驰车来说,可以用下面的类定义

1
2
3
public class Person{
public void drive(Benz benz);
}

那么人就只能开奔驰车了。如果需求变化,比如人开宝马车,我们就需要修改 Person 类的方法,这样的程序扩展性比较低,于是程序需要依赖于抽象,

1
2
3
4
5
6
public class Person{
public void drive(ICar car);
}
class Benz implement Icar{}
class BMW implement Icar{}

这样的话,在变化出其他车来也可以让程序得心应手。

还有一点需要注意的是,依赖倒置原则在大型项目中优势突出,在一些小型项目中效果不明显,甚至还会造成程序变得复杂的现象。

接口隔离原则

这里有两个定义:

  • 客户端不应该依赖它不需要的接口
  • 类间的依赖关系应该建立在最小的接口上

接口隔离原则主要是对接口定义,同时也是对类定义,接口和类尽量使用原子接口或原子类类组装。但是,这个原子的发奋是设计模式中一大难题,在时间中可以根据以下几个规则类衡量:

  • 一个接口只服务于一个子模块或业务逻辑;
  • 通过业务逻辑压缩接口中 public 方法,避免接口臃肿;
  • 已经被污染的接口,尽量去修改,若变更的风险较大,则采用适配器模式转化处理;
  • 了解环境,拒绝盲从。看到某大神的案例,就照本宣科的照抄。这样做很危险,环境不同,接口拆分的标准就不同。深入了解业务逻辑,才能设计出最好的接口;

接口隔离原则要掌握一个「度」,接口粒度小,会增加系统的灵活度,但会增加系统的开发难度,造成提高开发成本;接口粒度大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。

那如何准确地时间接口隔离原则呢?时间、经验和领悟!

迪米特原则

迪米特原则的核心观点就是类间解耦,弱解耦,只有弱解耦以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。读者采用迪米特原则是需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。

迪米特原则要求类间解耦,但解耦是有限度的,除非是计算机的最小单元——二进制的 0 和 1。那才是完全解耦,在实际项目中,需要适度地考虑这个原则,别为了套用原则而做项目。原则知识工参考,如果违背了这个原则,项目也未必会失败,这就需要大家在采用原则时反复度量,不遵循是不对的,严格执行就是“过犹不及”。

开闭原则

在 6 个原则中,开闭原则是重中之重,是最基本的原则,是其他 5 个原则的精神领袖。我们在使用开闭原则时要注意一下几个问题:

  • 开闭原则也只是一个原则(不要过度依赖)
  • 项目章程非常重要
  • 预知变化

在项目中设计模式的使用

我在工作中使用过的设计模式是单例模式和观察者模式。

我将数据类定义成单例模式,为了减少数据的传递,将数据存储在单一实例中,避免了数据的丢失,而且能够相应数据更新

在项目中有一个业务场景,向串口写出指令,之后串口会有数据返回,在数据返回后,个业务层会去集合中寻找数据,这样的话会导致有时数据获取不正确或获取不到数据的问题。因此我采用观察者模式,当数据到来以后,通知所有业务层,确保可以让业务层即时拿到数据。

坚持原创技术分享,您的支持将鼓励我继续创作!