模式和原则

设计模式介绍

模式:每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心,这是模式经典定义。其实“模式”只是一个代号,就像人名,把关于“重复发生的问题的描述和解决办法”统称为模式。
“模式”其实就是一种经验的积累,就象大多数人的教育经历都是从小学到初中再到高中再到大学,这也是一种模式,是中国的教育模式。人们提到“设计模式”就是默指“面向对象设计模式”,但模式绝对不局限于此,例如《企业架构模式》,还有常用的MVC、IOC等。

比设计模式更重要:GRASP (职责分配原则)

设计模式是关于类和对象的一种高效、灵活的使用方式,就是说,必须先有类和对象,否则一切都是空谈,那么类和对象是从哪来?这就需要比23种设计模式更经典的GRASP模式登场了,GRASP(General Responsibility Assignment Software Patterns),“通用职责分配软件模式”,GRASP共包括9种模式,描述了对象设计和职责分配的基本原则。如何把现实世界的业务功能抽象成对象,如何决定一个系统有多少对象,每个对象都包括什么职责,GRASP模式给出了最基本的指导原则。GRASP是学习使用设计模式的基础。

1>Information Expert (信息专家)

设计对象(类)时,如果某个类(SKU)拥有完成某个职责(Equals())所需要的所有信息(SKUID),那么这个职责就应该分配给这个类来实现。这个类就是这个职责的信息专家。
eg>常见的网上购物车(ShopCar),需要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只需要更新商品数量即可。
这里写图片描述
Equals()要放到哪个类里来实现呢?因为要根据商品编号(SKUID)来唯一区分商品,而商品编号是只存在于商品类,所以根据信息专家模式,应把Equals()放在商品类里。

2>Creator (创造者)

实际应用中,符合下列任一条件时,都应由类A来创建类B,这时A是B的创建者:
a. A是B的聚合
b. A是B的容器
c. A持有初始化B的信息(数据)
d. A记录B的实例
e. A频繁使用B
如果一个类创建了另一个类,那么这两个类之间就有了耦合(产生依赖关系)。依赖/耦合本身没有错,但它们带来的问题就是在以后的维护中会产生连锁反应,而必要的耦合是逃不掉的,能做的就是正确地创建耦合关系,不要随便建立类之间的依赖关系,那么该如何去做呢?要遵守创建者模式原则,凡是不符合以上条件,都不能随便用A创建B。
eg>因为订单(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. 两个不同模块的内部类之间不能直接连接,否则必招报应.
eg>Creator的例子,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可降低耦合,以便降低系统复杂性.
这里写图片描述

4>High cohesion (高内聚)

给类尽量分配内聚(功能性内聚)的职责。即功能性紧密相关的职责应该放在一个类里,并共同完成有限的功能,那么就是高内聚合。这样更有利于类的理解和重用,也便于类的维护。
高内聚也可以说是一种隔离,一个部分内部发生问题,不会影响其他部分,因为高内聚的对象之间是隔离开的。
eg>一个订单数据存取类(OrderDAO),订单即可以保存为Excel模式,也可以保存到数据库中;不同的职责最好由不同的类来实现,这才是高内聚的设计.
这里写图片描述
把两种不同的数据存储功能分别放在了两个类里来实现,这样如果保存到Excel的功能发生错误,那么就去检查OrderDAOExcel类,使系统更模块化,这两个类就可以分配不同的人同时进行开发。

5>Controller (控制器)

用来接收和处理系统事件的职责,一般应该分配给一个能够代表整个系统的类,这样的类通常被命名为XX处理器/XX协调器/XX会话。
原则:
a. 系统事件的接收与处理通常由一个高级类来代替。
b. 一个子系统会有很多控制器类,分别处理不同的事务。
详细论述,参考《UML和模式应用》第16章。

6>Polymorphism (多态)

eg>设计一个绘图程序,可以画不同类型的图形,定义一个抽象类Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape类里的Draw()方法,就可以使用同样的接口(Shape抽象类)绘制出不同图形,如下图:
这里写图片描述
符合高内聚和低耦合原则,后来又增加菱形(Diamond)类,对整个系统结构没任何影响,只要增加一个继承Shape的类就行。

7>Pure Fabrication (纯虚构)

高内聚低耦合是系统设计的终极目标,但内聚和耦合永远都是矛盾对立的。高内聚会拆分出更多数量的类,但是对象之间需要协作来完成任务(高耦合),反过来毅然。解决这个矛盾就需要纯虚构模式,由一个纯虚构的类来协调内聚和耦合,可在一定程度上解决问题。
eg>上面例子,如果绘图程序需要支持不同的系统,那么因为不同系统的API结构不同,绘图功能也需要不同的实现方式,那么该如何设计更合适呢?
这里写图片描述
因为增加了纯虚构类AbstractShape,不论是哪个系统都可通过AbstractShape类来绘制图形,即没有降低原来的内聚性,也没有增加过多的耦合!

8>Indirection (间接)

事不直接办,要绕个弯才行。好处是本来直接会连接在一起的对象彼此隔离开,一个变动不会影响另一个。前面低耦合模式说“两个不同模块的内部类之间不能直接连接”,但可通过中间类来间接连接两个不同模块,这样这两个模块之间仍是没有耦合/依赖关系的。

9>Protected Variations (受保护变化)

预先找出不稳定的变化点,使用统一的接口封装起来,如果未来发生变化时,可通过接口扩展新功能,而不需修改原来旧的实现。也可以把这个模式理解为OCP(开闭原则)原则,就是说一个软件实体应当对扩展开发,对修改关闭。在设计一个模块时,要保证这个模块可在不需被修改的前提下可以得到扩展。好处是通过扩展给系统提供了新职责,以满足新需求,同时又没改变系统原来的功能。

比设计模式更重要:设计原则

模式背后都潜藏着一些“永恒的真理”,真理就是设计原则。就像人的世界观和人生观一样,那才是支配你一切行为的根本,而对于设计模式来说,为什么这个模式要这样解决这个问题,而另一个模式要那样,它们背后都遵循的就是永恒的设计原则。设计原则是设计模式的灵魂
设计原则看《敏捷软件开发—原则、模式与实践》,常用设计原则:

1>单一职责原则(SRP)

就一个类而言,应该仅有一个引起它变化的原因(正方形).也就是说,不要把变化原因各不相同的职责放在一起,因为不同的变化会影响到不相干的职责。不该你管的事情你不要管,管好自己的事情就可以了。
eg>图形计算程序只使用了正方形的Area(),永远不会使用Draw(),而它却跟Draw()关联了起来。违反单一原则,如果未来因为图形绘制程序导致Draw()产生变化,就会影响到本来毫不关系的图形计算程序。
这里写图片描述
该将不同的职责分配给不同的类,使单个类的职责尽量单一,就隔离了变化,这样不会互相影响。
这里写图片描述

2>开放—封闭原则(OCP)

软件实体(类/模块/函数等)应该是可以扩展但不可修改。意思是可随便增加新的类,但不要修改原来的类。其实还是一个隔离变化的问题。
eg>客户端程序通过数据访问接口操作数据,开始用的是SQL Server/Oracle数据库,但后来考虑到成本,改用免费的MySQL;对于客户端程序来说,后来的扩展对它没任何影响,它在不知不觉间就用上了MySQL。
这里写图片描述

3>依赖倒置原则(DIP)

抽象不应该依赖于细节。细节应该依赖于抽象。另一说法高层不应该依赖于底层,两者都应该依赖于抽象。关键要理解一点,只有抽象的东西才是最稳定的,也就是说,我们依赖的是它的稳定。如果将来“抽象”也不稳定了,那么谁稳定我跟谁,傍大款?
eg>开关跟灯直接连接在一起,也就是说开关依赖于灯的打开()/关闭(),那么如果想用这个开关也可以打开电视/音响呢,显然这个设计是无法满足,因为依赖了细节而不是抽象,这个开关已经等价于“灯的开关”。
这里写图片描述
设计一个通用的开关,现在不仅可以打开灯,还可打开电视/音响,甚至未来任何实现了“开关接口”的任何东西。
这里写图片描述

4>接口隔离原则(ISP)

不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。再通俗点说,不要强迫客户使用它们不用的方法,如果强迫,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
eg>取款/存款/转帐都使用一个通用界面接口,也就是说,每一个类都被强迫依赖了另两个类的接口方法,那么每个类有可能因为另外两个类的方法(跟自己无关)而被影响。拿取款来说,它根本不关心“存款操作”和“转帐操作”,可是它却要受到这两个方法的变化的影响.
这里写图片描述
为每个类都单独设计专门的操作接口,使得它们只依赖于它们关心的方法,这样就不会互相影响.
这里写图片描述

5>替换原则(LSP)

子类型必须能够替换掉它们的基类型。即:必须保证继承中的“ISA”关系,如果违反了LSP原则,常会导致在运行时(RTTI)的类型判断违反OCP原则。
eg>函数A的参数是基类型,调用时传递的对象是子类型,正常情况下,增加子类型都不会影响到函数A的,如果违反了LSP,则函数A必须小心的判断传进来的具体类型,否则就会出错,这就已经违反了OCP原则。

关于模式学习

深刻理解oo是学好设计模式的基础,掌握ood原则才能掌握ood模式的精髓,从而灵活运用设计模式。仅知道oo的语言机制是不够的,懂得语言里的封装/继承/多态,只是满足了最最基础的条件,要真正发挥oo的强大作用,关键是要深刻理解以上的GRASP模式和设计原则,在此基础上去再深入理解设计模式,并在实践中不断磨练。
模式跟oo原则相比其实并不重要,如果能设计出基本符合以上原则的程序,那么可能就已经总结出了新的模式,所以学习模式的根本是为了深入理解oo思想和原则,以写出高内聚低耦合的程序。
李建忠的“C#面向对象设计模式纵横谈系列课程”提出了一个“重构到模式”的理论,模式不完全是供我们套用的模版,在特定的业务环境下,我们实现的可能只是“类似XX模式”的设计模式,因为针对这个环境,这么使用就是最合适的,而不是什么时候都必须完全照搬GOF的23种设计模式的格式,模式是死的,而人是活的,找到最合适的实现方式就好,不要为了设计模式而使用设计模式。

  • GRASP和设计原则的关系。GRASP也是一类设计原则,只不过GRASP是关于“职责分配”的设计原则
  • 这里提到的原则更具体些,而“针对接口编程”、“组合优于继承”相对来说更抽象些
  • 书籍推荐:
    《UML和模式应用》
    《敏捷软件开发—原则、模式与实践》
    《Head First Design Patterns》
    李建忠老师的《C#面向对象设计模式纵横谈系列课程》

评论:
我上面写的意思是因为增加菱形类对整体结构,其他类都没有任何影响,也可是说是很符合OCP原则。“高内聚”是指各个图形类处理自己的特定功能,所以内聚,“低耦合”是指各个图形类之间没有直接都连接,即没有强耦合。

在一些开发过程中。设计并不是很明显。而是随着开发进度模式才逐渐浮现出来。在前期设计中有时候会过早的陷入细节当中。如何避免此类情况发生呢?
1、关于如何把握使用设计模式的尺度问题?
我想这应该是一个程序设计“内功”的问题,需要多年修炼才能有所心得。虽然有前人总结好的设计模式这个工具,但是如何用好设计模式又是个问题?但是在熟练使用设计模式之前,我们还有很多常用的设计原则和GRASP模式可以使用,认真体会这些原则和GRASP,其实它们就是一些约束条件,是它们在真正主宰着程序的设计是否优劣,而设计模式只是基于这些常用原则和GRASP的最佳实践而已,所以从这个角度说,要掌握好使用设计模式的尺度,必须要先深入理解本文提到的这些常用原则和GRASP模式。在这个基础之上,相信随着经验的积累,你一定会把设计模式用得越来越熟练的。
2、前期可设计内容不明显,后期才浮现出来,如何避免?
嘿!这真是一个好问题,也是我一直在考虑的一个问题,基于我目前的认识,我认为这种情况是无发避免的。OOAD的一个最根本原则就是找到系统中可能会变化的部分,将它们隔离起来,降低跟其他部分的耦合。所以这个问题也可以说成是在开发初期设计者发现“可能变化部分”的能力问题,这也是需要经验积累并不断提高的一种能力。但是即使是最优秀的设计大师也不可能在一开始发现所有的变化点,因为这是不符合事物的客观规律的,如果一开始就要实现一个100%完整全面的设计,那么这肯定是一个永远无法完成的设计。所以后来才有了《敏捷》,《重构》这些优秀的软件开发方法。这里最后还要提一下“重构到模式”的理论,也就是说模式是重构出来的,而不是一步到位的。

出处:http://www.cnblogs.com/justinw/archive/2006/11/28/574573.html#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值