关于设计的杂录、思考

来源:http://hi.baidu.com/weizhengjim/blog/item/49fc85faf6a3eb8e9f514615.html

 看了设计模式相关的书估计都半年了,平时记录了些笔记和理解,虽然凌乱了点,不过以后有机会看看也好。

设计模式和框架

框架是结构的复用,组件是功能的复用。
"框架和类库最重要的区别是:框架是一个'半成品'的应用程序,而类库只包含一系列可被应用程序调用的类。

从某种意义上说,我们也可以把"控制反转"看作是"依赖倒置"的一个特例。例如,用模板方法模式实现的"控制反转"机制其实就是在框架系统和应用程序之间抽象出了一个描述所有算法步骤原型的接口类,框架系统依赖于该接口类定义并实现程序流程,应用程序依赖于该接口类提供具体算法步骤的实现,应用程序对框架系统的依赖被"倒置"为二者对抽象接口的依赖。

总地说来,应用程序和框架系统之间的依赖关系有以下特点:
1. 应用程序和框架系统之间实际上是双向调用,双向依赖的关系。
2. 依赖倒置原则可以减弱应用程序到框架之间的依赖关系。
3. "控制反转"及具体的模板方法模式可以消解框架到应用程序之间的依赖关系,这也是所有框架系统的基础。
4. 框架系统可以独立重用。

如何实现框架中的扩展点

技术分类 回调机制 开发手段
传统编程语言 函数指针 函数指针作参数
面向对象编程语言 抽象方法(或称虚函数) 抽象基类、接口

设计模式和重构

使用设计模式跟重构一样,都是改进设计结构的方法
在设计时运用设计模式主要是满足功能上的需求,或应付以后的需求变化。在编码时使用设计模式进行重构则是因为需求变化
重构的目的是改进设计。而运用设计模式是为了解决问题,他是高质量设计的标准,所以模式是重构的目标 重构和测试驱动可以使我们每次都把注意力集中在构建过程的某一方面
重构的正确性需要测试来保证,所以测试驱动是重构的前提

阅读代码的建议

有相关文档的话,先看相关文档,最好先了解整个系统的概念模型。
先画个静态模型,然后可以用过调试来观察调用关系,从而得到动态模型

通过注释或开发者的口述可以得知每个类(对象)的作用和类之间的关系,更重要的是这些类及它们关系所表达的意图。补充光凭调试观察行为时会漏掉的行为意图
所以我们写注释时要写明意图,正如TDD强调根据意图编程,我们写注释时就应该写明,这样就可以根据注释来编程。设计时其实也一样。
如果是跟踪的话,调试比看代码更有效。不调试的话,你通常只看接口,很难看到实现(如某个子类继承的方法就很难看到,运行时具体用的是那个实现也很难看到),因为采用那个实现是运行时决定的。

找不到合适的入口,就只能在框架里打转。不要天真的以为看了些框架所使用技术的简单例子,就可以看懂一个复杂的框架,没有设计者的解释,是很难看懂框架的

要看一个类中有没有你要的方法,有时需要直接在类里面写个测试方法,然后用this.来看提示出来的方法。因为有些方法是继承自父类,光看该类很难看出有没有(但是eclipse有更好的功能来看这些信息吗?)

要懂得整体架构,一些主要的api,才能理解别人的代码是做什么的,然后去关注具体的算法

设计方法

动态模型可以表示类的行为,从而发现系统需要的类(静态模型漏掉的类)。但如果类太多,就很复杂。
静态模型可以减少类的数目,用继承可以把类组织成层次结构,而不是都堆积成一层。在静态模型上运用设计原则,会减少不必要的具体对象间关联,而转化为抽象间的动态关系。
所以 应该先画动态分析行为(避免不使用的方法),后画静态(在这过程中用静态模型来减少类的数目)

功能动态关系,结构对应静态关系
在进行架构设计时,体现为应先由要实现功能得出大概概念(可以也用CRC卡来分析需求,得出概念模型),用概念得出静态模型,再设计接口,再到实现。

一个功能需求变更时,应该只需改一处代码,这是正交性(解耦性)的体现。分层的目的就是使每个模块只依赖一个模块,而不是互相依赖
为每种变化使用不同的特化(继承),将变化转移到使用或拥有这种变化的对象中(组合)

关于设计模式

Handler(有时是函数指针)就是侦听者,就是观察者,观察者一般是接口,被观察者一般是基类

抽象工厂返回的是一组对象或提供一组方法。而建造者模式通常返回的是组装好的对象,建造者模式中的创建对象的函数采用类似模板模式的方式,子类提供具体的组装方法。不同建造者对应不同视图的对象。

工厂方法也可以把两个类层次分开。凡是工厂都可以加入模板。如抽象工厂,返回的哪一组对象就可以放入模板。工厂方法,可以在工厂所在类中加个模板方法

关于桥接。继承和委托的目的都是复用。桥接模式可以分开两边的类层次,如果不分开,会出现A*B的情况,即继承体系再继承到一起会有A*B个类。分开了之后复用的方式由继承变成委托,只需右边的子类可以组合进左边,而不需要加到左边的下一层类层次,所以出现A+B的情况。即用桥接模式或策略模式则是A+B个类。在桥的抽象一边以实现一边的实例作为成员变量,又有点像适配器、策略模式。这样做的好处可以让左边加入新类更容易,不需要为新类写B个子类。

设计模式和算法

设计模式体现了结构的美
算法和底层的知识则关注功能
设计模式是用来改进结构的范式,算法是实现任务的范式。
在概念层用设计模式视角,在底层用算法视角

设计原则则是一条条的规则。 设计模式是用于设计的一套解决方案,而不是简单的规则
一个模式应该有动机(背景),优缺点(温说过没有理解缺点等于没有理解它)

算法就是步骤序列,使用特定结构的数据集(资源)通过步骤(行为转为指令)序列来完成特定任务(解决问题),它解决问题的通常为一个项目中的最小单元
把指令抽象为步骤,把数据结果抽象为数据集,则把算法的代码抽象为伪代码(即语言)

系统论与系统设计

结构决定功能
所以在进行架构设计时,应先由功能得出大概概念(模型),用概念得出静态模型,再设计接口,再到实现。
在系统科学中,整合的主要任务,就是改善结构以优化功能。
只要是系统就必有一般系统的特征,因此本质不同的领域会出现结构的一致性
把系统论作为思维范式

《软件敏捷开发》笔记

关于敏捷
软件会随着修改发臭。敏捷开发人员知道如何防止发臭。因为他们遵循了以下步骤:
(1) 他们遵循敏捷实践去发现问题
(2) 他们应用设计原则去诊断问题
(3) 他们应用适当的设计模式去解决问题。
这个软件开发活动的相互作用的过程就是敏捷开发的设计

敏捷实践原则:
人和交互 重于 过程和工具
可以工作的软件 重于 面面俱到的文档
客户合作 重于 合同谈判
随时应对变化 重于 遵循计划

设计原则
• 单一职责原则(SRP):
– 一个类应该仅有一个引起它变化的原因。
• 开放封闭原则(OCP):
– 类模块应该是可扩展的,但是不可修改(对扩展开放,对更改封闭)
• Liskov 替换原则(LSP):
– 子类必须能够替换它们的基类
• 依赖倒置原则(DIP):
– 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
– 抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
• 接口隔离原则(ISP):
– 不应该强迫客户程序依赖于它们不用的方法。

1、SRP 单一职责原则
一个类应该只有一个发生变化的原因。如果一个类承担多于一个职责,那么职责的变化会影响类完成其他职责的能力。
2、OCP 开发-闭合原则
软件实体(类、模块、函数等)应该是可扩展,但不可修改的
Ocp建议我们对系统进行重构。关键是抽象。如策略模式
3、LSP Liskov替换原则
子类必须能够替换它们的基类型
违反了Lsp,则违反了ocp
ood中的is-a关系是就行为方式而言的(基于契约的)
4、DIP 依赖倒转
找出潜在的抽象
5、ISP 接口隔离原则
不应该强迫客户程序依赖于它们不用的方法(造成不应有的耦合)
使用委托或多继承分离接口。

关于设计
应使用uml小步进行迭代设计演进
以一点动态关系作为起点,然后探索这些动态性所蕴涵的静态关系。根据一些好的设计原则来更改静态关系,接着返回去改进动态图。

《设计模式初学者指南》笔记 处理消息的"方法"构成了对象与外部的唯一结合点。对象是一组能力,重点放在对象能做什么,而不是在如何利用数据实现能力上。 不要等你需要做什么事情的时候才向对象要信息,而是要选择拥用这些信息的对象为你服务。 一些经验法则 1、 对象是根据"合约"定义的(合约:对象所表现出的行为,不变的,暴露在外的) 2、 所有数据都是私有的(实现细节也是) 3、 变更的范围应被限制在单个类中,对象实现的方式必须可以作任何改变 4、 盲目用get、set函数有害 面向对象系统比过程更复杂,也更易维护,其思想是将系统内在必然的复杂性组织起来,而不是减少 面向对象编程最困难的问题是学习放弃对程序控制的全局性感知,而依赖于对象的局部知识来完成任务。 建模必须待在"问题域"(领域建模)而不是在实现层面上进行建模 getter方法的修改会引发其他部分的连锁反应 没有getter更易维护。用getter 是为不思考。对象在运行时的实际通信方式,这是懒惰,违背封装原则的 面向对象的设计中,用CRC卡描述用例,在问题域进行分析。 CRC卡片,即类一职责一伙伴(class Responsibmty collaborator)卡片。卡片内容:模块(类)名,职责,协作者 以CRC卡片为辅助工具的设计有以下几个步骤: (1)识别类和职责--这里首先是识别类,或者说是识别对象。在这个方法中,类与对象的区别并不严格,同时将其记录在检索卡片上,为进一步的工作打下基础。建议使用自然语言的分析方法,从用户需求规格说明书中的名词,物理和概念的实体中发现有用的类。将具有类似属性的类合成一些,引进适当的基类或子类,以形成继承关系的层次结构。然后,从用户的需求说明书中寻找对有关的信息和行为的描述,以发现职责。简单地讲,职责就是需要做的事,必须完成的任务,后要解决的问题,但不涉及到如何实现。 (2)分配职责--在这里,将职责分配到类,并记录在相应的卡片上。应尽可能将职责分布到所有的类上,而且确保行为与有关信息不要分开,与同一实体的信息要集中在一起。如有必要,有些职责可以由几个类共同承担。 (3)找寻伙伴--很少有一个类可以独自完成有用的系统功能.任何一个类在完成自己的职责时往往都需要其他类的协作。这一步就是要找到与每一个类协作的伙伴,并记录在相应的卡片上。具体做法是依次检查每一类所承担的每一项责任,看是否需要其他类的帮助来完成。如果需要,进一步确定需要其做什么。 (4)细化--模拟在执行每个基本功能时系统内部出现的场景,以此推动细化工作的进行。在这个过程中,cRC卡片是十分重要的一个工具。 对象间信息传递 法则: 传递对象(接口更好)而不是原始数据 用"压入"模型,而不是"拉出"(如getter) 基类是脆弱的(少用extends) 修改基类麻烦,委托和继承都可实现复用。但委托+接口(封装+接口)的方式更灵活 规格化继承: 某些操作,所有对象都用的放到公共基类 某些操作只是一组对象子集用到的话,则放到规格化的基类的子类
相关文章
相关标签/搜索