《软件设计精要与模式》 - 书摘精要

(P1) 软件设计不过是软件开发进程中不可或缺的一环链条而已。

 

(P2)

设计没有标准,只有目标。如果硬要制定一个标准,那么标准就是快捷、适用与优雅。

 

满足客户需求的设计就是最好的标准。

 

(P4) 在软件开发中,唯一不变的就是变化。

 

(P5) Martin Fowler 则认为,架构是系统核心而又稳定的组成部分,是系统构建的基础。

 

(P9) 设计模式正是因为解决软件开发领域中的类似问题,从而提出的解决方法,它作为面向对象编程经验的总结,是软件设计技巧中最重要的方法与原则。

 

(P11) UML 是面向对象世界的世界语,它便于程序员间无差别的交流,让其他人更容易理解你的意图。

 

(P13) 需求实则是客户假想的操作体验,如果把客户看作是测试用户的话,这些假想的操作体验,其实就是测试用例。

 

(P13) 测试驱动开发更注重细节,如果没有高屋建瓴的架构作为指导,很容易以点代面、以偏概全。

 

(P17) 封装变化需要与抽象相结合,遵循“面向接口编程”的思想,如此才能做到对扩展开放。

 

(P18)

封装变化是设计模式的核心思想。

 

合理的封装符合单一职责原则,因为类公开得越少,就说明它承担的责任越少,与其它对象之间的依赖就越少。良好的封装保障了对象的高内聚。

 

(P19) GOF认为,我们应该优先使用“合成/聚合”,而不是使用集成,这就是所谓的“合成/聚合复用原则”(Composition/Aggregation Reuse Principle)。

 

(P20) 抽象代表具体,就代表了无限多种可能。

 

(P21) 多态有机地将封装、继承与抽象结合起来。首先,它利用封装的原理,定义了对象类型;然后,又通过继承保证不同类型之间的关系;至于抽象则提供了对象多态的能力。

 

(P23)

软件设计需要经验积累,不过有时还得需要那么一点灵感。

 

面向对象设计的原则是对面向对象思想的提炼,它比面向对象思想的核心要素更具有可操作性,但与设计模式相比,却又更加的抽象,是设计精神要义的抽象概括。

 

面向对象思想就像是法理的精神,设计原则相当于基本宪法,而设计模式就好比各式各样的成文法了。

 

(P23)

面向对象设计原则:

“单一职责原则” —— 封装的思想

“开放封闭原则” —— 封装、多态的思想

“Liskov替换原则” —— 继承的规范

“依赖倒置原则” —— 多态、抽象的思想

“接口隔离原则 —— 封装的思想

 

(P27)某种程度上讲,接口就是标准,要保障接口的稳定,就应该对对象进行合理的标准。一般的设计原则之所以强调方法参数尽量避免基本类型,原因正在于此。

 

(P29)

开放利用了对象的抽象,封闭则在一定程度上利用了封装。

 

子类型能够完全替换父类型,而不会让调用父类型的客户程序从行为上有任何改变。

 

(P31)

“抽象不应该依赖于细节,细节应该依赖于抽象。”

 

抽象属于高层,细节属于低层,低层依赖于高层,而不是高层依赖于低层,这正是依赖倒置的真谛。依赖倒置原则与“面向接口编程”的思想不谋而合。

 

GOF在设计模式一书中旗帜鲜明地提出了面向对象设计的首要原则,那就是“针对接口编程,而不是针对实现编程”。

 

(P35)

只有积极得面对“变化”,方才是可取的态度。

 

设计模式是“封装变化”思想的最佳阐释。无论是创建型模式、结构型模式还是行为型模式,归根结底都是寻找软件中可能存在的“变化”,然后利用抽象对象对这些变化进行封装。

 

(P36) 从依赖关系的强弱性来看,继承的耦合度要强于组合(合成或聚合)。

 

(P49) 使用硬编码方式创建一个对象,必然会带来对象之间的具体依赖。一种最简单的方式是将反射技术与配置文件相结合,在具体对象拥有共同抽象的前提下。通过配置文件获得具体对象的类型信息,然后利用反射创建相应的对象。

 

(P53)

在.NET中,虽然可以利用<section></section>对配置文件进行分节,但终究不够直观。

 

惯例优于配置(Convention over Configuration)来源于Ruby on Rails框架的设计理念,也被认为是Rails大获成功的关键因素之一。

 

ASP.NET MVC 框架同样采用了惯例优于配置的思想。

 

(P54) 实现依赖注入的前提是面向接口编程,而辅助的技术则是利用反射技术。

 

(P67) 测试驱动开发(Test-Driven Development, TDD)是极限编程倡导的软件开发方法,提倡测试先行。测试驱动开发强调快速实现功能,再以重构改善代码的结构。

 

(P73) 当一个对象承担了过多的责任时,就有必要考虑对其进行职责划分,除非该对象是一个用于协调对象关系的类。

 

(P76) 既然有如此多的类型,且具有共同的抽象类,那么类型的创建就应该通过工厂进行管理。

 

(P79) 谨以我之愚见,思考测试驱动开发的方式,认为测试驱动开发内力精深,大约分为4种无上之力:

1. “驱动力” —— 驱动程序代码编写与设计;

2. “学习力” —— 新兵训练营之绝佳教材;

3. “自信力与他信力” —— 能将程序的Bug降到最少;

4. “控制力” —— 与设计紧密结合,牢牢控制开发过程;

 

(P82)

工厂方法模式是将创建对象实例的责任,转移到工厂类中,并利用抽象的原理,将实例化行为延迟到具体工厂类。

 

对象的创建工作必然涉及设计中的实现细节,从而导致创建者与被创建者之间耦合度增强。

 

(P102) 合成(Composite)模式体现了对象间部分与整体之间的关系,故而描述合成模式的最佳方式莫过于树形图。

 

(P104) 对于子节点的管理,合成模式提供了两种方式:一种是透明方式,也就是在根结点中声明所有用来管理子元素的方法,包括Add()和Remove()等方法。另一种是安全方式,与透明方式刚好相反,它只在枝节点对象里声明管理子元素的方法,由于叶节点不具备这些方法,调用时就不会出现透明方式的安全错误。

 

(P128) 由组合实现对象的重用,由继承实现对象的多态性,就能够完美地解决“为对象动态添加新的职责”的需求。

 

(P133) 当需要提高流的读、写性能时,不管是文件流还是网络流,.NET框架都提供了同样的方式,即通过缓冲区(Buffer)存放数据流,以达到改进性能的目的。

 

(P139) 封装意味着隐藏具体的实现,而抽象则能够隔绝接口的定义与实现。利用职责分离,以抽象方式建立对象之间关系,可以最大限度地减少彼此间的耦合程度,从而建立一个松散耦合的对象网络。我们往往将这些可能变化的对象抽象为接口或抽象类,从而将原来的具体依赖改变为抽象依赖。对象不再受制于具体的实现细节,这就代表它们是可被替换的。

 

(P156) “面向接口编程”是面向对象编程思想中最重要的一个原则。根据“封装变化”的原理,我们常常对易于变化的部分进行抽象,定义为接口。

 

(P158) 委托是一种引用方法的类型,相当于类型安全的C++函数指针。一旦为委托分配了方法,就具备了与方法完全相同的行为。

 

(P160) 接口可以被任何类与结构实现,但只能被实现为公有的实例方法;而对于委托而言,只要方法符合委托的方法签名,就可以将该方法包括静态方法,甚至匿名函数与lambda表达式传递给委托。

 

(P184) 好的设计原则是尽量使用接口,而不是抽象类。

 

(P186) 根据接口隔离原则,我认为使用多个专门的接口比使用单一的总接口要好。

 

(P189) 显式类型转换容易带来和具体类型之间的强耦合,有悖基本的设计原理。

 

(P195) 面向对象设计的本质,正是封装、继承与多态的巧妙运用。

 

(P195) David Wheeler 认为:“计算机科学中的大多数问题都可以通过增加一层间接性来解决。”

 

(P203) 在面向对象设计中,复用对象的方法除了继承,还可以采用组合。根据“合成/聚合复用原则”,应该优先考虑使用对象组合的方式。这正是对象的适配器模式的设计思想。

 

(P206)

由于对象的适配器模式采用了组合方式传递被适配对象,因此没有必要为每个被适配对象创建一个适配器类。适配器类与被适配对象之间的耦合度也随之降低。

 

我的建议是如果需要为被适配对象引入新的行为,建议使用类的适配器模式。而在其它情况下,首先考虑对象的适配器模式。尤其是当适配器类需要从多个对象中提取信息,从而用以匹配目标接口时,更应该使用对象的适配器模式,因为对象的组合关系不会受到继承的限制。

 

(P211) 适配器模式的长处在于统一两个不相吻合的接口,关进在于适配,而不是扩展。“适配”是对职责的转化,目的是为了保证与目标接口的一致性。

 

(P217) 主动出击,而非消极地应对,在设计策略上是可取的。前提是,你需要将设计模式的应用烂熟于心。

 

(P233) 接口之间的依赖关系 —— 由于接口只有定义,没有实现,因此这种依赖关系的弱依赖的。

 

(P233) 抽象工厂模式步骤:

1. 创建对象的抽象接口;

2. 创建抽象工厂接口;

3. 实现具体工厂类;

 

(P236)

由于使用了配置文件,一旦需要创建其它类型的对象,只需修改配置文件即可,源程序并不需要做任何改变,同时也避免了程序集的重新编译。

 

在面向对象设计中,需要谨记一个原则,就是“若非万不得已,我们尽量不要修改已经公布的接口。”

 

(P242) 在对类型进行抽象时,究竟应该选择抽象类还是接口,并没有一个“放之四海皆准”的标准。整体而言,一下几个原则或许值得我们重视:

1. 如果子类中有共同的实现逻辑,应优先选择定义抽象类;

2. 如果存在明显的多重继承的可能,应优先选择定义接口;

3. 如果仅需要继承接口,而不提供具体实现,应选择定义接口;

4. 其它情况,应优先选择定义抽象类;

 

(P259) 对行为进行抽象的最佳类型是接口。

 

(P270) 如果说需求是设计的前提,那么面向对象思想与设计模式就是设计的基础。只有有效而又合理地运用面向对象思想与设计模式,才能够提高代码的重用性与可扩展性,保障代码结构的简洁与优雅。

 

(P285) 在所有23中设计模式中,职责链模式的结构较为复杂,糅合了抽象、继承、封装等设计因素。而它引入的链表结构,需要创建所有职责对象,并在每个职责对象中保存下一个职责对象的引用,这对性能会造成一定的影响。但它带来的好处也是显而易见的,因为它实现了职责对象的智能查找,决定因素就在于职责的合理分配与恰当的封装。

 

(P286) 观察者模式中最关键的一点是需要对观察者(Observer)角色进行抽象,唯有如此,方能解除观察者与被观察者之间的依赖关系。

 

(P292) 观察者模式有时候也被称为“发布-订阅模式”(Publish - Subscribe)。订阅者通过订阅发布者的事件,获取发布者通知,从而更新自己的状态。

 

(P296) 在.NET中,如果需要实现观察者模式,我强烈建议利用事件与委托来完成整个设计。

 

(P313)

在设计过程中,一旦发现设计到了一个死胡同里,就应该具备壮士断腕的决心,推翻以前的设计。

 

真正的面向对象思想应该包括了3个特征:封装、继承与多态。

 

(P343) 若要构建一个与业务逻辑紧密相关的解决方案,通常会采用分层的方式将应用逻辑分解为不同的层与子系统。每一层中的组件均保持内聚性,并处于一个特定的抽象层次上。每一层均与它下面各层保持松散耦合,这就是所谓的“分层架构模式”。

 

(P345) 分层架构可以达到如下目的: 分散关注、松散耦合、逻辑复用、标准定义。

 

(P363) 应该尽量避免让SQL语句出现在数据访问层之外的模块中。

 

(P367) 应用ORM框架带来的最直接好处就是:缩短了整个项目开发的周期。

 

(P371) Transaction Scope 类实现了IDisposable接口,因此可以将创建Transaction Scope实例的代码放入到using语句中,加强对内存的管理。

 

(P388) 应用程序缓存的实现原理说来平淡无奇,主要通过ASP.NET管理内存中的缓存空间。放入缓存中的应用程序数据对象,以“键/值对”的方式存储,这便于用户在访问缓存中的数据项时,可以根据Key值判断该项是否在缓存中。

 

(P422) 利用ASP.NET的Profile和Membership特性,可以简化用户管理的实现。同时,它还能够有机的和登录控件结合,快速实现用户的注册信息和登录管理。

 

(P435)

ASP.NET控件的执行生命周期:

1.

阶段:  初始化

操作: 初始化在传入Web请求生命周期内所需的设置

重写: Init事件 (OnInit方法)

 

2.

阶段: 加载视图状态

操作: 在此阶段结束时,会自动填充控件的ViewState属性,控件可以重写LoadViewState方法的默认实现,以自定义状态还原

重写: LoadViewState方法

 

3.

阶段: 处理回发数据

操作: 处理传入窗体数据,并相应地更新属性。注意:只有处理回发数据的控件参与此阶段

重写: LoadPostData方法(如果已实现IPostBackDataHandler)

 

4.

阶段: 加载

操作: 执行所有请求共有的操作,如设置数据库查询。此时,树中的服务器控件已创建并初始化,状态已还原并且窗体控件反映了客户端的数据

重写: Load事件 (OnLoad方法)

 

5.

阶段: 发送回发更改通知

操作: 引发更改事件以响应当前和以前回发之间的状态更改。 注意:只有引发回发更改事件的控件参与此阶段

重写: RaisePostDataChangedEvent方法(如果已实现IPostBackDataHandler)

 

6.

阶段: 处理回发事件

操作: 处理引起回发的客户端事件,并在服务器上引发相应的事件。 注意:只有处理回发事件的控件参与此阶段

重写: RaisePostBackEvent方法(如果已实现IPostBackEventHandler)

 

7.

阶段: 预呈现

操作: 在呈现输出之前执行任何更新,可以保持在预呈现阶段对控件状态所做的更改,而在呈现阶段所做的更改则会丢失。在此阶段后,自动将控件的ViewState属性保持到字符串对象中。此字符串对象发送到客户端并作为隐藏变量发送

重写: PreRender事件 (OnPreRender方法)

 

8.

阶段: 保存状态

操作: 回来为了提高效率,控件可以重写SaveViewState方法以修改ViewState属性

重写: SaveViewState方法

 

9.

阶段: 呈现

操作: 生成呈现给客户端的输出

重写: Render方法

 

10.

阶段: 处置

操作: 执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如数据库连接。

重写: Dispose方法

 

11.

阶段: 卸载

操作: 执行销毁控件前的所有最终清理操作。控件设计通常在Dispose方法中执行清理操作,而不处理此事件。

重写: UnLoad事件 (OnUnLoad方法)

你可能感兴趣的:(设计)