GRASP原则

一、Grasp
1.1简介
GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件模式,它由《UML和模式应用》(Applying UML and Patterns)一书作者Craig Larman提出。与其将它们称之为设计模式,不如称之为设计原则,因为它是站在面向对象设计的角度,告诉我们怎样设计问题空间中的类与分配它们的行为职责,以及明确类之间的相互关系等,而不像GoF模式一样是针对特定问题而提出的解决方案。因此GRASP站在一个更高的角度来看待面向对象软件的设计,它是GoF设计模式的基础。GRASP是对象职责分配的基本原则,其核心思想是职责分配(Responsibility Assignment),用职责设计对象(Designing Objects with Responsibilities)。
1.2Grasp原则
1、Infomation Expert(信息专家)
信息专家模式是面向设计的最基本原则,是我们平时使用最多,应该跟我们的思想融为一体的原则。也就是说,我们设计对象(类)的时候,如果某个类拥有完成某个职责所需要的所有信息,那么这个职责就应该分配给这个类来实现。这时,这个类就是相对于这个职责的信息专家。
示例:我们在设计购物网站的时候,为避免重复,一种商品只能在购物车中出现一次,如果多次出现,则需要将其数量增加。这个时候我们在将物品放入购物车的时候,要首先判断当前物品是否在购物车中,判断两个物品是否为同一个物品的方法这个职责应该委托给谁呢?显而易见,商品类中有唯一标识,所以这个职责由商品类实现,而不是购物车。
2、创造者原则(creator)
creator原则的本质是创建类对象职责应该委托给那个对象,也就是谁应该负责产生某个类的实例。
解决方案: 如果符合下面的一个或者多个条件,则可以将创建A的实例的职责分配给B;
a、A 是 B 的聚合
b、A 是 B 的容器
c、A 持有初始化 B 的信息(数据)
d、A 记录 B 的实例
e、A 频繁使用 B
满足上述一种或者多种情况的时候,我们应该奖创建A的实例的职责分配给B。
合理的creator原则带来的优点:如果职责分配合理,设计就能降低耦合,提高设计的清晰度,封装性和重用性。

示例:例如订单和商品的关系是聚合关系,这个时候我们将在订单中创建商品。
3、低耦合(Low coupling)
耦合是评价一个系统中各个元素之间连接或者依赖关系强弱的尺度。低耦合的原则是我们在设计系统的时候尽量降低系统中各个元素之间的耦合度,这样对于系统的理解和维护都有很大的益处。
耦合性高的系统会带来的坏处:
一个类的修改导致其他类产生较大的影响; 系统难以维护和理解; 系统的重用性差,在重用一个高耦合类的时候,不得不重用它所依赖的所有类。
低耦合模式的意思就是要我们尽可能地减少类之间的连接。
其作用非常重要:
a、低耦合降低了因一个类的变化而影响其他类的范围。
b、低耦合使用类更容易理解,因为类会变得简单,更内聚。
下面这些情况会造成类 A、B 之间的耦合:
a、A 是 B 的属性
b、A 调用 B 的实例的方法
c、A 的方法中引用的 B,例如 B 是 A 方法的返回值或参数。
d、A 是 B 的子类,或者 A 实现 B
关于低耦合,还有下面一些基本原则:
a、Don’t Talk to Strangers 原则
意思就是说,不需要通信的两个对象之间,不要进行无谓的连接,连接了就有可能产生问题,不连接就一了百了了。
b、如果 A 已经和 B 有连接,如果分配 A 的职责给 B 不合适的话(违反信息专家模式),那么就把 B 的职责分配给 A。
例如:Creator 模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。
4、纯虚构(pure Fabrication)
纯虚构原则与我们所说的纯虚函数类似。
纯虚构的作用是用来解决高内聚和低耦合之间的矛盾的。高内聚低耦合是我们系统设计的终极目标,高内聚意味着我们要将类拆分成多个功能集中的类,但是拆分的多个类之间需要进行协作才能正常工作,这样又增加了类之间的耦合性。
纯虚构原则是用来解决上述问题的方案。它要求将一部分类的职责转移到纯虚构类中,在理想的情况下,分配给这种虚构类的职责是为了达到高内聚低耦合的效果。在实际的操作过程中,纯虚构类的实现又很多种方式,例如将数据库中操作的方法从数据库实体中分离出来,形成专门的数据访问类;通过对类的分解来实现类的重用,新增加的数据访问类对应于数据的持久化存储,这是软件开发过程中为了方便虚构出来的一个概念。一般情况下,纯虚构模式通常基于功能进行划分的。
A具有一个类型为B的属性;
A调用B的方法
A的方法包含对B的引用(参数或者返回值的方式)
A是B的直接或者间接的子类
A是接口B的一种实现
在类的划分上,尽量创建松耦合的类,类之间的耦合性越低,越有利于复用,修改一个类不会影响其他类。
在类的设计上,尽量降低类中成员和方法的访问权限。
在类的设计上,尽量将类设计为不变类
在类的引用上,将一个对象对另一个对象的引用降低到最小
例如:Creator 模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。
5、控制器原则(controller)
控制器模式的实质是将一些系统事件的接受和处理委托给一个的对象controller,这个对象可以是一个类,系统或者子系统,它不与UI进行交互,它只负责系统信息的接收和处理。
一般情况下,控制器是一个系统,这个系统中包括多个处理器,分别对应处理不同的事务。通常情况下,一个控制器应当把要完成的功能委托给其他对象,而它只负责任务的协调控制和分配。

控制器原则与MVC模式相对应,MVC模式是一种比设计模式更高的架构模式。
6、中介模式(indirect)
中介模式的目的是为了避免两个对象之间产生直接耦合,降低对象之间的耦合度。
解决方案是建立中间对象来协调两个对象之间的交互,避免耦合性过高。
7、高内聚(high cohesion)
内聚是评价一个对象的职责被关联的尺度或者强弱,也可以说是功能性内聚的职责。也就是功能性紧密的相关职责应该放在同一个类中,并共同完成有限的功能。这样做更加有利于对类的理解和重用,也可以降低类的维护成本。
往往低内聚的系统设计会导致类的混乱,当对功能进行扩展或者改进的时候带来不必要的麻烦,低内聚的类也不利于重用,因为他们的职责如此之混乱。
为了达到高内聚,我们需要对类的职责进行分解,使分解出来的类具有独立的职责,满足单一职责原则。将一些需要在多个类中使用到的方法封装到一个类中,其他的类只负责他们需要负责的相关功能,这样我们可以提高类的内聚程度。
例如:一个订单数据存取类(OrderDAO),订单即可以保存为 Excel 模式,也可以保存到数据库中;那么,不同的职责最好由不同的类来实现,这样才是高内聚的设计。
8、受保护模式(protected variations)
受保护模式的实质与OCP(开放闭合原则)类似,我们首先找到系统中不稳定的变化点,使用统一的接口封装起来,如果未来发生变化的时候,可以通过扩展接口来扩展新的功能,而不需要改变旧的代码。这样达到易于扩展的目的。
9、多态原则(polymorphism)
多态原则与面向对象设计原则中的多态概念类似
例如:我们想设计一个绘画程序,要支持可以画不同类型的图形,我们定义一个抽象类 Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape 类里的Draw() 方法,这样我们就可以使用同样的接口(Shape抽象类)绘制出不同的图形。

二、设计模式学习心得
1.设计模式对面向对象设计过程中的相关问题是有多种解决方法的,我认为解决问题首先即是要根据问题本身寻找合适的对象,面向对象程序由对象组成,对象包括数据和对数据进行操作的过程。对象在收到客户的请求(或消息)后,执行相应的操作。客户请求是使对象执行操作的唯一方法,操作又是对象改变内部数据的唯一方法。由于这些限制,对象的内部状态是被封装的,它不能被直接访问,它的表示对于对象外部是不可见的。面向对象设计最困难的部分是将系统分解成对象集合。因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。面向对象设计方法学支持许多设计方法。你可以写出一个问题描述,挑出名词和动词,进而创建相应的类和操作;或者,你可以关注于系统的协作和职责关系;或者,你可以对现实世界建模,再将分析时发现的对象转化至设计中。设计的许多对象来源于现实世界的分析模型。但是,设计结果所得到的类通常在现实世界中并不存在,有些是像数组之类的低层类,而另一些则层次较高。
2.其次,我们要确定对象的粒度,它的意思即是对象在大小和数目上变化极大,它们能表示下自硬件或上自整个应用的任何事物,因而我们常常无法有效的描述这个待确定的对象,而在设计模式中,外观模式就很好地描述了怎样用对象表示完整的子系统,享元模式描述了如何支持大量的最小粒度的对象,抽象工厂模式和建造者模式产生哪些专门负责生成其他对象的对象,访问者模式和命令模式生成的对象那个负责实现对其他对象或对象组的请求。
3.我们要指定对象接口,在面向对象系统中,接口是基本的组成部分。对象只有通过它们的接口才能与外部交流,如果不通过对象的接口就无法知道对象的任何事情,也无法请求对象做任何事情。对象接口与其功能实现是分离的,不同对象可以对请求做不同的实现,也就是说,两个有相同接口的对象可以有完全不同的实现。当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关。支持相同请求的不同对象可能对请求激发的操作有不同的实现。发送给对象的请求和它的相应操作在运行时刻的连接就称之为动态绑定。动态绑定是指发送的请求直到运行时刻才受你的具体的实现的约束。因而,在知道任何有正确接口的对象都将接受此请求时,你可以写一个一般的程序,它期待着那些具有该特定接口的对象。进一步讲,动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称为多态,它是面向对象系统中的核心概念之一。多态允许客户对象仅要求其他对象支持特定接口,除此之外对其假设几近于无。多态简化了客户的定义,使得对象间彼此独立,并可以在运行时刻动态改变它们相互的关系。设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口。设计模式也许还会告诉你接口中不应包括哪些东西。设计模式也指定了接口之间的关系。特别地,它们经常要求一些类具有相似的接口;或它们对一些类的接口做了限制。
4.关注对象描述的实现,我们一般很少提及到实际上怎么去定义一个对象。对象的实现是由它的类决定的,类指定了对象的内部数据和表示,也定义了对象所能完成的操作。对象通过实例化类来创建,此对象被称为该类的实例。当实例化类时,要给对象的内部数据(由实例变量组成)分配存储空间,并将操作与这些数据联系起来。对象的许多类似实例是由实例化同一个类来创建的。新的类可以由已存在的类通过类继承来定义。当子类继承父类时,子类包含了父类定义的所有数据和操作。子类的实例对象包含所有子类和父类定义的数据,且它们能完成子类和父类定义的所有操作。这里就借用了抽象类,抽象类的主要目的是为它的子类定义公共接口。一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作。非抽象类称为具体类。子类能够改进和重新定义它们父类的操作。更具体地说,类能够重定义父类定义的操作,重定义使得子类能接管父类对请求的处理操作。类继承允许你只需简单的扩展其他类就可以定义新类,从而可以很容易地定义具有相近功能的对象族。这方面我们要重点关注类继承与接口继承的比较和我们所作的是对接口编程而不是对实现编程这两个问题。
5.合理地应用复用机制,首先面向对象系统中功能复用的两种最常用技术是类继承和对象组合,类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见。对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。继承和组合各有优缺点。类继承是在编译时刻静态定义的,且可直接使用,因为程序设计语言直接支持类继承。类继承可以较方便地改变被复用的实现。当一个子类重定义一些而不是全部操作时,它也能影响它所继承的操作,只要在这些操作中调用了被重定义的操作。但是类继承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性。子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好的结果:因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系,因此我们一般优先使用对象组合而不是类继承。其次我们要关注的是委托的使用,它使组合具有与继承同样的复用能力。在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者。它的主要优点在于它便于运行时刻组合对象操作以及改变这些操作的组合方式。
6.在设计过程中关联运行时刻和编译时刻的结构设计,个面向对象程序运行时刻的结构通常与它的代码结构相差较大,代码结构在编译时刻就被确定下来了,它由继承关系固定的类组成,而程序的运行时刻结构是由快速变化的通信对象网络组成,可见程序运行时刻和编译时刻的结构是存在很大差别的,很明显代码不可能揭示关于系统如何工作的全部。系统的运行时刻结构更多地受到设计者的影响,而不是编程语言。对象和它们的类型之间的关系必须更加仔细地设计,因为它们决定了运行时刻程序结构的好坏。
虽然在讨论设计模式的问题解决时我们可以从这六个方面深入分析,然而实际对于设计模式的使用过程中,我们要想学会灵活、科学地使用设计模式就更要深入地实验这些模式,体会不同模式之间应用场景地不同,设计模式地选择过程中,我们要理解好变化的设计思维,要积极地摆脱对特殊操作、软硬件平台、对象表示或实现和算法的依赖因素,积极理解低耦合、高内聚的思想,要结合设计目标对设计未来的变化都要充分地考虑,设计模式可以确保系统能以特定方式变化,从而帮助你避免重新设计系统。每一个设计模式允许系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮。

以下是一个简单的Grasp算法的代码示例,用于处理一个二维物体的抓取问题: ```python import numpy as np # 定义物体的形状和质心 object_shape = np.array([[0, 0], [0, 1], [1, 1], [1, 0]]) object_centroid = np.array([0.5, 0.5]) # 定义机械手的夹爪宽度 gripper_width = 0.2 # 定义抓取点的数量和分辨率 num_grasp_points = 10 grasp_resolution = 0.01 # 初始化抓取点集合 grasp_points = np.zeros((num_grasp_points, 2)) # 随机生成抓取点 for i in range(num_grasp_points): # 随机生成抓取点的位置 grasp_point = np.random.uniform(-1, 1, 2) # 将抓取点移动到物体质心 grasp_point = grasp_point - np.mean(object_shape, axis=0) # 将抓取点旋转到水平方向 angle = np.arctan2(grasp_point[1], grasp_point[0]) rotation_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]) grasp_point = np.dot(grasp_point, rotation_matrix) # 将抓取点移动到合适的位置 grasp_point[0] = np.clip(grasp_point[0], gripper_width / 2, object_shape[:, 0].max() - gripper_width / 2) grasp_point[1] = np.clip(grasp_point[1], -grasp_resolution / 2, grasp_resolution / 2) grasp_point = grasp_point + object_centroid grasp_points[i] = grasp_point # 计算抓取点的质心 grasp_points_centroid = np.mean(grasp_points, axis=0) # 将抓取点按照到物体质心的距离排序 grasp_points_distance = np.linalg.norm(grasp_points - grasp_points_centroid, axis=1) sorted_indices = np.argsort(grasp_points_distance) sorted_grasp_points = grasp_points[sorted_indices] # 输出最优的抓取点 print("最优的抓取点为:", sorted_grasp_points[0]) ``` 上述代码中,我们首先定义了物体的形状和质心,以及机械手的夹爪宽度和抓取点的数量和分辨率。然后,我们通过随机生成抓取点的位置,并对其进行一系列的变换和筛选,最终得到了一个抓取点的集合。最后,我们根据抓取点到物体质心的距离,将抓取点进行排序,并输出最优的抓取点。 需要注意的是,上述代码只是一个简单的示例,实际应用中需要根据具体的问题进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值