写点什么

面向对象设计原则

用户头像
引花眠
关注
发布于: 2021 年 02 月 16 日

设计原则

虽然说编成范式提供了一些可用设计理念,但是相对来讲是不好落地的,那么相比于编成范式的不可操作性,设计原则的可操作性就比较强了。 对于面向对象的设计原则,其中有一套成体系的原则叫做 SOLID 原则,它不仅可以指导我们如何设计类,还可以指导我们学习框架,学习设计模式等。


那 SOLID 原则是什么呢?它实际上是五个设计原则首字母的缩写,它们分别是:


  1. 单一职责原则(Single responsibility principle,SRP)

  2. 开放封闭原则(Open–closed principle,OCP)

  3. Liskov 替换原则(Liskov substitution principle,LSP)

  4. 接口隔离原则(Interface segregation principle,ISP)

  5. 依赖倒置原则(Dependency inversion principle,DIP)


这些设计原则是由 Robert Martin 提出并逐步整理和完善的。他在《敏捷软件开发:原则、实践与模式》中,对 SOLID 原则进行比较完整的阐述。


单一职责原则

核心思想: 一个类或模块只应该有一个引起变化的原因或者说一个软件模块只为同一类行为者负责


只为同一类行为者负责,可能不太好理解,比如对于大部分系统都会有的用户类来说,其可能的类如下:


public class UserInfo {  private long userId;  private String username;  private String email;  private String telephone;  private long createTime;  private long lastLoginTime;  private String avatarUrl;  private String provinceOfAddress; // 省  private String cityOfAddress; // 市  private String regionOfAddress; // 区  private String detailedAddress; // 详细地址  // ...省略其他属性和方法...}
复制代码


那么如何判断其职责是否单一呢,如果这个用户信息是在一个管理系统中,那么它就是职责单一的;但是如果这是在一个电商系统中,那么地址信息就应该拆分出来,放到单独的类中去处理,因为地址信息可能会被订单、物流等模块使用,该类的职责就不是单一的。


所以使用单一职责的重点就是职责的划分,也就是是分离不同的关注点,不同的业务对于同一类事物可能有不同的关注点,或者可以参考领域驱动设计中的相关概念来区分不同的关注点(很可能你认为的同一类事物只是恰好有了相同的名字而已)。


如何使用单一职责原则:


  1. 不要过度设计,先设计一个比较粗的类(满足业务需求),等业务发展起来了,再考虑如何将其拆分成几个细粒度的类


什么时候应该拆分呢:


  1. 当不好给一个类起名字的时候,可能它的职责就是太复杂了

  2. 当类中大量的方法都是集中操作类中的某几个属性,比如,在 UserInfo 例子中,如果一半的方法都是在操作 address 信息,那就可以考虑将这几个属性和对应的方法拆分出来

  3. 类中的代码过多,影响阅读性和可维护性

  4. 和这个类有依赖关系的类过多时,就需要考虑对其进行拆分了。


开闭原则

核心思想: Software entities(classes、modules、functions、etc.)should be open for extension,but closed for modification. 软件实体(如类、模块、函数等)应该对拓展开放,对修改关闭。


这里所谓的对修改关闭,指的是不需要修改已有的代码,就可以增加新的功能,而不是一点代码都不动。 还有这一条原则是在同一个层次存在的,而不是说的不同的代码,可能对某一部分是扩展,但是对于整体就是修改。


对于一个软件系统而言,需求肯定是会变的,如果没有变动,那么软件就死了。


开闭原则是一个非常有用的原则,大部分的设计模式都是为了解决代码的扩展性而存在的,就是开闭原则在其作用。


那么如何在开发软件的时候遵循开闭原则呢: 在写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩展开放、对修改关闭”。 但是也要注意不要过度设计,这样很可能成本上无法接受。


开源软件中的扩展点:


  1. Mybatis 中的插件机制

  2. Spring 中的过滤器、监听器

  3. Servlet 中的 Filter

  4. netty 中的 Handler 等


Liskov 替换原则

里氏替换原则:


functions that use pointers to base classes must be able to use objects of derived classes without knowing it. 所有能引用基类的地方必须能透明地使用其子类的对象。


这一原则其实说的是如何设计类的继承体系,设计子类的时候需要满足父类对功能的约束,子类可以改变实现,但是不能改变原来函数的行为,这些行为包括:


  1. 函数声明要实现的功能;

  2. 对输入、输出、异常的约定 比如:子类对输入的数据的校验比父类更加严格,那子类的设计就违背了里式替换原则。

  3. 甚至包括注释中所罗列的任何特殊说明 比如:Collection 接口与 List 接口关于 add 的描述就不同

  4. 。。。


比如非常经典的长方形与正方形问题:



class Rectangle { private int height; private int width;
// 设置长度 public void setHeight(int height) { this.height = height; }
// 设置宽度 public void setWidth(int width) { this.width = width; }
// public void area() { return this.height * this.width; }}
class Square extends Rectangle { // 设置边长 public void setSide(int side) { this.setHeight(side); this.setWidth(side); }
@Override public void setHeight(int height) { this.setSide(height); }
@Override public void setWidth(int width) { this.setSide(width); }}
复制代码


当我需要的是长方形时,它的行为就是长宽可以单独变换,那么如果我把正方形传入就不满足长这一特定。


还有比如如果我们规定鸟都是会飞的,那么企鹅就不满足这一行为,所以它是鸟的子类。


所以要满足 LSP


  1. 首先这个对象体系要有一个统一的接口,而不能各行其是

  2. 子类要满足 IS-A 的关系(基于行为)。


接口隔离原则

核心思想: 不应强迫使用者依赖于它们不用的方法。No client should be forced to depend on methods it does not use.


这一原则是用来设计或衡量一个接口是不是过胖,如果一个接口中的方法能够很轻易的分为几组,每组都是单独使用的,那么这个接口就是胖接口了。 需要对接口进行瘦身,到达什么样的标准呢,就是对于使用该接口的客户来说:


  1. 不要提供我不需要的功能 万一客户端错误调用怎么办

  2. 提供所有我需要的功能 否则还需要去找额外的支持


这里所说的接口不单单说的是一种语法,而是指提供的功能,比如每个具体类提供的公开方法就是它的接口。 这里所谓的接口隔离,其实隔离的是使用者对接口的使用,每个接口代表的只是一种角色。 这就像每个人在实际生活中扮演着不同的角色一样。在家里,我们是父母的子女;在公司里,我们是公司的员工;购物时,我们是顾客;出行时,我们是乘客,但所有这些角色最终都是由我们一个人承担的。


在实际的使用过程中,我们需要


  1. 识别对象的不同角色,设计小接口,但是要有限度。一个接口只服务于一个子模块或业务逻辑。

  2. 为依赖接口的类定制服务 接口属于客户的,不属于接口所在的层次。

依赖倒置原则

第一次听说依赖倒置的时候时候,最不理解的就是“倒置”,后来想如果有倒置,那么肯定有正置,只有找到什么是正置,才能明确什么是倒置。 在《敏捷软件开发-原则、模式与实践》 中对于正置,有简单的描述:高层模块依赖于底层的细节,或者说依赖于具体的实现。那么倒置就是与其相反,其具体原则就是:


  1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象;

  2. 抽象不应该依赖细节。细节应该依赖抽象。


如果用 Java 语言描述的话就是:


  1. 模块之间的依赖是通过抽象(接口或抽象类)发生的,具体类之间不相互依赖

  2. 接口或抽象类不依赖于具体类。具体类依赖于接口或抽象类。


“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。 ——百度百科


由此可以看出,好莱坞原则强调的是高层对于底层主动调用,而不需要底层向高层自我推荐(好像有点惨),高层只关心它需要的接口(这些接口于高层属于同一层),而底层实现高层需要的接口,对于底层的变化高层也不关心(每个演员演同一部戏,细节可能不一样,但是整体效果都一样;演员随时可以换,戏不换。)。


从这两个描述可以看出“依赖倒置原则”与“好莱坞原则”强调的都是高层拥有所有权。


这一原则也是设计框架经常使用的原则,对于我们日常使用的开发框架来说,比如 Spring,它不会依赖与我们的代码,恰恰相反,我们可能受制于它。


发布于: 2021 年 02 月 16 日阅读数: 18
用户头像

引花眠

关注

还未添加个人签名 2018.06.11 加入

还未添加个人简介

评论

发布
暂无评论
面向对象设计原则