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

问题引出

软件开发过程中,需要设计大量的类,使他们交互以实现特定的功能性需求。但是不同的设计方式,对程序的非功能性需求(可扩展性,稳定性,可维护性等)的实现程度则完全不同。
有没有一种统一的设计方式既实现功能性需求又能满足非功能性需求?没有。开发中药遵循现有的设计原则如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 {@Overridepublic void draw() {// 绘制圆形}
}class Square implements Shape {@Overridepublic void draw() {// 绘制正方形}
}// 客户端代码,依赖于稳定的接口
class DrawingClient {public void drawShape(Shape shape) {shape.draw();}
}

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

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

相关文章

C++核心编程和桌面应用开发 第八天(继承)

目录 1.继承 1.1继承语法 1.2继承方式 1.3继承中的对象模型 1.4继承中的构造和析构 1.5继承中的同名成员处理 1.5.1同名属性处理 1.5.2同名成员函数处理 1.6继承中的同名静态成员处理 1.6.1同名静态成员属性处理 1.6.2同名静态成员函数处理 1.7多继承 1.继承 1.1继…

『网络游戏』自适应制作登录UI【01】

首先创建项目 修改场景名字为SceneLogin 创建一个Plane面板 - 将摄像机照射Plane 新建游戏启动场景GameRoot 新建空节点重命名为GameRoot 在子级下创建Canvas 拖拽EventSystem至子级 在Canvas子级下创建空节点重命名为LoginWnd - 即登录窗口 创建公告按钮 创建字体文本 创建输入…

数据结构——栈与队列的实现(全码)

一 栈的概念 栈是一种特殊的线性表&#xff0c;栈内数据遵循先进后出(LIFO)的原则&#xff0c;对于栈&#xff0c;只能在同一侧进行入栈和出栈操作。 入栈操作和出栈操作是在栈的同一侧进行的&#xff0c;如图示&#xff1a; 对于栈这种数据类型&#xff0c;我们可以采用链表或…

GSLAM——一个通用的SLAM架构和基准

GSLAM: A General SLAM Framework and Benchmark 开源地址 摘要&#xff1a; SLAM技术最近取得了许多成功&#xff0c;并吸引了高科技公司的关注。然而&#xff0c;如何同一现有或新兴算法的界面&#xff0c;一级有效地进行关于速度、稳健性和可移植性的基准测试仍然是问题。本…

数据库-分库分表

什么是分库分表 分库分表是一种数据库优化策略。 目的&#xff1a;为了解决由于单一的库表数据量过大而导致数据库性能降低的问题 分库&#xff1a;将原来独立的数据库拆分成若干数据库组成 分表&#xff1a;将原来的大表(存储近千万数据的表)拆分成若干个小表 什么时候考虑分…

mybatis自定义类型处理器

mybatis自定义类型处理器 其实使用MySQL或Oracle数据库很少会遇到自定义类型处理器的情况&#xff0c;之前是因为项目中使用了PGSQL才接触到这块的&#xff0c;这里简单做一下记录 要创建一个自定义的类型处理器&#xff0c;就需要继承BaseTypeHandler类或者实现TypeHandler接…

深度学习--------------------------------使用注意力机制的seq2seq

目录 动机加入注意力Bahdanau注意力的架构 总结Bahdanau注意力代码带有注意力机制的解码器基本接口实现带有Bahdanau注意力的循环神经网络解码器测试Bahdanau注意力解码器该部分总代码 训练从零实现总代码简洁实现代码 将几个英语句子翻译成法语该部分总代码 将注意力权重序列进…

CVSS 4.0 学习笔记

通用漏洞评分系统(CVSS)捕获了主要技术软件、硬件和固件漏洞的特征。其输出包括数字分数,表明与其他漏洞。 以下因素可能包括但不限于:监管要求、客户数量受影响、因违约造成的金钱损失、生命或财产受到威胁,或潜在漏洞的声誉影响。这些因素在CVSS评估范围之外。 CVSS的好…

基于PHP猫咖私人影院系统【附源码】

效果如下&#xff1a; 系统首页界面 用户注册界面 包厢信息界面 零食信息界面 管理员登录界面 包厢预订界面 猫咪信息界面 研究背景 近年来&#xff0c;随着生活品质的提升和个性化消费需求的增长&#xff0c;猫咖和私人影院等新兴休闲娱乐方式逐渐受到年轻人的青睐。猫咖结合…

招联金融校招内推2025

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…

复现文章:R语言复现文章画图

文章目录 介绍数据和代码图1图2图6附图2附图3附图4附图5附图6 介绍 文章提供画图代码和数据&#xff0c;本文记录 数据和代码 数据可从以下链接下载&#xff08;画图所需要的所有数据&#xff09;&#xff1a; 百度云盘链接: https://pan.baidu.com/s/1peU1f8_TG2kUKXftkpYq…

人机协作:科技与人类智慧的融合

随着科技的飞速发展&#xff0c;越来越多的领域开始借助人工智能&#xff08;AI&#xff09;和自动化技术来提升工作效率。人机协作&#xff08;Human-Machine Collaboration&#xff09;这一概念逐渐成为现代技术进步的核心。它不仅改变了我们的工作方式&#xff0c;也在重新定…

基于SpringBoot“花开富贵”花园管理系统【附源码】

效果如下&#xff1a; 系统注册页面 系统首页界面 植物信息详细页面 后台登录界面 管理员主界面 植物分类管理界面 植物信息管理界面 园艺记录管理界面 研究背景 随着城市化进程的加快和人们生活质量的提升&#xff0c;越来越多的人开始追求与自然和谐共生的生活方式&#xf…

基于YOLOv8-deepsort算法的智能车辆目标检测车辆跟踪和车辆计数

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色…

中国当代哲学家思想家教育家颜廷利:真正问题意识体现在哪些方面

在《升命学说》这部划时代的著作中&#xff0c;二十一世纪的杰出哲学家颜廷利教授提出了一个引人深思的观点。他巧妙地将阿拉伯数字“5”与汉字“吾”联系起来&#xff0c;指出两者发音相似&#xff0c;而“吾”字代表着自我。在儿童识字教学中&#xff0c;教师们常以创意的方式…

Linux操作系统——概念扫盲I

目录 虚拟机概念刨析 在那之前&#xff0c;询问什么是虚拟化&#xff1f; 现在来看看什么是虚拟机 虚拟机有啥好的 小差&#xff1a;那JVM也叫Java Virtual Machine&#xff0c;有啥区别呢&#xff1f; Reference 虚拟机概念刨析 我们下面来简单聊聊虚拟机这个概念。对于…

物联网开发者必读:从HTTP到MQTT,八大协议全解码!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! Hello 大家好,我是小米,一个喜欢研究和分享技术的IT宅。今天咱们聊点不一样的——物联网(IoT)中的各种协议!最近在研究IoT的过程中,我发现物联网领…

基于ssm vue uniapp实现的爱心小屋公益机构智慧管理系统

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然…

基于PHP的校园二手书交易管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的校园二手书交易管理系统 一 介绍 此二手书交易管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈&#xff1a;phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注…

窗口系统与图形绘制接口

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 当我们想要进行底层图形应用(GUI)开发时,往往需要用到窗口系统和图形库,这里简单介绍一下 视窗系统(window system)与通信协议 下面内容主要针对Unix-like操作系统 视窗系统是以使用视窗作为主要特征之一的图形用…