熟悉GRASP,并撰写心得回答设计模式是怎样解决设计问题的,在实际编程中是如何使用的?
GRASP(General Responsibility Assignment Software Patterns),中文名称为“通用职责分配软件模式”,GRASP一共包括9种模式,如何决定一个系统有多少对象,每个对象都包括什么职责,GRASP模式给出了最基本的指导原则。初学者应该尽快掌握、理解这些原则,因为这是如何设计一个面向对象系统的基础。可以说,GRASP是学习使用设计模式的基础。
1、Infomation Expert(信息专家)
信息专家模式是面向设计的最基本原则,是我们平时使用最多,应该跟我们的思想融为一体的原则。也就是说,我们设计对象(类)的时候,如果某个类拥有完成某个职责所需要的所有信息,那么这个职责就应该分配给这个类来实现。这时,这个类就是相对于这个职责的信息专家。
例如: 常见的网上商店的购物车(ShopCar),需要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只需要更新商品的数量即可。如下图:
针对这个问题需要权衡的是,比较商品是否相同的方法需要放到哪个类里来实现呢?分析业务得知需要根据商品的编号(SKUID)来唯一区分商品,而商品编号是唯一存在于商品类的,所以根据信息专家模式,应该把比较商品是否相同的方法放在商品类里。
2、Creator(创造者)
实际应用中,符合下列任一条件的时候,都应该由类 A 来创建类 B,这时 A 是 B 的创建者:
a、A 是 B 的聚合
b、A 是 B 的容器
c、A 持有初始化 B 的信息(数据)
d、A 记录 B 的实例
e、A 频繁使用 B
如果一个类创建了另外一个类,那么这两个类之间就有了耦合,也可以说产生了依赖关系。依赖或耦合本身是没有错误的,但是他们带来的问题就是在以后的维护中产生连锁反应,而必要的耦合是逃不掉的,我们能做的就是正确的创建耦合关系,不要随便建立类之间的依赖关系,那么该如何去做呢?就是要遵守创建者模式规定的基本原则,凡是不符合以上条件的,都不能随便用 A 创建 B。
例如:因为订单(Order)是商品(SKU)的容器,所以应该由订单来创建商品。如下图:
这里因为订单是商品的容器,也只有订单持有初始化商品的信息,所以这个耦合关系是正确的且没有办法避免的,所以由订单来创建商品。
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。
c、两个不同模块的内部类之间不能连接,否则比招报应!
例如:Creator 模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。如下图:
这里我们在订单类里增加了一个 TotalPrice() 方法来执行计算总价的职责,没有增加不必要的耦合
4、High cohesion(高内聚)
高内聚的意思是给类尽量分配内聚的职责,也可以说成是功能性内聚的职责。即功能性紧密相关的职责应该放在一个类里,并共同完成有限的功能,那么就是高内聚合。这样更有利于类的理解和重用,也便于类的维护。
高内聚也可以说是一种隔离,就像人体由很多独立的细胞组成,大厦由很多砖头、钢筋、混凝土组成,每一个部分(类)都有自己独立的职责和特性,每一个部分内部发生了问题,也不会影响其他部分,因为高内聚的对象之间是隔离开的。
例如:一个订单数据存取类(OrderDAO),订单即可以保存为 Excel 模式,也可以保存到数据库中;那么,不同的职责最好由不同的类来实现,这样才是高内聚的设计,如下图:
这里我们把两种不同的数据存储功能分别放在了两个类里来实现,这样如果未来保存到 Excel 的功能发生错误,那么就去检查 OrderDAOExcel 类就可以了,这样也使系统更模块化,方便划分任务,比如这两个类就可以分配到不同的人同时进行开发,这样也提高了团队协作和开发进度。
设计模式如何解决设计问题
1 设计模式怎样解决设计问题
1.1 寻找合适的对象
面向对象设计最困难的部分是将系统分解为对象的集合。
设计的许多对象来源于现实世界的分析模型,这里和领域驱动设计有点关联。分析所得到的类,很多事现实中并不存在的类。这是抽象的结果。设计中的抽象对于产生灵活的设计至关重要。就像我设计的一个流程调度模型。
1.2 决定对象的粒度
记笔记可以让我达到沉流的状态。
1.3 指定对象接口
1.4 描述对象实现
OMT表示法:
1、 对象:最上面的黑体表示类名,下面依次是操作,数据。
2、 实例化:虚线箭头表示一个类实例化另外一个对象。
3、 继承:竖线和三角表示继承关系。
4、 抽象类:类名以黑体斜体表示,操作也用斜体表示。
5、 引用
箭头加黑点表示一个类引用另外一个类。
重点:
1、 类的继承和接口继承的比较
对象的类和对象的类型的区别:
对象的类定义了对象是怎样实现的,同时也定义了对象内部状态和操作的实现。对象的类型只与它的接口有关。一个对象可以由多个类型(支持多个接口),不同类的对象可以有相同的类型。
类和类型紧密相连,类定义了对象的操作,也定义了对象的类型。
类的继承和接口的继承的差别:
c++中接口继承接近于公有继承纯抽象类。纯实现继承或纯类继承接近于私有继承。
2、 对接口编程,而不是对实现编程——面向对象设计的第一个原则
1.5 运用复用机制
1、 继承和组合的比较
继承是一种白箱复用,父类的内部细节对子类可见。
对象组合彼此不知道对方内部细节,成为黑箱复用。
继承的优缺点:
1) 子类可以直接重定义父类的操作。
2) 编译时刻决定了,无法在运行期间更改。
3) 子类要知道父类的实现细节,这样就部分破坏了封装性。子类和父类依赖过于紧密,父类的某些变化必然导致子类的变化。开发过程中遇到过类似的问题。这种依赖,限制了灵活性以及复用性。比如,服务体系中经常出现这样的问题,导致代码拷贝。
组合(通过获得对象的引用而在运行时刻动态的定义)的优缺点:
1) 对象间通过接口彼此交互。
2) 对象只能通过接口访问,不要也不能知道对方细节,这样不会破坏封装性。
3) 运行时刻可以使用另外一个对象替换这个对象,提高了灵活性。
4) 对象的实现基于接口编写,所以实现上存在较少的依赖关系。
5) 优先使用组合有助于保持每个类被封装,并被集中在单个任务上,提高整体内聚性。类和类的层次都维持一个较小的规模,
6) 基于对象组合的设计会有更多的对象(而又较少的类),且系统的行为依赖于对象间的关系而不是定义在某个类的内部。
理想的情况下,应该通过组合原有构件实现新的功能,而不是创建新的构件。
面向对象设计的第二个原则:优先使用对象组合,而不是类继承。
2、 委托
委托时一种组合方法,它是组合具有与继承同样的能力。
委托的主要优点在于它便于在运行时刻组合对象操作,以及更改操作的组合方式。它是软件更加的灵活。
和其他的技术方案相同,它也存在不足之处:增加了软件的复杂度——动态的,高度参数化的软件比静态的软件更难于理解。
3、 继承和参数化类型的
1.6 关联运行时刻的结构和编译时刻的结构
1.7 设计应支持变化
设计应该支持变化——所说的是,一个设计方案,对变化要有一定的适应性,即封装变化。
变化是导致重新设计的原因。设计要对一定范围内的变化友好。
4、 对于程序的分层设计,对于处于同一分层的模块,对外应保持一定的抽象,并且,使用同种类型的通信协议。