九大GRASP类和设计模式

面向对象设计原则是一组指导软件设计的原则,其中GRASP(General Responsibility Assignment Software Patterns)是其中的一部分。这些原则帮助设计者确定类应该负责执行哪些职责,以及如何分配这些职责。在下面的文档中,我们将详细介绍九大GRASP原则,并提供Java语言的代码示例,以便更好地理解这些原则的应用。

1. Information Expert(信息专家)

原则: Information Expert原则建议一个类应该负责处理自身拥有的信息,或者说,一个拥有必要信息的类应该负责执行相关操作。

详细说明: Information Expert关注的是哪个类在执行一个操作时最具有相关的信息。如果某个类拥有完成某个职责所需要的所有信息,那么它就是信息专家。这有助于确保每个类都在处理与自身职责相关的任务。

理解:当我们不确定某个职责该分配给类A还是类B的时候,我们可以遵循这个原则。这个设计原则和单一设计原则不同,单一职责原则考虑的是单个类中的职责是否都属于一类职责。而信息专家模式考虑则是该把该同一类职责放进类A还是类B中。假设我们有一个长方形Rectangle类(类中有width和height属性)和一个Measure类,我们应该把getArea()方法放进Rectangle中去,还是将width和height参数传给Measure类,在Measure中实现getArea()呢?依照该准则,既然Rectangle方法已经有了实现getArea()所必须的属性的话,那么就把该把getArea()方法放进Retangle类中。同理如果有一个计算属性呢?假设是长宽高比例widthHeightRatio的话,也遵循该原则。

举例: 考虑一个图书管理系统,有一个Book类表示图书信息:

class Book {private String title;private String author;// 构造函数、访问器和其他方法public void displayBookInfo() {System.out.println("Title: " + title + ", Author: " + author);}
}

在这个例子中,Book类是信息专家,因为它拥有并处理有关图书的信息。

2. Creator(创建者)

原则: Creator原则建议一个类应该创建与之关联或组合的类的实例。

如果符合下面的一个或者多个条件,则可将创建类A实例的职责分配给类B

  • B包含A;
  • B聚合A;
  • B拥有初始化A的数据并在创建类A的实例时将数据传递给类A;
  • B记录A的实例;
  • B频繁使用A。

详细说明: Creator原则指导在哪个类应该负责创建与其关联的对象实例。这有助于确保对象的创建与其使用者解耦,提高系统的灵活性和可维护性。

**理解:**在面向对象的设计当中,无法避免去创建对象。假设对象B创建对象A,那么对象B就产生了与对象A的耦合。而这种耦合是无法消除的,即使你将创建对象A的职责分配给对象C,这种耦合还是存在的,只是从对象B转移到对象C上,系统内还是依然存在这个耦合,无法避免。那么当我们无法消除耦合的时候,我们应该考虑的是如何降低这个耦合的耦合度。这个原则给出了指导方针。以上的几个条件潜在的表明了,其实B已经对A有了耦合,既然B已经存在了对A的耦合,那么我们不妨再将创建A的职责分配给他。这样分配的话,系统内仅存在一个A与B的耦合。如果将创建A的职责分配给C的话,那么系统内就会存在B与A(B包含A、B频繁使用A等条件)和C与A这两个耦合。

举例: 考虑一个在线购物系统,有一个ShoppingCart类和一个Order类:

class ShoppingCart {public Order createOrder() {return new Order();}
}

在这个例子中,ShoppingCart类是创建者,负责创建与其关联的Order类的实例。

3. Controller(控制器)

原则: 把接收或者处理系统事件消息的职责分配给一个类。这个类可以代表:整个系统、设备或者子系统;系统事件发生时对应的用例场景,在相同的用例场景中使用相同的控制器来处理所有的系统事件。。

详细说明: Controller原则有助于维护系统的一致性和可扩展性。它指导在哪个类应该负责协调和控制系统的活动。

**理解:**一个控制器是负责接收或者处理事件的组件对象。MVC模式中的C就是控制器模式。而一个控制器应该处理一类事件。例如我们项目中经常会有的UserController就承担添加用户,删除用户的事件。一个子系统需要定义多个控制器,分别对应不同的事件处理。一般来说,控制器应当把要完成的功能委托给Service或者其他业务处理对象,它只负责协调和控制业务流程,尽量不要包含太多业务逻辑。

举例: 假设有一个在线购物系统,包含ShoppingCartPaymentProcessor两个类。在这里,Controller原则建议将购物车和支付的控制逻辑分配给一个独立的控制器类,比如ShoppingCartController

4. Low Coupling(低耦合)

原则: Low Coupling原则建议尽量减少类之间的依赖关系。

以下是一些耦合关系的体现:

  • A具有一个B类型的属性;
  • A调用B的方法;
  • A的方法包含对B的引用,如方法参数类型为B或返回类型为B;
  • A是B的直接或者间接子类;
  • B是一个接口,A实现了该接口。

详细说明: 低耦合有助于系统的可维护性和可扩展性,因为当一个类的改变不会影响到其他类时,系统更容易进行修改和更新。

**理解:**在以上的这些耦合条件中,出现得越多代表耦合程度越高。这些条件简单笼统的来说就是A对B的“感知”。这种感知体现在对象属性、方法参数、方法返回值以及接口上面。高耦合的类过多地依赖其他类,这种设计将会导致:一个类的修改导致其他类产生较大影响,系统难以维护和理解。在重用一个高耦合的类时不得不重用它所依赖的其他类,系统重用性差。如何降低耦合的程度有以下一些方法:尽量减少对其他类的引用,提高方法和属性的访问权限,尽量使用组合/聚合原则来替代继承。其实面向对象编程中的多态就是一种降低类型耦合的方法,如果没有多态的话,我们的方法需要知道所有子类类型,而多态的话只需要知道父类即可。降低了类型耦合。

举例: 考虑一个图书馆管理系统,有一个Library类和一个Book类。使用聚合来减少类之间的依赖关系:

class Library {private List<Book> books;public void addBook(Book book) {books.add(book);}
}

在这个例子中,Library类通过聚合的方式引入了Book类,实现了低耦合。

5. High Cohesion(高内聚)

原则: High Cohesion原则建议一个类应该有高度相关的职责,即一个类应该专注于一个功能领域。

详细说明: 高内聚有助于确保一个类的方法和属性彼此关联,从而提高类的可读性和可维护性。

**理解:**很直观的例子就是,如果类的功能都是高内聚并职责单一的,类的复杂性就降低了,复杂性降低导致维护的成本也就降低了。在传统的Dao设计模式当中,我们应该尽量拆分细粒度职责单一的Dao供Service进行调用。在Service当中,哪一类的数据操作调用哪一个Dao就显而易见,并且单个Dao不会太过膨胀导致维护性变差。高内聚也代表了高隔离,高隔离就意味着,在修改某一个方法的时候,不至于影响到太多其他类。

举例: 考虑一个汽车管理系统,有一个Car类:

class Car {private Engine engine;private Transmission transmission;// 相关的汽车功能方法
}

在这个例子中,Car类具有高内聚性,因为它包含了与汽车相关的引擎和传动系统。

6. Polymorphism(多态)

原则: Polymorphism原则建议使用多态性来实现通用性和灵活性。

详细说明: 多态性允许以通用的方式处理不同类型的对象,从而提高系统的灵活性和可扩展性。

**理解:**在面向对象的设计当中经常要根据对象的类型来进行对应的操作。假设我们有一个画图Draw类,有多个图形类Rectangle、Circle、Square。如果要按照不同图形类进行绘制的话,就需要在Draw类的方法中使用if-else的程序结构,依次判断类型进行绘制。如果新增一个图形类的话,就又需要对这段代码进行更改。这就违反了开闭原则。而采用多态的形式,将绘制的具体步骤交给图形类的子类实现。就不用使用if-else的程序结构,在新增图形类的时候也不需要修改Draw类。通过引入多态,子类对象可以覆盖父类对象的行为,更好地适应变化。策略模式、工厂方法模式就是关于多态比较好的例子。

举例: 考虑一个图形绘制系统,有一个Shape接口和具体的实现类:

interface Shape {void draw();
}class Circle implements Shape {@Overridepublic void draw() {// 绘制圆形的逻辑}
}class Square implements Shape {@Overridepublic void draw() {// 绘制正方形的逻辑}
}

在这个例子中,Shape接口和具体的实现类展示了多态的概念。

7. Pure Fabrication(纯虚构)

原则: 原则建议可以创建一个不代表真实世界概念的类,以实现低耦合、高内聚的目标。

详细说明: 纯虚构的类可能不对应实际系统中的实体,但它们有助于组织和分离系统的不同部分,从而提高系统的可维护性。

**理解:**在OO设计时,系统内的大多数类都是来源于现实世界中的真实类(领域模型)。然而,在给这些类分配职责时,有可能会遇到一些很难满足低耦合高内聚的设计原则。纯虚构模式对这一问题给出的方案是:给人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念,而代表虚构出来的事物。比较明显的一个例子就是适配器模式,通过虚构出适配器这么一个概念来解耦两个对象之间的耦合。

适配器类举例

在适配器模式中,适配器类是一个纯虚构类,它没有对应于真实世界中的实体,而是引入为了解决两个不同接口之间的耦合问题。

适配器模式的场景:

假设有一个现有的系统,其中包含一个接口 OldInterface,而你引入了一个新的类 NewClass,它实现了一个新的接口 NewInterface。现在,你想要在系统中使用 NewClass,但是由于 OldInterfaceNewInterface 不兼容,需要一个适配器来使它们协同工作。

适配器模式的类结构:

  1. OldInterface:现有系统的接口。
  2. NewInterface:新引入的接口。
  3. NewClass:实现了 NewInterface 的新类。
  4. Adapter(适配器):这是纯虚构的类,它的唯一目的是将 NewClass 适配到 OldInterface 中。

Java 代码示例:

// 现有系统的接口
interface OldInterface {void oldMethod();
}// 新引入的接口
interface NewInterface {void newMethod();
}// 新的类,实现了新接口
class NewClass implements NewInterface {public void newMethod() {System.out.println("NewClass implements NewInterface");}
}// 适配器类,纯虚构的类,目的是适配 NewClass 到 OldInterface
class Adapter implements OldInterface {private NewClass adaptee;public Adapter(NewClass adaptee) {this.adaptee = adaptee;}public void oldMethod() {adaptee.newMethod();}
}// 客户端代码
public class Client {public static void main(String[] args) {NewClass newClass = new NewClass();Adapter adapter = new Adapter(newClass);// 使用适配器调用现有系统的接口adapter.oldMethod();}
}

在这个例子中,Adapter 类是一个纯虚构的类,它没有现实世界的对应。它的目的是通过调用 NewClass 的方法来适配 OldInterface,从而使得现有系统能够与新的类协同工作,解决了接口不兼容的问题。这就是纯虚构的一个实际应用场景。

许多项目都需要对数据库进行操作,将系统中的一些对象进行持久化。信息专家模式给出的建议是将持久化的职责分配给具体的每一个模型类。但是这种建议已经被证明是不符合高内聚低耦合原则的。于是,现在的做法往往会在项目中加入类似于DAO或者Repository这样的类。这些类在领域模型中是并不存在的。

8. Indirection(间接)

原则: Indirection原则建议通过引入一个中介者或者通过委托来降低类之间的耦合度。

详细说明: 通过间接方式减少类之间的直接依赖关系,有助于提高系统的灵活性和可维护性。

理解:“中介”简单来说就是通过一个中间人来处理一件事。本来直接联系的两个对象可以通过另一个中间对象进行交互,这样做便实现了隔离和解耦,一个对象的变动不会影响另一个对象,仅会影响到中间对象。在设计模式当中的适配器模式,桥接模式都采用了一个中间对象来进行解耦。

举例: 考虑一个购物系统,有一个PaymentGateway类:

class PaymentGateway {public void processPayment(Order order) {// 处理支付逻辑}
}class ShoppingCart {private PaymentGateway paymentGateway;public void checkout() {paymentGateway.processPayment(this.order);}
}

在这个例子中,ShoppingCart类通过间接的方式使用了PaymentGateway类来处理支付逻辑,降低了直接依赖的耦合度。

9. Protected Variations(受保护的变化)

原则: Protected Variations原则建议保护系统的稳定性,通过封装不稳定的因素。

详细说明: 当系统中的某个元素(类、模块等)可能会发生变化时,为了保护其他元素不受这个变化的影响,我们应该定义一个稳定的接口。通过这个接口,其他元素与变化点进行交互,而不是直接与变化点的具体实现交互。这样,如果未来变化发生,我们只需要修改接口的实现而不影响其他部分的代码。

解决方案:

  1. 识别不稳定的变化点: 在设计中,预先识别哪些部分可能发生变化,哪些是相对稳定的。这可以通过对需求、技术选型等方面的分析来实现。
  2. 定义稳定的接口: 为变化点定义一个稳定的接口,其他元素只与这个接口进行交互。这样,即使变化点发生了变化,其他元素也不会受到直接影响。
  3. 通过接口扩展新功能: 如果未来发生变化,我们可以通过扩展接口来添加新的功能,而不需要修改原来的实现。这种扩展性使得系统更容易适应变化。

示例:

考虑一个文件读取的例子,我们可以定义一个 FileReader 接口,并有两个不同的实现类 PlainTextFileReaderBinaryFileReader。其他模块只需要与 FileReader 接口交互,而不需要直接与具体的文件读取实现交互。如果未来需要支持新的文件类型,我们只需扩展 FileReader 接口而不需要修改其他部分的代码。

public interface FileReader {String read(String filePath);
}public class PlainTextFileReader implements FileReader {public String read(String filePath) {// 读取纯文本文件的实现}
}public class BinaryFileReader implements FileReader {public String read(String filePath) {// 读取二进制文件的实现}
}

通过这样的设计,我们保护了其他模块免受文件读取实现的变化的影响,实现了受保护变化的原则。

设计模式

适配器

问题:如何解决不相容的接口问题,或者如何为具有不同接口的类似构件提供稳定的接口?

解决方案:通过中介适配器对象,将构件的原有接口转换为其他接口

  • 将一个类的接口转换成客户希望的另外一个接口Adapter模式
  • 使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

如何体现

  • 使用多态和接口
  • 增加一层间接性对象

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

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

相关文章

leetcode 30. 串联所有单词的子串

题目链接&#xff1a;leetcode 30 1.题目 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words [“ab”,“cd”,“ef”]&#xff0c; …

PyTorch之线性回归

1.定义&#xff1a; 回归分析是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。线性回归是利用称为线性回归方程的最小二乘函数&#xff0c;对一个或多个自变量和因变量之间关系&#xff0c;进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参…

LeetCode——1962. 移除石子使总数最小

通过万岁&#xff01;&#xff01;&#xff01; 题目&#xff1a;给你一个数组。数组中的元素表示石子的个数&#xff0c;我们可以从里面移除一些元素&#xff0c;溢出的规则是第i位置的元素除2后向下取整。并且可以移除k次&#xff0c;要求最后的石子总个数最小。思路一&…

生成allure报告出现:ALLURE REPORT UNKNOWN

问题&#xff1a;点击浏览器查看时无法查看到报告 错误代码&#xff1a; if __name__ "__main__":pytest.main([./test_study/test_fixture.py])os.system("allure generate ./temps -o ./temps --clean") 结果导向&#xff1a; 解决&#xff1a;因为…

Hadoop入门学习笔记——四、MapReduce的框架配置和YARN的部署

视频课程地址&#xff1a;https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接&#xff1a;https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记&#xff08;汇总&#xff09; 目录 四、MapReduce的框架配置和YARN的部署4.1. 配置MapReduce…

动态规划03-01背包问题

问题描述 作为动态规划中最重要的经典例题&#xff0c;01背包问题开启了我们学习二维dp数组的道路。 题目如下&#xff1a; 有一个容量为V的背包&#xff0c;还有n个物体。现在忽略物体实际几何形状&#xff0c;我们认为只要背包的剩余容量大于等于物体体积&#xff0c;那就可…

缓存高可用:缓存如何保证高可用?

前面我们提到了缓存集群的负载均衡策略&#xff0c;保证缓存服务的高可用&#xff0c;集群策略是最常用的&#xff0c;本文我们以 Redis 为例&#xff0c;分析一下单点缓存如何扩展到集群&#xff0c;以及集群部署的几种常见模式。 Redis 的主从复制 集群实现依靠副本&#x…

爬虫字典生成工具,CeWL使用教程

爬虫字典生成工具,CeWL使用教程 1.工具概述2.参数解析3.使用实例1.工具概述 CeWL 是一个 ruby 应用程序,它将给定的 URL 爬到指定的深度,可以选择跟随外部链接,并返回一个单词列表,然后可用于密码破解者 Cewl 是黑客武器库中的强大工具,因为它允许创建有针对性的单词列…

如何在Spring Boot中优雅地进行参数校验

1. 前言 在平时的开发工作中&#xff0c;我们通常需要对接口进行参数格式验证。当参数个数较少&#xff08;个数小于3&#xff09;时&#xff0c;可以使用if ... else ...手动进行参数验证。当参数个数大于3个时&#xff0c;使用if ... else ...进行参数验证就会让代码显得臃肿…

使用 Spring Boot + MyBatis开发需要注意的事项以及开发模版

前言&#xff1a; 注意&#xff0c;本篇不适用于有相关开发经验的开发者&#xff0c;作为一个在职开发者&#xff0c;我经常在完成从0-1的模块&#xff0c;也就是从数据库表开始到创建实体类&#xff0c;以及dao层&#xff0c;Service层等业务需要添加相关注解&#xff0c;这样…

pytorch常用的几个函数详解

view view() 是 PyTorch 中的一个常用函数&#xff0c;用于改变张量&#xff08;tensor&#xff09;的形状。在深度学习中&#xff0c;我们经常需要调整数据的形状以适应不同的网络结构或计算需求&#xff0c;view() 函数就是用来完成这个任务的。 基本用法 view() 函数接受…

nn.LSTM个人记录

简介 nn.LSTM参数 torch.nn.lstm(input_size, "输入的嵌入向量维度&#xff0c;例如每个单词用50维向量表示&#xff0c;input_size就是50"hidden_size, "隐藏层节点数量,也是输出的嵌入向量维度"num_layers, "lstm 隐层的层数&#xff0c;默认…

Python---静态Web服务器-多任务版

1. 静态Web服务器的问题 目前的Web服务器&#xff0c;不能支持多用户同时访问&#xff0c;只能一个一个的处理客户端的请求&#xff0c;那么如何开发多任务版的web服务器同时处理 多个客户端的请求? 可以使用多线程&#xff0c;比进程更加节省内存资源。 多任务版web服务器…

计算机网络——网络层(四)

前言&#xff1a; 前面我们已经对物理层和数据链路层有了一个简单的认识与了解&#xff0c;现在我们需要对数据链路层再往上的一个层&#xff0c;网络层进行一个简单的学习与认识&#xff0c;网络层有着极其重要的作用&#xff0c;让我们对网络层进行一个简单的认识与学习吧 目…

Ubuntu:VS Code上C++的环境配置

使用 VSCode 开发 C/C 程序 , 涉及到 工作区的.vscode文件夹下的3个配置文件&#xff08;均可以手动创建&#xff09; : ① tasks.json : 编译器构建 配置文件 ; ② launch.json : 调试器设置 配置文件 ; ③ c_cpp_properties.json : 编译器路径和智能代码提示 配置文件 ; …

神经网络:机器学习基础

【一】什么是模型的偏差和方差&#xff1f; 误差&#xff08;Error&#xff09; 偏差&#xff08;Bias&#xff09; 方差&#xff08;Variance&#xff09; 噪声&#xff08;Noise&#xff09;&#xff0c;一般地&#xff0c;我们把机器学习模型的预测输出与样本的真实label…

详解FreeRTOS:专栏总述

目录 1、理论篇 2、基础篇 3、进阶篇 4、高级篇 5、拓展篇 本专栏基于FreeRTOS底层源码介绍了嵌入式实时操作系统的概念&#xff0c;FreeRTOS任务创建、任务调度、任务同步与消息传递&#xff0c;软件定时器、事件通知等知识。 主要分为5方面内容&#xff1a;理论篇、基础…

Python中json模块的使用与pyecharts绘图的基本介绍

文章目录 json模块json与Python数据的相互转化 pyecharts模块pyecharts基本操作基础折线图配置选项全局配置选项 json模块的数据处理折线图示例示例代码 json模块 json实际上是一种数据存储格式&#xff0c;是一种轻量级的数据交互格式&#xff0c;可以把他理解成一个特定格式…

spring核心组件详细分析图

文章目录 spring核心组件 spring核心组件 spring七大组成部分 1、核心容器 这是Spring框架最基础的部分&#xff0c;它提供了依赖注入&#xff08;DependencyInjection&#xff09;特征来实现容器对Bean的管理。这里最基本的概念是BeanFactory&#xff0c;它是任何Spring应用…