面向对象-类设计笔记

类设计(GRASP设计原则)

软件设计中的原则(GRASP)

  • GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件原则。
  • 属于原则层析一级设计模式,指导类的职责分配
  • 共有9种原则,描述了对象设计和职责分配的基本原则
    • 创建者(Creator)
    • 信息专家(Information Expert)
    • 低耦合(Low coupling)
    • 控制器(Controller)
    • 高内聚(High Cohesion)
    • 多态性(Polymorphism)
    • 纯虚构(Pure Fabrication)
    • 间接性(Indirection)
    • 受保护变化(Protected Variations)

1. Creator(创建者)

  • 问题:谁负责创建某类的新实例?

例:在Monopoly(大富翁)种,由谁来创建Square对象?

分析:分为两种情况:
1.创建Board时,由Board创建;
2.创建游戏时全部一起创建。

  • 创建者设计原则
    • 目的:合理分配创建职责,以降低耦合(参照低耦合设计原则)
    • 符合下列任意条件的时候,建议将创建类B实例的职责分配给类,这时A是B的创建者:
      • A是B的聚合
      • A是B的容器
      • A持有初始化B的信息(数据),创建B时会传递给B
      • A记录B的实例
      • A频繁使用B
    • 遵守创建者模式规定的基本原则,不要随便分配类的创建职责,增加类之间不必要的耦合
      • 如果一个类创建了另一个类,这两个类之间就有了耦合,产生了依赖关系。
      • 多余的依赖或耦合,会带来的维护问题
      • 必要的耦合是逃不掉的,能做的就是正确地创建耦合关系,不要随便建立类间依赖关系。
    • 创建者设计原则禁忌
      • 有时对象创建具有相当的复杂性 ,最好把创建职责委派给称为具体工厂或抽象工厂的辅助类
    • 创建者指导我们分配与创建对象相关的职责。
  • 优点:支持低耦合

总结

  • 对象的创建具有相当的复杂性,创建者模式不能独立考虑。
  • 我们可以结合交互图考虑和决定职责的分配

2. Information Expert(信息专家原则)

  • 问题:如果给定Square名称,从哪个对象查询该Square对象的相关信息??谁负责了解Square?
  • 信息专家原则是类职责分配基本原则
    • 给对象分配职责的基本原则是信息专家原则
    • 是面向对象设计的最基本原则
    • 建议:把职责分配给具有完成该职责所需要信息的类
      • 设计对象(类)的时候,如果某个类拥有完成某个职责所需要的所有信息,那么这时,这个类就是相对于这个职责的信息专家。
  • 缺点
    • 有时专家原则并不合适

    例如:谁应负责把Sale存入数据库?按专家原则来说,就需要Sale自己把自己存入数据库,显然不对

  • 优点
    • 信息的封装性得到维持
    • 易于理解和维护

总结

  • 总的来说,就是一个类只干该干的事情,不该干的事情不干。某个类拥有完成某个职责所需要的所有信息,就把这个职责分配给这个类来实现。

3. Low coupling (低耦合)

  • 问题:为什么是通过Board对象查询Square,而不是任意一个类?
  • 耦合:是对某元素与其他元素之间的联系、感知和依赖程度的度量
  • 下面这些情况会造成类A和B之间的耦合:
    • A是B的属性
    • A调用B的实例的方法
    • A的方法中引用了B,例如B是A方法的返回值或参数。
    • A是B的子类,或者A实现了B(接口)
  • 低耦合的意思就是要尽可能地减少类之间的连接
    • 如果存在耦合或依赖,当被依赖的元素发生变化,则依赖者也会受到影响。低耦合能有效减少这种影响
  • 低耦合的一些基本原则:
    • Don’t Talk to Strangers原则:不需要通信的两个对象之间,不要进行无谓的连接。
    • 如果A已经和B有连接,分配A的职责给B不合适的话(违反信息专家模式),那么就把B的职责分配给A。
    • 两个不同模块的内部类之间不能直接连接

由谁创建Payment,并使之与Sale相关联? 下面两个图要选哪个?

分析:选图二更好;因为paymen和sale之间必然会有关联,图一会增加不必要的关联,耦合更高,不符合低耦合要求;选图二更好,顺应paymen和sale之间必然的关联,不会增加不必要的关联。

  • 禁忌
    • 对于稳定和普通使用的元素的高耦合,是允许的. 如对java库(Java.util)的使用等
  • 优点
    • 减少因变化产生的影响,提高重用性
    • 易于单独理解

总结

  • 有关联、依赖、引用等关系都会造成耦合,要想低耦合,就要尽量非必要类之间的关联,从而造成耦合。但也不是绝对,对于一些稳定、频繁使用的元素高耦合还是有必要的。

4. Controller (控制器)

  • 问题:在UI(User Interface)之下首先接收和协调系统操作的对象是什么?
  • 简单的分层架构包括UI层和业务层。依据MVC原则,UI层对象不应包含应用逻辑和业务逻辑
  • UI请求职责分配给能代表下列对象:
    • 代表全部“系统”或“根对象”,(在只有少数几个系统操作时,符合高内聚原则),如MonopolyGame
    • 代表运行软件的设备,如电话、收银机等
    • 代表用例或会话的对象
  • 控制器可以不是领域对象
  • 控制器的选择
    • 第一类:控制器表示整个系统\装置或子系统的外观控制器
      • 如Register、TelecommSwitch
      • 当没有过多的系统事件时,外观控制器合适
    • 第二类:虚构的用例控制器类
      • 系统事件较多时,或有跨越不同过程的大量系统事件时
  • 优点:
    • 增加了可复用和接口可插拨的能力
    • 获得推测用例状态的机会
  • 禁忌
    • 避免臃肿的控制器
      • 只有一个控制器接收系统中全部的系统事件
      • 控制器完成诸多必要的任务,而不是把工作委派出去
      • 控制有许多属性,维护与领域相关的重要信息
    • UI层不处理系统事件
      • 系统操作职责分配给应用层,而不是UI层中的对象

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

5. High cohesion (高内聚)

  • 问题:怎样使得复杂性可管理?
  • 高内聚:把功能性紧密相关的职责放在一个类里,并共同完成有限的功能
  • 内聚可以度量软件对象操作在功能上的相关程度。
    • 内聚和耦合互相依赖
  • 禁忌
    • 少数情况下,可以接低内聚
    • 允许某人方便对一个类或构件维护
    • 为了获得更好性能,
  • 优点
    • 能够更加轻松,理解设计
    • 简化维护和优化设计
    • 支持低耦合

例如:一个订单数据存取类(OrderDAO),订单即可以保存为Excel模式,也可以保存到数据库中;那么,不同的职责由不同的类来实现,这样才是高内聚的设计。照此,我们就把两种不同的数据存储功能分别放在了两个类里来实现。这样如果未来保存到Excel的功能发生错误,那么就去检查OrderDAOExcel类就可以了

总结

  • 在一个低内聚的类中会执行很多互不相关的操作,这将导致系统难于理解、难于重用、难于维护,容易受到变化带来的影响。因此我们需要控制类的粒度,在分配类的职责时使其内聚保持为最高,提高类的重用性,控制类设计的复杂程度。

6. Polymorphism (多态)

  • 问题:如何处理基于类型的选择,创建可插拨的构件
  • 解决方案
    • 当相关选择或行为随类型有所不同时,使用多态操作为变化的行为类型分配职责
    • 应用多态操作为变化的行为类型分配职责,这样不需要测试对象的类型,也不要使用条件逻辑来执行基于类型的不同选择
  • 多态是面向对象的重要特性,简单点说:“一个接口,多种实现”
  • 使用多态,使得对不同类的对象发出相同的消息将会有不同的行为
  • 多态允许将子类的对象当作父类的对象使用, 允许引用所指向的对象可以在运
    行期间动态绑定

例:定义一个抽象类Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape类里的Draw()方法,这样我们就可以使用同样的接口(Shape抽象类)绘制出不同的图形。如果还要拓展出新的图形,如三角形,只需要定义Triangle来继承Shape,不会对其他图形产生影响。

总结分析

  • 程序中经常因为条件变化而引发同一类型的不同行为。如果用if-else或switch-case等条件语句来设计程序,当系统发生变化时必须修改程序的业务逻辑,这就导致扩展有新变化的程序会很麻烦。所以我们用多态来实现,将不同的行为指定给不同的子类,就可以很好的避免一个地方改变带来的连锁反应。

7. Pure Fabrication (纯虚构)

  • 问题:当不想破坏高内聚和低耦合的设计原则时,谁来负责处理这种情况?
  • 解决方案
    • 设计一个虚构的类,分配一组高内聚职责,用以支持高内聚、低耦合和复用。
    • 该类不代表领域的概念,是凭空虚构的
    • 理想情况:虚构类的职责支持高内聚低耦合
  • 纯虚构基于相关功能性,以功能为中心构造对象
  • 禁忌
    • 不要滥用行为解析及纯虚构对象,导致大量行为对象,功能变为对象

例:在数据库中保存Sale类;根据信息专家,职责应分配给Sale;
造成的问题有:会造成Sale与数据库接口类耦合;面向数据库的操作与实际的销售概念无关,使得Sale非内聚;不利于数据库操作的复用,其他类也存在保存对象职责
解决方法:虚构一新类PersitentStorage,负责Sale的存储

总结

  • 纯虚构模式就是创建一个纯虚构类,将一部分类的职责(高内聚和低耦合矛盾的功能)转移到纯虚构类,从而达到高内聚和低耦合的目的。

8. Indirection (间接性)

  • 问题:
    • 避免两个或多个对象之间直接耦合
    • 如何使对象解耦合,以支持低耦合,提高复用力
  • 解决方案:
    • 将职责分配给中介对象,使其作为其他构件或服务之间的媒介,以避免它们之间的直接耦合
    • 中介对象实现了构件之间的间接性

总结

  • 总的来说就是,就像你要做那个东西,不直接去拿,先去戴个手套,再去拿。对于程序,“两个不同模块的内部类之间不能直接连接”,但是我们可以通过中间类来间接连接两个不同的模块,这样对于这两个模块来说,他们之间仍然是没有耦合/依赖关系的。

9. Protected Variations (受保护变化)

  • 问题:
    • 如何设计,使内部的变化不会对其他元素产生不良影响?
  • 解决方案:
    • 预先识别不稳定的变化点,定义稳定的接口
    • 如果未来发生变化的时候,可以通过接口扩展新的功能,而不需要去修改原来旧的实现
  • PV是一个根本原则,是大部分编程和设计的机制和模式,它使系统能够适应和隔离变化
    • 许多设计都是基于这种思想。如数据驱动设计,服务查询(Service Lookup)等
  • 优点
    • 易于增加新变化所需的扩展
    • 可以引入新的实现而无需影响客户
    • 低耦合
    • 能够降低变化的成本和影响

总结

  • 为了符合受保护变化模式,我们通常需要对系统进行抽象化设计,定义系统的抽象层,再通过具体类来进行扩展。这样一来,如果需要扩展系统的行为,我们就无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,在不修改已有代码的基础上扩展系统的功能。
posted @   吧拉吧拉吧  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示