zoukankan      html  css  js  c++  java
  • 重温设计模式系列(三)面向对象设计原则

    背景

    面向对象基础知识,只是给了我们一个概念,如何更好的设计出良好的面向对象代码,需要有设计原则作为支持。设计原则是核心指导思想,在这些原则的基础上,经过不断的实践,抽象,提炼逐步产生了针对特定问题的设计模式。因此,学好设计模式的基础是掌握基本的设计原则。本文将介绍面向对象常用的设计原则。(某些原则,也可以用在系统级,模块级等类型的设计中应用)

    1、代码抽象三原则

    1.1 DRY原则(Don't repeat yourself)

    意思是:不要重复自己。它的涵义是,系统的每一个功能都应该有唯一的实现。也就是说,如果多次遇到相同的问题,就需要抽象出一个通用的解决方案,不要重复开发相同的功能。

    用代码举例:如果两个地方需要发送短信的功能,第一个功能是发送提醒短信,第二个是发送验证码短信。则需要把发送短信的公用代码进行提炼。

    1.2 YAGNI原则( You aren't gonna need it)

    意思是:你不会需要它。出自极限编程的原则,指除了核心功能外,其它功能一概不要部署。背后的指导思想是尽快的让代码运行起来。简单理解是尽量避免不必要的代码,少就是多。比如:过多的日志打印,过多逻辑检查,过多的异常处理等,如果能简化则简化。

    1.3Rule Of Three原则

    Rule of three 称为"三次原则",指的是当某个功能第三次出现时,才进行"抽象化"。它的含义是:当第一次用到某个功能时,写一个特定的解决方法;第二次又用到的时候,拷贝上一次的代码;第三次出现的时候,才着手"抽象化",写出通用的解决方法。

    1.4 三原则之间的关系

    DRY强调对通用问题的抽象,YAGNI强调快速和简单。Rule Of Three相当于对前两个原则做了一个折衷,提出了应用原则的度量。三原则的折中,有以下几个好处。

    (1)省事,避免过度设计:如果只有一个地方用,就没必要过度抽象,避免过度设计。

    (2)容易发现模式:问题出现的场景多,容易找到通用的部分,方便进行抽象,进而找到模式。

    2、GRASP原则

    GRASP(General Responsibility Assignment Software Patterns),中文名称:“通用职责分配软件模式”,核心是自己干自己能干的事,自己只干自己的 事,也就是职责的分配和实现高内聚。用来解决面向对象设计的一些问题。GRASP一共包括9种模式,给出了最基本的面向对象指导原则,比如:如何决定一个系统有多少对象,每个对象都包括什么职责。

    2.1 Infomation Expert(信息专家)

    设计类时,如果一个类有完成某个职责的所有信息,则应该把该职责分配给该类。此时,该类相当于该职责的信息专家。

    例如: 常见的网上商店的购物车(ShopCar),需要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只需要更新商品的数量即可。如下图:

    针对这个问题需要权衡的是,比较商品是否相同的方法放到哪个类里来实现呢?分析业务得知需要根据商品的编号(SKUID)来唯一区分商品,而商品编号是唯一存在于商品类的,所以根据信息专家模式,应该把比较商品是否相同的方法放在商品类里。

    2.2 Creator(创造者)

    用于判断对象的初始化由哪个类发起,用于确定正确的依赖关系。实际应用中,符合下列任一条件的时候,都应该由类 A 来创建类 B,这时 A 是 B 的创建者:

    a、A 是 B 的聚合

    b、A 是 B 的容器

    c、A 持有初始化 B 的信息(数据)

    d、A 记录 B 的实例

    e、A 频繁使用 B

    例如:因为订单(Order)是商品(SKU)的容器,所以应该由订单来创建商品。如下图:

    这里因为订单是商品的容器,也只有订单持有初始化商品的信息,所以这个耦合关系是正确的且没有办法避免的,所以由订单来创建商品。

    2.3 Low coupling(低耦合)

    耦合是指两个类之间的依赖程度。低耦合说明两个类之间的依赖程度低。好的耦合是低耦合,有以下好处:

    (1)低耦合降低了因为一个类的变化,影响其他类的范围。

    (2)使类之间的关系简单,更容易理解。

    耦合的场景

    a、A 是 B 的属性

    b、A 调用 B 的实例的方法

    c、A 的方法中引用的 B,例如 B 是 A 方法的返回值或参数。

    d、A 是 B 的子类,或者 A 实现 B

    例如:Creator 模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。如下图:

    这里我们在订单类里增加了一个 TotalPrice() 方法来执行计算总价的职责,没有增加不必要的耦合。

    2.4 High cohesion(高内聚)

    内聚是指类内部职责的紧密程度,高内聚的类是设计良好的类,具备良好的隔离性,当内部变化了,只要接口不改变,不影响其他部分。

    例如:一个订单数据存取类(OrderDAO),订单即可以保存为 Excel 模式,也可以保存到数据库中;那么,不同的职责最好由不同的类来实现,这样才是高内聚的设计,如下图:

    这里我们把两种不同的数据存储功能分别放在了两个类里来实现,这样如果未来保存到 Excel 的功能发生错误,那么就去检查 OrderDAOExcel 类就可以了,这样也使系统更模块化,方便划分任务,比如这两个类就可以分配到不同的人同时进行开发,这样也提高了团队协作和开发进度。

    2.5 Controller(控制器)

    用于接收和处理系统事件的职责,一般配置给可以代表整个系统的类,一般成功XXController。

    有如下原则:

    a、系统事件的接收与处理通常由一个高级类来代替。

    b、一个子系统会有很多控制类,分别处理不同的事务。

    在MVC架构中,对应的是C。

    2.6 Polymorphism(多态)

    面向对象的三大特征一下,指一个接口可以有不同的实现,用于提高系统的灵活性和扩展性,写出高内聚,低耦合的代码。

    例如:我们想设计一个绘画程序,要支持可以画不同类型的图形,我们定义一个抽象类 Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape 类里的Draw() 方法,这样我们就可以使用同样的接口(Shape抽象类)绘制出不同的图形,如下图:

    这样的设计更符合高内聚和低耦合原则,虽然后来我们又增加了一个菱形(Diamond)类,对整个系统结构也没有任何影响,只要增加一个继承 Shape 类就行了。

    2.7 Pure Fabrication(纯虚构)

    这里的纯虚构跟我们常说说的纯虚构函数意思相近。高内聚低耦合,是系统设计的终极目标,但是内聚和耦合永远都是矛盾对立的。高内聚以为这拆分出更多数量的类,但是对象之间需要协作来完成任务,这又造成了高耦合,反过来依然。该如何解决这个矛盾呢?这个时候就需要纯虚构模式,由一个纯虚构的类来协调内聚和耦合,可以在一定程度上解决上述问题。

    例如:上面多态模式的例子,如果我们的绘图程序需要支持不同的系统,那么因为不同系统的API结构不同,绘图功能也需要不同的实现方式,那么该如何设计更合适呢?如下图:

    这里我们可以看到,因为增加了纯虚构类AbstractShape,不论是哪个系统都可以通过AbstractShape 类来绘制图形,我们即没有降低原来的内聚性,也没有增加过多的耦合,可谓鱼肉和熊掌兼得。

    2.8 Indirection(间接)

    用于隔离或组合两个类之间的交互,避免两个或多个事物之间直接耦合,比如用户类和商品类,两者的职责不同,如果之间进行直接调用,会形成强耦合(多对多关系),则可以增加一个中间者,实现对调用用户类和商品类的聚合处理。

    2.9 Protected Variations(防止变异)

    如何设计对象、系统和子系统,使其内部的变化或者不稳定因素不会对其他元素产生不良影响?

    预先识别不稳定的因素,抽象为接口,针对接口编程,隔离变化,如果未来发生变化,可以通过接口扩展新功能。例如:订单支付中的支付方式是不稳定的,刚开始有网银,快捷,之后又有了微信,支付宝。在设计时可以抽象出支付接口,当增加微信时,增加微信实现即可。

    3、SOLID原则

    3.1 单一职责原则(Single Responsibility Principle - SRP)

    一个类只有一个引起变化的原因。如果一个类有多个引起变化的原因,当其中一个变化时会影响到其他代码。这样代码的内聚性不好,会导致维护性变差,复用性降低。

    用于指导对类的设计,只有一个引起变化的原因,单一职责,设计出高内聚的类(或方法等元素)。

    3.2 开放封闭原则(Open Closed Principle - OCP)

    对于软件实体应该对扩展开放,对修改关闭。对扩展开放,是指当有新需求或需求变化时,可以仅对代码进行扩展,就可以适应新的需求。对修改关闭是指,类或方法一旦设计完成,就不需要对其进行修改。

    实现开闭原则的基础时,找到变化,封装变化。

    用于指导可扩展的设计。

    3.3 里氏替换原则(Liskov Substitution Principle - LSP)

    一个软件实体如果使用的是基类的话, 那么也一定适用于其子类, 而且它根本觉察不错使用的是基类对象还是子类对象;反过来的代换这是不成立的,

    用于指导继承体系的设计。子类出现的子类,父类可以替换,在子类设计时可以扩展父类的功能,但不能改变父类原有的功能。

    3.4 最少知识原则(Least Knowledge Principle - LKP)

    最少知识原则又叫迪米特法则。一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。也就是说一个软件实体应当尽可能少的与其他实体发生相互作用。当一个模块修改时,尽量少的影响其他的模块,容易扩展,这是对软件实体之间通信的限制,要求限制软件实体之间通信的宽度和深度。

    用于指导类之间的关系(通信)设计。

    3.5 接口隔离原则(Interface Segregation Principle - ISP)

    接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。

    用于指导接口的设计,对接口进行约束。

    (1)接口尽量单一,但要适度,避免过多的接口类定义。

    (2)实现类只实现需要的接口即可,当一个类实现多个接口时,调用时在具体场景只使用单一接口即可,把不必要的隐藏起来。这样依赖关系是最小的。有利于控制变化。

    3.6 依赖倒置原则(Dependence Inversion Principle - DIP)

    依赖倒置原则的核心思想是面向接口编程,不应该面向实现类编程。

    (1)抽象不应该依赖于细节。细节应该依赖于抽象。

    (2)高层不应该依赖于底层,两者都应该依赖于抽象。

    用于指导抽象设计,依赖稳定的,将稳定的进行抽象。

    4、其他设计原则

    4.1 组合/聚合复用原则(Composition/Aggregation Reuse Principle - CARP)

    在设计中,优先考虑使用组合,而不是继承。继承容易产生副作用,组合具有更好的灵活性。如:代理模式、装饰模式、适配器模式等。

    4.2 无环依赖原则(Acyclic Dependencies Principle - ADP)

    当 A 模块依赖于 B 模块,B 模块依赖于 C 模块,C 依赖于 A 模块,此时将出现循环依赖。在设计中应该避免这个问题,可通过引入“中介者模式”解决该问题。

    4.3 共同封装原则(Common Closure Principle - CCP)

    将易变的类放在同一个包里,将变化隔离出来。该原则是“开放-封闭原则”的延生。

    4.4 共同重用原则(Common Reuse Principle - CRP)

    如果重用了包中的一个类,那么也就相当于重用了包中的所有类,我们要尽可能减小包的大小。

    4.5 好莱坞原则(Hollywood Principle - HP)

    好莱坞明星的经纪人一般都很忙,他们不想被打扰,往往会说:Don't call me, I'll call you. 翻译为:不要联系我,我会联系你。对应于软件设计而言,最著名的就是“控制反转”(或称为“依赖注入”),我们不需要在代码中主动的创建对象,而是由容器帮我们来创建并管理这些对象。

    4.6 保持它简单与傻瓜(Keep it simple and stupid - KISS)

    不要让系统变得复杂,界面简洁,功能实用,操作方便,要让它足够的简单,足够的傻瓜。

    4.7 惯例优于配置(Convention over Configuration - COC)

    尽量让惯例来减少配置,这样才能提高开发效率,尽量做到“零配置”。很多开发框架都是这样做的。

    4.8 命令查询分离(Command Query Separation - CQS)

    在定义接口时,要做到哪些是命令,哪些是查询,要将它们分离,而不要揉到一起。在读写分离或分布式系统中应用较多。

    4.9 关注点分离(Separation of Concerns - SOC)

    将一个复杂的问题分解为多个简单的问题,然后逐个解决简单的问题,那么复杂的问题就解决了。

    4.10 契约式设计(Design by Contract - DBC)

    模块或系统之间的交互,都是基于契约(接口或抽象)的,而不要依赖于具体实现。该原则建议我们要面向契约编程。

    小结

    本文介绍了常用的设计原则,基于这些原则,可以用于指导代码设计,架构评审,Code Review等。在设计原则的基础上产生了设计模式,下一篇,我们会整体介绍GOF 23种设计模式。

  • 相关阅读:
    P1536 村村通 题解
    P1551 亲戚题解
    P1185 绘制二叉树 题解
    P3884 [JLOI2009]二叉树问题
    P1087 [NOIP2004 普及组] FBI 树
    P1305 新二叉树题解
    P1229 遍历问题
    P1030 [NOIP2001 普及组] 求先序排列题解
    P1827 [USACO3.4]美国血统 American Heritage 题解
    深度优先搜索dfs 讲解教程
  • 原文地址:https://www.cnblogs.com/itfly8/p/14035074.html
Copyright © 2011-2022 走看看