GRASP七大基本原则+纯虚构防变异

news2024/11/24 3:13:21

问题引出

软件开发过程中,需要设计大量的类,使他们交互以实现特定的功能性需求。但是不同的设计方式,对程序的非功能性需求(可扩展性,稳定性,可维护性等)的实现程度则完全不同。
有没有一种统一的设计方式既实现功能性需求又能满足非功能性需求?没有。开发中药遵循现有的设计原则如GRASP达到相对较好的程序开发质量。


什么是GRASP

GRASP是General Responsibility Assignment Software Principle,通用职责分配软件原则。核心思想是“职责分配”。GRASP将在通用代码层面知道以下编程过程:

  1. 某个方法要交给哪个类来实现比较合适(方法给哪个类)
  2. 某个类由哪个类来创建合适(类由哪个类创建)
  3. 某个类包含哪些成员和方法(类应该有哪些方法和成员)
  4. 两个类交互时,采用哪种方式?
  5. 某个类在某些情况下应该转变为另一个类
  6. 类的哪些成员和方法可以被哪些类访问


1 信息专家原则

1.1 问题引出及原则

问题:流水类越级操作了Item类。导致了级联修改。 

打八折应该是流水类应该负责的事情,但是产生级联修改。小票类也要进行修改。

Sale小票类应该是存放所有流水的总价。而各个流水的价格(subTotal)应该交给流水类来计算,这样在某商品打折时,可以在流水类中进行优惠计算。

把职责分配给具有完成该职责所需信息的那个类,这个类就是信息专家

即:如果某个对象拥有完成某个职责所需要的所有信息,那么这个职责就分配给这个对象实现。这个时候,这个类就是相对于这个职责的信息专家。

/**
 * 小票
 */
class Sale {
    saleItemMap: SalesLineItem[]
}

/**
 * 流水
 */
class SalesLineItem {
    // 销量
    quantity: number;
    desprition: ProductDesprition;
}

/**
 * 商品详情
 */
class Item{
    des: string;
    id: string;
    price: number;
}

1.2 总结

优点: 

  • 信息的封装性得以维持
    • 对象充分利用自身的信息来完成任务。
    • 支持低耦合,形成更健壮、可维护的系统。
  • 系统行为分布到不同的类
    • 形成内聚性更强的轻量类,易于维护和理解。

2 创建者原则

2.1 总结

算是信息专家原则的一个特化。

优点:

支持低耦合:创建者模式不会增加耦合性,因为所创建的类与创建者之间本身已经存在关联。

有利于类的重用


3 低耦合原则

对于程序设计,耦合一般是指代码块、类、模块、系统间的互相调用或引用。
一般的,程序设计的耦合可以分为:间接耦合,数据耦合,对象耦合,控制耦合,公共耦合,内容耦合

3.1 数据耦合

两个模块之间有调用|用关系,传递的是数据值(int,double,float,string),相当于值模型传递。

3.2 对象耦合

两个模块之间有调用门|用关系,传递的是数据对象,相当于引用模型传递。 

3.3 控制耦合

指一个模块调用另一个模块时,传递的是控制变量(如开关、标志等),被调模块通过该控制变量的值有选择地执行块内某一功能。

3.4 公共耦合

指通过一个公共数据环境相互作用的那些模块间的耦合。公共耦合的复杂程度随耦合模块的个数增加而增加。

3.5 内容耦合

当一个模块通过非正常入口而转入另一个模块内部,或者直接使用另一个模块的内部数据,则会产生内容耦合。 

3.6 总结 

  • 低耦合是在制定设计决策期间需要牢记的原则,是评估所有设计结果时要运用的评估原则。
  • 低耦合不能脱离专家和高内聚模式孤立地考虑,应该作为影响职责分配的原则之一。
  • 没有绝对的度量标准来衡量耦合程度的高低(高低是一个相对的概念)。重要的是能够估测当前的耦合程度,评估增加耦合是否会导致问题。

优点:

  • 不受其他构件变化的影响
  • 易于单独理解
  • 便于复用

 4 高内聚原则

内聚性较低的类,会执行很多互不相关的操作。这将导致系统:

  • 难以理解
  • 难以复用
  • 难以维护
  • 脆弱,容易受到变化的影响

所以,需要对类的职责进行拆分,以达到高内聚。分解后的类,应当具有独立的职责,一个类只完成与它高度相关的工作。如果要实现在多个类中重复使用的方法,或者其他功能需要的方法,则把该方法封装在其他类中。类与类之间彼此协作,完成复杂的任务。

class Main {
    getSum(...branches) {
        let sum = 0;
        branches.forEach(item => {
            sum += item.income - item.spending;
        });
        return sum;
    }
}

class Branch {
    income: number;
    spending: number;
    constructor(income: number, spending: number) {
        this.income = income;
        this.spending = spending;
    }
}
/*
Main类直接访问Branch类的数据,造成内容耦合。
如果Branch类的数据字段变动,Main类也需要随之进行迭代,不利于引入新的变化。
Main的求和函数 getSum 中,不应该计算某一个Branch的利润。
因为这属于Branch的业务逻辑。
*/

class Main {
    getSum(...branches) {
        let sum = 0;
        branches.forEach(item => {
            sum += item.getProfit();
        });
        return sum;
    }
}
class Branch {
    private income: number;
    private spending: number;
    constructor(income: number, spending: number) {
        this.income = income;
        this.spending = spending;
    }
    getProfit() {
        return this.income - this.spending;
    }
}
/*
总店与分店解耦:总店不用直接访问分店的内部数据
避免了例如分店数据发生变动时,总店模块也要随之升级的情况。
总店的求和函数 getSum 只完成与之高度相关的一件事情,使得内聚性提高。
*/

4.1 总结

1、具有高度相关功能的模块或类,可以采用相对较高耦合度的交互方式进连接(如继承、对象引用)
2、而功能差异较大的模块或类,应该采用耦合度较低的交互方式进行连接(如接口调用,抽象类调用)

优点:

  • 能够更加轻松、清楚地理解设计。
  • 降低类的复杂性,降低代码的维护和改进成本。
  • 通常支持低耦合。
  • 由于内聚的类可以用于某个特定的目的,因此细粒度、相关性强的功能,可复用性增强。

5 控制器原则

 

当控制器负担过多的职责,且没有重点时,该控制器就是一个臃肿的控制器。这样的控制器违背高内聚原则,不利于模块复用和维护。继续以上面的控制器 LoginController 为例,它负责处理登录场景的事件协调。现在系统中出现一些新的用例场景,并新增事件处理器 Other、OtherOne和 OtherTwo。如果新增的事件继续让 LoginController 负责,如下所示,则会造成控制器的职责过多且没有重点。代码的可读性和可复用性都会降低。 

// Login控制器
class LoginController {
    private buttonModel: Button;
    private closeModel: Close;
    private otherModel: Other;
    private otherOneModel: OtherOne;
    private otherTwoModel: OtherTwo;
    constructor() {
        this.buttonModel = new Button();
        this.closeModel = new Close();
        this.otherModel = new Other();
        this.otherOneModel = new OtherOne();
        this.otherTwoModel = new OtherTwo();
    }
    // 事件协调/分发函数
    dispathEvent(event: string) {
        this.buttonModel.handle(event);
        this.closeModel.handle(event);
        this.otherModel.handle(event);
        this.otherOneModel.handle(event);
        this.otherTwoModel.handle(event);
    }
}
/*
我们对上述控制器进行改进。拆分 Login 控制器的职责
增加一种控制器 Other,使得每个控制器只负责处理一种用例场景。
这样代码变得更清晰了,且每个控制器的内聚性提高。改进后代码如下:
*/
class LoginController {
    private buttonModel: Button;
    private closeModel: Close;
    constructor() {
        this.buttonModel = new Button();
        this.closeModel = new Close();
    }
    dispathEvent(event: string) {
        this.buttonModel.handle(event);
        this.closeModel.handle(event);
    }
}
// Other 控制器
class OtherController {
    private otherModel: Other;
    private otherOneModel: OtherOne;
    private otherTwoModel: OtherTwo;
    constructor() {
        this.otherModel = new Other();
        this.otherOneModel = new OtherOne();
        this.otherTwoModel = new OtherTwo();
    }
    dispathEvent(event: string) {
        this.otherModel.handle(event);
        this.otherOneModel.handle(event);
        this.otherTwoModel.handle(event);
    }
}

 解决方法:

  • 增加控制器:在存在很多系统事件的系统中,增加控制器,每个控制器负责不同的场景。
  • 设计控制器:良好地设计控制器,使它把处理系统事件的任务分发出去。

6 多态原则

如何处理基于类型的选择?如何创建可插拔的软件组件? 

 接收的都是Action,但是实现的功能不一样。同样的方法调用得到的是不同效果。

多态实现:

  • 方式一:接口
  • 方式二:重载
  • 方式三:抽象类和抽象方法

 当相关选择或行为随类型而变化时,使用多态操作来为变化的行为分配职责。

推论:不要测试对象的类型,也不要使用条件逻辑来执行基于类型的不同选择。如果我们使用 if-else 或 switch 语句来执行不同类型的分支,当出现新的变化时,往往需要修改散落在各处的的if语句,让软件难以维护,也容易出现缺陷。

function demo(animals) {
    for(const animal of animals) {
        if (animal instanceof Duck) {
            console.log('Duck Duck Duck');
            // do Duck's thing
        } else if (animal instanceof Cattle) {
            console.log('mou mou mou');
            // do Cattle's thing
        }
    }
}
/*
以上代码的缺陷:

当需要新增 animal 类型时,就需要增加 if/else 逻辑。
每个 animal 类型的行为逻辑无法在其他地方复用。
*/
/*
接下来使用多态模式,对该示例进行改进。
新增 Animal 接口以及 makeSound 方法。
Duck 类和 Cattle 类代表两种不同的动物,它们都实现了 makeSound 方法。
这样一来,在 demo 函数中,只需要遍历 animals 列表,
并执行 makeSound 方法即可。改进后的代码如下:
*/
interface Animal {
    makeSound(): void;
}
class Duck implements Animal {
    makeSound() {
        console.log('Duck Duck Duck');
    }
}
class Cattle implements Animal {
    makeSound() {
        console.log('mou mou mou');
    }
}
function demo(animals: Animal[]) {
    for(const animal of animals) {
        // 不同的 animal 发出不同的声音
        animal.makeSound();
    }
}
/*
改进后的优点:
该功能模块更容易引入新变化。
每个类的逻辑可复用。
*/

6.1 总结

多态:同种行为(方法)有不同的实现。 反过来,不同的类型,统一为同种类型,隐藏了多余的方法。

多态和继承的关系:继承可以实现多态;多态的实现方法不局限于继承,还有接口实现、组合实现、代理实现等。

优点:

  • 符合多态原则的对象,易于增加新变化所需的扩展。
  • 无需影响客户,就能够引入新的实现。

7 间接原则

 

interface RowingBoat {
    row(): void;
}
class Captain {
    private rowingBoat: RowingBoat;
    constructor(rowingBoat: RowingBoat) {
        this.rowingBoat = rowingBoat;
    }
​
    row() {
        this.rowingBoat.row();
    }
}
class FishingBoat {
    sail() {
        // do something
    }
}
/*
这里 FishingBoat 已经被其他对象使用了,它本身不想把 sail 方法改成 row。
而 Captain 也已经被其他业务使用了,所以它本身的接口也不能修改。
怎样协调 Captain 和 FishingBoat,使 Captain 可以调用 FishingBoat?
*/
//根据间接性建议,可以增加一个中介对象,
//避免 Captain 和 FishingBoat 直接耦合。实现如下:
class FishingBoatAdapter implements RowingBoat {
    private boat: FishingBoat;
​
    constructor() {
        this.boat = new FishingBoat();
    }
​
    row() {
        this.boat.sail();
    }
}
//Captain调用
var captain = new Captain(new FishingBoatAdapter());
captain.row();
//Captain 和 FishingBoat 之间低耦合
//Captain 既能调用 FishingBoat
//又不影响 FishingBoat 复用到其他业务。

优点:实现构件之间的低耦合。


8 纯虚构

当信息专家原则无法实现低耦合和高内聚时,那可以引入纯虚构方案。

对人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念——虚构的事物,用以支持高内聚、低耦合和复用。这种类是凭空虚构的。理想情况下,分配给这种虚构物的职责支持高内聚和低耦合,使这种虚构物清晰或纯粹——因此称为纯虚构。

有一个图形库,其中 DrawShapes 类接收所有图形数据,输出所有图形。根据信息专家,DrawShapes 拥有所有图形的数据,所以 DrawShapes 应该负责每个图形的输出。

interface ShapeData {
    name: string;
    data: any;
}

class DrawShapes {
    constructor(data: ShapeData[]) {
        data.forEach(item => {
            if (item.name === 'circle') {
                this.drawCircle(item.data);
            }
            if (item.name === 'square') {
                this.drawSquare(item.data);
            }
        });
    }
​
    private drawCircle(data: CricleData) {
        // draw circle
    }
​
    private drawSquare(data: SquareData) {
        // draw square
    }
}
  •  高耦合:DrawShapes 与所有图形耦合在一起。
  • 不易扩展:新增一种图形,都需要新增 if 。
  • 不易复用:drawCircle这些方法无法单独复用到其他业务。

新增一个类 ShapeController,由这个类负责调度所有图形,连接 DarwShapes 和 所有图形。这个类在图形业务领域并没有相关的概念,它是我们虚构出来的。

interface IController {
    draw(data: any): void;
}
class ShapeController {
    ctrl: IController;
    root: '/';
    constructor (root: string) {
        this.root = root;
    }
​
    draw(name: string, data: Record<string, any>) {
        // 根据 name 来查找图形
        let fullpath = path.resolve(this.root, name);
        const ShapeClass = require(fullpath);
        // 实例化图形并绘制
        const shape = new ShapeClass();
        shape.draw(data);
    }
}

//每个图像单独模块
// cricle
class Circle implements IController {
    draw(data: CricleData) {
        // draw circle
    }
}
​
// rectangle
class Rectangle implements IController {
    draw(data: RectangleData) {
        // draw rectangle
    }
}

// DrawShape
class DrawShape {
    constructor(data: ShapeData[]) {
        let shapeController = new ShapeController('/shape');
        data.forEach(item => {
            shapeController.draw(item.name, item.data);
        });
    }
}
  • 纯虚构通常会接纳本来基于“专家模式”所分配给领域类的职责,这里要特别注意防止纯虚构的滥用。
  • 基本所有的设计模式都是纯虚构,比如控制器,适配器,观察者。
  • 不必纠结一个类是否为纯虚构,纯虚构是基于相关的功能性进行划分,是一种以功能或者行为为中心的对象。

9 防变异

 有A、B两个元素,A内部的变化不会对B造成影响,B内部的变化也不会对A造成影响。识别预计变化或不稳定之处,分配职责以在这些变化之外创建稳定接口。

指在面向对象设计中,应当预见到系统中哪些部分可能会发生变化,并将这些部分封装起来,从而保护系统的其他部分不受这些变化的影响。通过定义稳定的接口,系统中的各个部分可以与这些接口交互,而不是直接与变化的部分交互。这样,当变化发生时,只需修改接口的实现,而不需要修改依赖于这些接口的其他代码。

防变异原则的目的是隔离变化,使得系统的其他部分在变化发生时不需要做修改,从而提高系统的可维护性和灵活性。

在这个反例中,DrawingClient 类直接依赖于 CircleSquare 类。如果未来需要添加新的图形,就需要修改 DrawingClient 类,这违反了防变异原则 。

// 客户端代码直接依赖于具体类
class DrawingClient {
    public void drawCircle() {
        new Circle().draw();
    }
    
    public void drawSquare() {
        new Square().draw();
    }
}
// 定义一个稳定的接口
interface Shape {
    void draw();
}

// 实现接口的具体类
class Circle implements Shape {
    @Override
    public void draw() {
        // 绘制圆形
    }
}

class Square implements Shape {
    @Override
    public void draw() {
        // 绘制正方形
    }
}

// 客户端代码,依赖于稳定的接口
class DrawingClient {
    public void drawShape(Shape shape) {
        shape.draw();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2195772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态规划算法——三步问题

1.题目解析 2.算法原理 本题可以近似看做泰波那契数列&#xff0c;即小孩到第一个台阶需要一步&#xff0c;到第二个台阶则是到第一个台阶的步数加上第一阶到第二阶的步数&#xff0c;同理第三阶就是第二阶的步数加上第二阶到第三阶的步数&#xff0c;由于小孩只能走三步&#…

基于STM32的智能垃圾桶控制系统设计

引言 本项目设计了一个基于STM32微控制器的智能垃圾桶控制系统&#xff0c;能够通过超声波传感器检测手部动作&#xff0c;自动打开或关闭垃圾桶盖&#xff0c;提升用户的便利性和卫生性。该项目展示了STM32微控制器在传感器检测、伺服电机控制和嵌入式智能控制中的应用。 环…

在不支持WSL2的Windows环境下安装Redis并添加环境变量的方法

如果系统版本支持 WSL 2 可跳过本教程。使用官网提供的教程即可 官网教程 查看是否支持 WSL 2 如果不支持或者觉得麻烦可以按照下面的方式安装 下载 点击打开下载地址 下载 zip 文件即可 安装 将下载的 zip 文件解压到自己想要解压的地方即可。&#xff08;注意&#x…

毕业设计选题:基于ssm+vue+uniapp的模拟考试小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

2024最新 Navicat Premium 17 简体中文版安装图文详细教程

Navicat 17 引入了一系列新特性&#xff0c;旨在提升用户体验和工作效率。以下是一些值得关注的新功能&#xff1a; ‌模型工作区的全面重新设计‌&#xff1a;包含了增强的图表设计、更强大的同步工具、数据字典支持等多项功能。这有助于在一个工作区中创建多个模型&#xff0…

集合论基础 - 离散数学系列(一)

目录 1. 集合的基本概念 什么是集合&#xff1f; 集合的表示方法 常见的特殊集合 2. 子集与幂集 子集 幂集 3. 集合的运算 交集、并集与补集 集合运算规则 4. 笛卡尔积 5. 实际应用 6. 例题与练习 例题1 练习题 总结 引言 集合论是离散数学的基础之一&#xff…

HarmonyOS第一课 04 应用程序框架基础-习题分析

判断题 1.在基于Stage模型开发的应用项目中都存在一个app.json5配置文件、以及一个或多个module.json5配置文件。T 正确(True) 错误(False) 这个答案是T - AppScope > app.json5&#xff1a;app.json5配置文件&#xff0c;用于声明应用的全局配置信息&#xff0c;比如应用…

利用大规模语言模型提高生物医学 NER 性能的新方法

概述 论文地址&#xff1a;https://arxiv.org/pdf/2404.00152.pdf 大规模语言模型在零拍摄和四拍摄任务中表现出色&#xff0c;但在生物医学文本的独特表达识别&#xff08;NER&#xff09;方面仍有改进空间。例如&#xff0c;Gutirrez 等人&#xff08;2022 年&#xff09;的…

Chrome浏览器调用ActiveX控件--allWebOffice控件功能介绍

allWebOffice控件概述 allWebOffice控件能够实现在浏览器窗口中在线操作文档的应用&#xff08;阅读、编辑、保存等&#xff09;&#xff0c;支持编辑文档时保留修改痕迹&#xff0c;支持书签位置内容动态填充&#xff0c;支持公文套红&#xff0c;支持文档保护控制等诸多办公功…

医院伤病员食堂批量打印—未来之窗行业应用跨平台架构

一、订单后厨打印批量 在医院伤员管理中&#xff0c;预约订单现场打印的方式往往不太合适。现场打印可能会导致效率低下&#xff0c;尤其在伤员较多、情况紧急的时候&#xff0c;容易造成混乱和延误。 采用统一打印的方式具有诸多优势。首先&#xff0c;能够集中处理打印任务&…

猴子吃桃-C语言

1.问题&#xff1a; 猴子第一天摘下若干个桃子&#xff0c;当即吃了一半&#xff0c;还不过瘾&#xff0c;又多吃了一个。 第二天早上又将剩下的桃子吃掉一半&#xff0c;又多吃一个。以后每天早上都吃了前一天剩下的一半零一个。 到第N天早上想再吃时&#xff0c;见只剩下一个…

ctf.bugku - SOURCE

题目来源&#xff1a; source - Bugku CTF 首先&#xff0c;访问页面&#xff0c; 得到的是假的 flag &#xff0c; 查看前端页面、代码、response返回&#xff1b; 没有有用信息&#xff1b; 查后端&#xff1a; git泄露 下载git文件 # wget -r http://114.67.175.224:156…

SIE将使用AI和机器学习加速游戏开发

索尼在一份新的索尼公司报告中透露&#xff0c;PlayStation将利用人工智能和机器学习来加快游戏开发速度。在报告的第16页&#xff0c;索尼表示&#xff1a;“加强能够帮助创作者以高效、高质量的方式最大化其IP价值的技术&#xff0c;包括传感和捕捉以及实时3D处理、人工智能和…

IDM6.42免费安装破解注册(Internet Download Manager)

01 到官网或者网盘下载安装包 中文官网链接&#xff1a; https://souurl.cn/9nbWw2 优惠码&#xff1a;WMHRDIDM5 夸克网盘&#xff1a;https://pan.quark.cn/s/885c8a9e487e​​​​​​​ 02 Powershell脚本使用: 总的来说&#xff0c;这段代码的目的是通过管理员身份下…

Spring Boot助力医院数据管理

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…

SolidWorks零件图到工程图标记尺寸

SolidWorks零件图&#xff0c;生成工程图后&#xff0c;自动标注尺寸&#xff1b;&#xff08;在零件图中尺寸做了标注&#xff0c;导出成工程图后&#xff0c;尺寸不一定合适&#xff0c;需要手动挪一下位置&#xff09;

solidity中的函数详解

1.概念 在Solidity中&#xff0c;函数是智能合约的基本构建块&#xff0c;用于实现特定的业务逻辑。以下是Solidity函数的一些关键特性和详细解释&#xff1a; 函数定义; 函数由 function 关键字开始&#xff0c;后跟函数的名称、参数列表和返回值。函数可以是内部的&#xff…

PHP变量(第④篇)

本栏目教学是php零基础到精通&#xff0c;如果你还没有安装php开发工具请查看下方链接&#xff1a; Vscode、小皮面板安装-CSDN博客 今天来讲一讲php中的变量&#xff0c;变量是用于存储信息的"容器"&#xff0c;这些数据可以在程序执行期间被修改&#xff08;即其…

【自动驾驶】《Planning-oriented Autonomous Driving》UniAD论文阅读笔记

1.参考 论文&#xff1a;https://arxiv.org/pdf/2212.10156 代码&#xff1a;https://github.com/OpenDriveLab/UniAD 2.摘要 原来的自动驾驶任务都是分为模块化的&#xff0c;感知&#xff0c;预测&#xff0c;规划等。每个独立的任务可能都优化得很好&#xff0c;但可能会…

NR工作频段

NR定义了两个频率范围&#xff0c;FR1和FR2。在很多场景下&#xff0c;对于不同的频率范围&#xff08;FR&#xff09;&#xff0c;射频规范是单独定义的。NR可以工作的频率范围&#xff0c;即FR1和FR2的频率范围&#xff0c;如下表所示。 FR频率范围FR1410 MHz – 7125 MHzFR…