Head First 设计模式学习笔记

 

1.         设计原则:找出应用中可能变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
这真是一个经典的原则。我现在能想到的一些代码利用的思想,几乎都有这种原则的影子。
a)         函数,把一些具有相同接口(返回值——输出和形参——输入)的模块抽出来,就是为了适应未来算法的变化。同时,有了更好的代码复用。
b)        模板思想
c)        继承中的抽象函数,虚拟函数的重写还不算,那只是代码复用,因为基类中的方法和继承类中的方法代码没有分离,只是可以重用。
d)        这是区别是否可以“硬编码”的标准。所谓硬编码,就是在语句中就指明类型名。当我们判断这个语句未来会发生改变或扩展时,就不要用硬编码来 new 一个对象,而应该考虑工厂等创建模式来动态创建了。
e)         大多数的软件设计与原则,都是着眼于变化这个主题。
2.         针对接口编程,而不是针对实现编程
a)         从这个意义上讲,公用继承(接口复用)比私有继承(实现继承)有用的多,因为前者体现了接口的契约关系。可能就是这个原因, JAVA 中的继承都是公有继承。
b)        类的私有和受保护成员都是为了实现而写的代码,公有函数才是接口。从这个意义上看,每个成员变量都应该设置为私有,因为成员是类的数据部分,而不是接口,如果要让数据成为接口,最好编写 get 方法。 C# 的属性概念确实简洁又高效。
c)        行为有接口,即他的声明,类也有接口,但行为本身不能继承,因此,把行为包装进一个类,会是一个好的办法,这就是策略模式。
d)        针对接口编程,也就是说要针对超类(基类)编程,执行时会根据实际情况执行到真正的行为。
e)         类、接口与类型的真正函义:一个类的类型是其公共接口,类还有其他的实现的部分,包括私有和受保护的成员变量和成员函数。面向对象中的接口( interface )与类的区别:前者更清晰简洁,运行效率也更高,他只是对实现了此接口的类的一种接口的规定。而类(如抽象类)更为灵活,我们讲继承类扩展了基类。而实现类实现了接口。当有一些代码可以放在基类中以供所有子类复用时,可以考虑使用基类,而不用接口,因为此时的复用是基于实现的,而非接口。当设计的一个类可能要继承多个接口或类时,请考虑使用接口,而非抽象类。因为 JAVA C #中没有多重继承,即使 C ++支持多重继承,但这也不是一个好的选择。
3.         设计原则:多用组合,少用继承。
a)         继承是类的静态行为,在编译时就确定了不会变;而组合是对象的动态行为,在运行时才确定。
b)        使用对象的组合来设计可以更灵活,更有弹性。这是因为你只需要调用组合类的接口即可完成接口的“包装”,更有弹性。虽然与继承相比,它多了一个对象的引用,但只是一个对象的引用而已,这可以使得在运行时使这个引用指向不同的子类,而获得在运行时确定行为时改变行为的弹性。
c)        使用类的继承可能会增加大量的类,导致管理不太方便。
d)        继承与组合的共同点是都可以代码复用的产生新的行为,只是前者可能局限于“覆盖重写”,而后者则是接口调用重新包装。
4.         模式使用的优点
a)         可以增加程序员沟通的共享词汇。不过你的词汇应当尽量准确哦。
b)        可以应付未来的变化,你应当清楚,这种变化是需求(或可能需求)的一部分,否则不应该为了使用模式而使用模式。设计的第一原则应当是简单。
c)        模式中有大量的 OO 设计原则,但并不意味着你懂了一些面向对象编程后就可以忽略模式,因为模式是某种经常出现的情境下为了解决某个问题的经验总结,是一种经验。
5.         观察者模式――让你的对象知悉现况
a)         有一个主题类(在有多个同类主题时,可以使用继承来获得多态的行为),是被观察的对象和数据。接口应该有注册、移除和通知观察者(如果有继承,则这些接口应当在基类中可以设置),成员应该有要观察的数据和观察者的链表。(这些最好在子类中设置,如果有几类观察者,则可以设置多个观察链);观察者至少实现 update 这个接口,此接口会在主题类的通知中被调用。
b)        MVC 中的观察者是视图和控制器,主题是模型。其中视图中还有 display 功能。
c)        传递参数可以有两种形式,要么把观察者只把所需要的参数直接传给观察者,要么把主题的引用直接传递过去,等待观察者自己去下拉数据。前者可能会高效一些,但不够灵活,因为参数的种类未来可能会改变,解决的方法之一可以对一类观察者使用一类对象,消息传递参数类本身也可以有继承的关系。后者虽然更有弹性一些,但需要观察者自己下拉数据(去调用主题类接口获得)。
d)        函数的类对象(策略模式中也出现过)也可以是观察者, C #中的事件委托机制中的委托和 JAVA 中的监听者都是观察者。
6.         设计原则:为了交互对象之间的松耦合设计而努力
a)         例如观察者和被观察者之间,他们没有任何的耦合。主题类只需要调用观察者的 update ,其观察链也可以是 object 类型。而观察者也只只需在初始时调用主题类的注册接口将自己注册一下就可以了。
b)        文件,尤其是字符文件(比如 html xml 文件),以及所有的“标准化”所做的努力也促使了各个模块、各个系统以及平台之间的松耦合。
c)        策略模式也具有某种程度上的松耦合的努力,主体类和行为类之间不是一一的依赖关系,主体类只需行为类提供的一类操作中的一者即可。
d)        当然,耦合的同时,需要双方遵守双方的接口约定。所有“标准化”、可移植和松耦合后面的原则。
7.         装饰模式――装饰对象(组合使用的强大威力的展现)
a)         使用组合的方式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
b)        组件类和装饰者的基类同时继承自同一个基类,这是为了使得装饰者和被装饰者类型可以匹配,最后可以等同或者叫透明的对待两者,所有装饰者继承自同一个装饰者基类。内组合一个被装饰者,使用委托的形式递归调用被装饰者的方法。比如计算一个商品的价钱,便可是是商品基本组件的价格加上其装饰者的价格(递归)。
c)        装饰者模式是针对抽象组件编程,而不是很特殊的具体组件。所谓抽象,这里指的是所有的组件和装饰者在完成同一接口功能上(完成此功能正是装饰的目的)是抽象统一的。比如价钱的计算,你不能突然有一个商品可以打折,如果考虑打折,则应该把折扣也考虑为抽象的内容之一。
d)        JAVA 中的 I/O 库很多都是用来包装 InputStream 的。
e)         注意:专注自己的责任,不仅每个人都应该如此,类也是如此。每个装饰者都应当只关注自己的工作,超出其工作范围的功能最好不要去做。
8.         设计原则:开放-关闭原则:类应该对扩展开放,对修改关闭
a)         所谓对扩展开放,就是对未来可能会扩展变化的地方一定先考虑好如何应付这种变化的策略。所谓对修改关闭,其实和前者强调的意思是一致的。对扩展开放的同时就是为了避免未来的扩展来会修改甚至重构整个代码――这是一个好的软件工程所要避免的事情。
b)        注意:此原则和模式的使用一样,可能会导致设计更为抽象,也更复杂,再次强调设计是为了满足需要中的最简单性。
c)        装饰者模式非常符合开放-关闭原则。因为他对应用的扩展(如功能的扩展)完全开放,你可以任意组合装饰者产生新的对象而无需修改代码。
9.         简单工厂、工厂方法、抽象工厂
a)         简单工厂只是把创建产品的代码放到了一起,可以包装成一个类也可以是一个函数,这也是代码复用最初的想法。
b)        工厂方法:利用类的继承,把产品的创建延迟到子类中去。即在超类中有一个实现,他是调用的一个抽象方法,但这个抽象的创建方法在实际运行的过程中,可能是子类中的经过重写后的创建具体产品的方法。其中在创建者类与产品类(创建方法所返回的类型)可以是一一对应的关系,或者说是平行的关系。双方都使用继承来实现。工厂方法的定义是:定义了一个创建对象的接口,但由子类决定要实例人的类是哪一个。工厂方法小类把实例化推迟到子类。也就是说,工厂方法让子类决定发实例化的类是哪一个,但这里说的决定不是指运行时刻决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个,此时(编译时)选择了哪一个类,自然就决定了产生哪一个产品。
c)        抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。抽象工厂允许客房使用抽象的接口来创建一组相关的产品,而不需要(甚至不关心)实际产出的具体产品是什么,这样一来,客房就从具体的产品中被解耦。具体的方法是:利用组合的方式,将工厂组合进创建者。工厂本身采用继承的方式,继承自同一接口或抽象类,这些接口中含有一系列此类产品的创建方法。单个产品的创建也可以利用工厂方法的方式。即此时抽象工厂的方法是创建者,产品是被创建者。
d)        三者的区别:
                         i.              简单工厂只是简单的代码复用
                       ii.              工厂方法用的是继承,而抽象工厂的是组合。前者创建新对象时,需要扩展一个类,并喜笑颜开它的工厂方法。而后者提供了一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。要想使用这个子类来创建具体的产品,必须先实例化一个此子类的对象并传入到创建方法中,这个创建方法使用的创建语句都是针对前面所述的抽象类型的。
                      iii.              抽象工厂的优点是可以创建一个产品家族,缺点是如果这个家族中有新的产品加入,则需要修改接口,所有需要一个大的接口。而工厂方法则只需要创建一个产品,因此只需要一个方法就可以了,当然就不用修改接口了。
10.     设计原则:依赖倒置原则:要依赖抽象,不要依赖具体类。
a)         比如在工厂方法中,如果不使用工厂方法,而在一个基类(抽象层次较高)根据参数不同来创建不同的产品(具体类),那就违反了些原则。而使用工厂方法便可以轻松的解决这个问题。
b)        几个指导性原则:
                         i.              变量不可以持有具体类的引用,比如使用 new ,此时可以改用工厂的方式。
                       ii.              不要让类派生自具体类。否则会依赖具体类,请派生自一个接口或抽象类。基类已实现的方法,应该由所有子类共享。
                      iii.              不要覆盖基类中已实现的方法,解释同上。
11.     单件模式――独一无二的对象
a)         使用一个静态方法返回一个静态对象,同时把构造器设置为私有。如果要考虑继承单件类,请你考虑是不是真的必须用单件模式?
b)        注意多线程情形
                         i.              使用 getInstance 同步,如果 getInstance 用得不多,这个方法简单又可行。
                       ii.              使用急切创建实例,而不是延迟创建实例。所谓延迟创建,即是在方法中,如 getInstance 中创建,只需要加一个条件判断(如 if object != null )),这可以节省很多静态实例的创建,但会带来多线程时的问题。急切创建实例和此相对,就在编译期间,即在类中就已经创建了一个对象。 JVM 保证在所有的方法运行前一定已经创建了此对象。 C ++也有此保证, C ++中静态实例的创建,如果是方法中的静态实例,则在方法第一次运行时创建,如果是类中的静态实例,则会先于运行此类的任何方法被创建,具体何时创建,我去查查书。 J
                      iii.              用双重加锁的方式来减少同步。不过这个在 JAVA   5 中才被支持。 C ++中如何支持我也不清楚。去查查书吧。
c)        不要让你的程序依赖于静态对象的初始化次序。否则会出现“意外”的 BUG
12.     命令模式――封闭调用
a)         将请求封闭成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式支持可撤销的操作。
b)        实现:调用者拥有或产生一个具体的命令对象,(产生的命令对象可以放入到命令链表中管理),具体的命令对象实现一个命令接口,在具体命令中,一般还会有一些命令执行的对象,命令执行就是调用是此对象的某个方法。
c)        多个批处理命令也可以是一个命令。
13.     适配器模式与外观模式
a)         适配器(对象)模式:通过组合的方式,把已有的一个类的接口转化为另一种接口。
b)        外观模式:把复杂的实现和接口隐藏起来,只提供所需功能(外观)的接口。
c)        适配器的类模式:使用继承(可能要用多继承)的方式,即同时继承自被适配者和目标类,这个适配器便可以同时有两个接口了。
14.     设计原则――“最少知识原则”:只和你的谈话,不用知道的就别去知道。
a)         这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。这和“最少依赖原则”是一致的。
b)        在对象的方法内,我们应当调用以下范围的方法
                         i.              该对象本身的方法
                       ii.              被当作和方法的参数而传递进来的对象
                      iii.              此方法所创建或实例化的任何对象
                     iv.              对象的任何组件
c)        最好不要调用另一个调用返回的对象的方法。
d)        外观模式是最少知识原则的代表,因为他产生一个接口,这个接口正是用户所要用的,用户无需其他类。
15.     模板方法模式――封装算法
a)         在基类中定义好算法框架,但其中的一些“步骤”中的算法是调用是某个子类的方法。这其实实现了一种钩子。
b)        注意遵守好莱坞原则
c)        模板方法模式和策略的区别在于,模板方法是定义好了框架,让子类去实现其某些钩子,而策略模式的重点在于有了一系列的算法,让方法去“挑选”。还有,模板方法使用继承,抽象类的方法保持算法框架,子类去重写其中的某些步骤,而策略模式则使用对象组合,在运行时可以选择使用一组算法中哪一个;
16.     好莱坞原则――基类(抽象层次较高的模块)可以调用子类的方法,他告诉子类“不要调用我们,我们会调用你”。因为客房代码只依赖于超类。
17.     迭代器与组合模式
a)         迭代器对集合使用一种共同的迭代接口,而不暴露对象集合中的对象。
b)        组合模式使得单个组件与集合组合统一、透明的处理,实现的方式是两者继承自同一基类(如组件类),在基类中提供诸如 add,move,getChild 之类的接口。当两者的接口不一致时,如叶结点(即单一组件)没有; add 之类的接口,此时可以什么都不做,或者返回 false ,或者抛出异常(不太好,不过可以在此层就把他处理掉)。
18.     状态模式――记录事物的状态,并根据状态“选择”不同的行为
a)         实现:所有的状态都继承同一个接口或抽象类,在事物的内部拥有一个状态对象,此对象的实际运行类可能是具体状态类中的某一个。在具体状态类中,实现了事物的所有行为,只是不同的状态行为不同罢了。
b)        类图和策略模式一样,其不同之处在于,状态模式中,状态的改变是在状态类中进行,而策略模式往往是用户自己选择实例某种算法。
19.     代理模式――控制对象访问:为另一个对象提供一个替身或占位符以控制对这个对象的访问。
20.     MVC ――模式的模式,复合模式之王!
a)         策略模式:视图和控制器实现了经典的策略模式:视图是一个对象,可以被高速使用不同的策略,而控制器提供了策略,一个视图可以有多个控制器。
b)        观察者模式:模型实现了观察者模式。观察者是视图和控制器,主题是模型。
c)        组合模式:视图中的组件的关系就是组合模式。 
d)        JSP/servlet model2 中, JSP 为视图, servlet 是控制器,而后台的数据库及其业务逻辑是模型。控制器的处理结果往往以 JavaBean 的形式打包和获取。
21.     模式是某种情境下,针对某问题的某种解决方案。
a)         情境就是应用某个情况和环境。
b)        问题就是你想在某情境下达到的目标,但也可以是某种情境下的约束,解决方案就是在此情境下,用来解决约束、达到目标。
c)        先研究已有的模式,再去发现自己的模式。(先看看别人的成果,消化吸收再创造自己的成果――积累与创新!)
d)        模式的三重境界
                         i.              模式是好东西,所以我要多用。
                       ii.              模式是好东西,我在需要时就使用,并且要用合适的模式。
                      iii.              致力于能解决问题的最简单方案。权衡的智慧哲学。所有的事物――自然而然。
22.     桥接模式――让抽象的界面和具体的实现都可以改变
a)         实现的方法:抽象界面可以有一个实现基类之引用。通过调用其接口来产生界面。界面也可以继续有继承,可以加入新的功能甚至引用。
b)        比较复杂,经常用在多个平台的图形和窗口系统上。
23.     生成器模式:使用生成器模式( Builder )封装一个产品的构造过程,并允许按步骤构造。
a)         创建者有一个 Builder 引用,可顺序的调用 Builder 的各个接口来有步骤的构造整个过程。 Builder 有具体生成器的子类。
b)        经常被用来创建组合结构
24.     责任链――让一个以上的对象有机会能够处理某个请求。
a)         所有处理类都继承自同一个基类。这个基类中含有一个自己类型的实例。
b)        请求链中的每个请求会给下一个请求处理,如果处理不了就给下一个。
c)        经常被用在窗口系统中,处理鼠标和之类的事件。( MFC 中的消息传递好像就是这个模式,能处理的就处理,不能处理的就给其基类,不过 MFC 将连成了一个链表)
d)        并不保证请求一定会被执行。
e)         可能不容易观察运行时的特征,有碍于除错。
25.     解释器:为语言创建解释。
a)         将每个语法规则表示成一个类,所有的类都继承自一个基类,基类中有一个基类类型(当然,运行时可能是其子类)的对象。
26.     中介者――集中相关对象之间复杂的沟通和控制方式。
a)         每个对象在自己状态改变时告诉中介,每个对象对中介发出的请求做出回应。
b)        让所有相关的类解耦,只和中介者通讯。
27.     备忘录――在关键对象返回前保存其状态,以支持撤销。
28.     原型――当创建给定类型的实例的过程很昂贵或很复杂(如类太多时,继承链太复杂时)时使用。
a)         使用对象的 clone 方法来实现
29.     访问者――让导游来响应访问者对象的访问。
               2007-12-5 於北大图书馆
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值