第六章 面向可维护性的构造(第一部分)

可维护性的常见度量指标

  • 可维护性:可轻松修改软件系统或组件,以纠正故障,提高性能或其他属性,或适应变化的环境。
  • 除此之外,可维护性还有其他许多别名:可扩展性(Extensibility)、灵活性(Flexibility)、可适应性(Adaptability)、可管理性(Manageability)、支持性(Supportability)。总之,有好的可维护性就意味着容易改变,容易扩展。
  • 软件可维护性的五个子特性:
    • 易分析性。软件产品诊断软件中的缺陷或失效原因或识别待修改部分的能力。
    • 易改变性。软件产品使指定的修改可以被实现的能力,实现包括编码、设计和文档的更改。如果软件由最终用户修改,那么易改变性可能会影响易操作性。
    • 稳定性。软件产品避免由于软件修改而造成意外结果的能力。
    • 易测试性。软件产品使已修改软件能被确认的能力。
    • 维护性的依从性。软件产品遵循与维护性相关的标准或约定的能力。
  • 一些常用的可维护性度量标准:
    • 圈复杂度(CyclomaticComplexity):度量代码的结构复杂度。
    • 代码行数(Lines of Code):指示代码中的大致行数。
    • Halstead Volume:基于源代码中(不同)运算符和操作数的数量的合成度量。
    • 可维护性指数(MI):计算介于0和100之间的索引值,表示维护代码的相对容易性。 高价值意味着更好的可维护性。
    • 继承的层次数:表示扩展到类层次结构的根的类定义的数量。 等级越深,就越难理解特定方法和字段在何处被定义或重新定义。
    • 类之间的耦合度:通过参数,局部变量,返回类型,方法调用,泛型或模板实例化,基类,接口实现,在外部类型上定义的字段和属性修饰来测量耦合到唯一类。
    • 单元测试覆盖率:指示代码库的哪些部分被自动化单元测试覆盖

聚合度与耦合度模块化编程

  • 模块化编程的含义:模块化编程是一种设计技术,它强调将程序的功能分解为独立的可互换模块,以便每个模块都包含执行所需功能的一个方面。
  • 设计规范:高内聚低耦合
  • 评估模块化的五个标准:
    • 可分解性:将问题分解为各个可独立解决的子问题
    • 可组合性:可容易的将模块组合起来形成新的系统
    • 可理解性:每个子模块都可被系统设计者容易的理解
    • 可持续性:小的变化将只影响一小部分模块,而不会影响整个体系结构
    • 出现异常之后的保护:运行时的不正常将局限于小范围模块内
  • 模块化设计的五条原则:
    • 直接映射:模块的结构与现实世界中问题领域的结构保持一致
    • 尽可能少的接口:模块应尽可能少的与其他模块通讯
    • 尽可能小的接口:如果两个模块通讯,那么它们应交换尽可能少的信息
    • 显式接口:当A与B通讯时,应明显的发生在A与B的接口之间
    • 信息隐藏:经常可能发生变化的设计决策应尽可能隐藏在抽象接口后面

【内聚性】

  • 又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。
  • 所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。

【耦合性】

  • 也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
  • 对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。

        并不是内聚越高越好,耦合越低越好,真正好的设计是在高内聚和低耦合间进行平衡,也就是说高内聚和低耦合是冲突的最强的内聚莫过于一个类只写一个函数,这样内聚性绝对是最高的。但这会带来一个明显的问题:类的数量急剧增多,这样就导致了其它类的耦合特别多,于是整个设计就变成了“高内聚高耦合”了。由于高耦合,整个系统变动同样非常频繁。

高耦合和低内聚



SOLID

SOLID: 5 classes design principles


 (SRP)  The Single Responsibility Principle 单一责任原则 

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

 (LSP)  The Liskov Substitution Principle Liskov替换原则 

 (DIP)  The Dependency Inversion Principle 依赖转置原则 

 (ISP)   The Interface Segregation Principle 接口聚合原则

 Single Responsibility Principle (SRP)

一个类,一个责任



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

软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。
      模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
      模块自身的代码是不应被修改的 
      扩展模块行为的一般途径是修改模块的内部实现 
      如果一个模块不能被修改,那么它通常被认为是具有固定的行为。
关键解决方案:抽象技术。 使用继承和组合来改变类的行为。

 Liskov Substitution Principle (LSP) 子类型替换原则

  Liskov's 替换原则意思是:"子类型必须能够替换它们的基类型。"或者换个说法:"使用基类引用的地方必须能使用继承类的对象而不必知道它。" 这个原则正是保证继承能够被正确使用的前提。通常我们都说,“优先使用组合(委托)而不是继承”或者说“只有在确定是 is-a 的关系时才能使用继承”,因为继承经常导致”紧耦合“的设计


Interface Segregation Principle (ISP) 接口分离原则

客户模块不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性。如Java中一个类实现多个接口,不同的接口给不用的客户模块使用,而不是提供给客户模块一个大的接口。
客户模块不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性。如Java中一个类实现多个接口,不同的接口给不用的客户模块使用,而不是提供给客户模块一个大的接口。


Dependency Inversion Principle (DIP) 依赖转置原则

高层模块不要依赖低层模块;
高层和低层模块都要依赖于抽象;
抽象不要依赖于具体实现; 
具体实现要依赖于抽象;
抽象和接口使模块之间的依赖分离。

Design Patterns for Maintainability


Creational patterns 创造性模式

 Factory Method pattern 工厂方法模式

当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。 

定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。


静态工厂类:


抽象工厂模式:

抽象工厂模式:提供接口以创建一组相关/相互依赖的对象, 但不需要指明其具体类。


Builder(构造器模式)



 Abstract Factory vs Builder

  前者:创建的不是一个完整产品,而是“产品族”(遵循 固定搭配规则的多类产品实例),得到的结果是:多个不同产品的实 例object,各产品创建过程对client可见,但“搭配”不能改变。
  后者: Builder 创建的是一个完整的产品,有多个部分组成,client不需了解 每个部分是怎么创建、各个部分怎么组合,最终得到一个产品的完整 object

Structural patterns

(1)Bridge(桥接模式)

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

桥接是一个接口,它与一方应该是绑定的,也就是解耦的双方中的一方必然是继承这个接口的,这一方就是实现方,而另一方正是要与这一方解耦的抽象方,如果不采用桥接模式,一般我们的处理方式是直接使用继承来实现,这样双方之间处于强链接,类之间关联性极强,如要进行扩展,必然导致类结构急剧膨胀。采用桥接模式,正是为了避免这一情况的发生,将一方与桥绑定,即实现桥接口,另一方在抽象类中调用桥接口(指向的实现类),这样桥方可以通过实现桥接口进行单方面扩展,而另一方可以继承抽象类而单方面扩展,而之间的调用就从桥接口来作为突破口,不会受到双方扩展的任何影响。

 注意点:

    1、定义一个桥接口,使其与一方绑定,这一方的扩展全部使用实现桥接口的方式

          2、定义一个抽象类,来表示另一方,在这个抽象类内部要引入桥接口,而这一方的扩展全部使用继承该抽象类的方式。

Bridge vs. Strategy

前者一个类A的对象中有其他类B的对象作为其组成部分,但A的对 象具体绑定到B的哪个具体子类的实现?在运行时通过delegation加以组合, 并永久保存这种delegation关系。

后者“算法”通常实现为“类的某个方法”的形式, strategy的目的并非在“调用算法的类”与“被调用算法所在的类”之间建 立起永久联系,而只是帮助前者临时使用后者中的“算法”,前者无需永久 保存后者的实例。

(2) Proxy(代理模式)

某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问 到,故设置proxy,在二者之间建立防火墙。


Proxy vs. Adaptor(代理模式和适配器模式)

Adaptor:目的:消 除不兼容,目的是B以客户端期望的统一的方式与A建立起联系。

Proxy: 目的:隔离对复杂 对象的访问,降低难度/代价,定位在“访问/使用行为”。

Composite(组合模式)


具体举个例子吧:




Composite vs Decorator

前者目的是在同类型的对象之间建立起 树型层次结构,一个上层对象可包含多个下层对象

后者 强调的是同类型对象之间的“特性增加”问题, 它们之间是平等的,区别在于 “拥有特性”的多少,每次decoration 只能作用于一个object。

Behavioral patterns

 Observer

“粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动  粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝 (回调callback粉丝的特定功能)




(2) Visitor

对特定类型的object的特定操作(visit),在运行时将 二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。

本质上:将数据和作用于数据上的某种/些特定操作分离开来。

本质上:将数据和作用于数据上的某种/些特定操作分离开来。



Visitor vs Iterator

迭代器:以遍历的方式访问集合数据而无需暴露其内部表 示,将“遍历”这项功能delegate到外部的iterator对象。
Visitor:在特定ADT上执行某种特定操作,但该操作不 在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活 扩展/改变visitor的操作算法,而不影响ADT

Strategy vs visitor

二者都是通过delegation建立两个对象的动态联系 

– 但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系 不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client 通过它设定visitor操作并在外部调用

– 而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。 这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类 而已。


区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同操 作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度, 灵活变化对其内部功能的不同配置。


(3) Mediator

多个对象之间要进行交互,不 是直接交互,而是通过mediator,实现消息的广播,从而将对象之间松散耦 合,利于变化





Observer vs Mediator

 Observer pattern: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. 一组object对另一个object B的状 态变化感兴趣(1对多),所以对其进行观察。B维持着一个对自己感 兴趣的object list,一旦自己发生变化,就通知这些object。双方地位 并不对等,一方只是“通知”,另一方接到通知后执行特定行为。

(4) Command

 将“指令”封装为对象,指令的所有细节对client隐藏,在指令 内对具体的ADT发出动作(调用ADT的细节操作)

 客户端 希望执行指令,但不想知道指令的 细节,也不想知道指令的具体作用 对象。

.将所有对client提供的指令 与内部执行的ADT操作彻底分开,指令对外看来是“黑盒”——进一 步“变本加厉”的封装!


Façade vs. Command
Façade vs. Command





猜你喜欢

转载自blog.csdn.net/weixin_41145325/article/details/80723902
今日推荐