《Head First设计模式》总结

1、模式(Pattern)定义 

策略(Strategy)模式:定义一组算法族,分别封装起来,让各个算法之间可以相互替换。此模式让算法的变化独立于使用算法的客户。

观察者模式:定义了对象之间的一对多依赖关系,当一个对象(主题对象)的状态改变时,它的所有依赖者(观察者对象)都会收到通知并自动更新。 

装饰者模式:动态地将责任加到对象身上。如果要扩展功能,装饰者模式提供了比继承更有弹性的替代方案。 

用静态方法定义的工厂被成为静态工厂,这样就不用使用创建对象的方法来实例化对象,使用方便。但是这样做的缺点是无法通过继承来改变创建方法的行为。 

*简单工厂不是一种设计模式,但是它比较常用。 

工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样,客户程序中关于超类的代码就和子类对象的创建代码解耦(Decouple)了。 

工厂方法的定义:abstract Product factoryMethod(String type); 

工厂(Factory Method Pattern)方法模式:定义了一个创建对象的接口,但是由子类来决定要实例化的类是哪一个。它让类把实例化推迟到了子类。 

抽象工厂模式:提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。 

单件(Singleton)模式:确保一个类只有一个实例,并提供一个全局访问点。 

命令(Command)模式:将“请求”封装成对象,以便使用请求、队列或日志来参数化其它对象。命令模式也支持可撤销的操作。 

适配器模式:将一个类的接口,转换成客户希望的另一个接口。适配器让原本接口不兼容的类合作无间。 

外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。 

迭代器模式:提供一种顺序访问集合对象中各个元素的方法,而又不暴露其内部的表示(也就是数据结构)。 

组合模式:将对象组合成树状结构来表现“整体/部分”的层级结构,让客户以一致的方式来处理个别对象以及对象组合。 

模板方法模式:在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法可以让子类在不改变算法结构的情况下,重新定义算法中的某些步骤。 

状态(State)模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 

代理模式:为另一个对象提供替身或占位符以控制对这个对象的访问。 

复合模式在一个解决方案中结合两个或多个模式,以解决一般或重复发生的问题。 

 

*视图(View):用来呈现模型。视图通常直接从模型中取得它需要显示的数据和状态。 

*控制器(Controller):取得用户的输入并解读其对模型的含义。 

*模型(Model):模型持有所有的数据、状态和程序逻辑。模型没有注意到视图和控制器,虽然它提供了操纵和检索状态的接口,并发送状态改变通知给观察者。 

 

模式:是在某种情境下(Context),针对某个问题的某种解决方案。 

反模式:告诉你如何采用一个不好的解决方案解决一个问题。 

 

2、面向对象设计原则 

封装变化--把软件中那些在将来可能产生变化的地方独立出来,与其他部分分割以减少变化时对它们的影响。这样的设计可以使系统变得有弹性,更好地应对变化。 

针对接口编程,而不针对实现编程。依据该原则,声明一个变量时要把它声明为超类型(接口或抽象类),而不是实现类。 

多用组合,少用继承。使用组合的方式可以实现代码的分割,使代码有更大的弹性,更好地提高了复用性。 

努力在交互对象之间实现松耦合,使它们之间的互相依赖降到最低,从而提高可复用性。 

类应该对扩展开放,对修改关闭。这就是我们常说的开放-关闭原则。 

要依赖抽象,不要依赖具体类。这个原则又被称为“依赖倒置原则(Dependency Inversion Principle)”。 

 

*遵循依赖倒置原则的三个指导方针: 

(1)变量不可以持有具体类的引用。这可以通过使用工厂避开。 

(2)不要让类派生自具体类。否则就会依赖具体类,违反了“针对接口编程,而不是针对现实编程”的软件设计原则。 

(3)不要覆盖基类中已实现的方法。出现这样的情况就说明基类设计的有问题。 

要减少对象之间的交互,只留下几个“密友”。这个原则被称为“最少知识(Least Knowledge)原则”,它告诉我们只和自己的密友谈话。 

 

*通过只调用以下几种范围内的方法可以做到尽量遵循“最少知识原则”:

(1)该对象本身 

(2)被当做方法的参数而传递进来的对象 

(3)此方法所创建或实例化的任何对象 

(4)对象的任何组件,比如类或对象本身的变量,或常量 

 

*最少知识原则的不同名称:(Principal of) Least Knowledge,(The) Law of Demeter,迪米特法则,得墨忒耳法则 

一个类应该只有一个引起变化的原因。 

别调用(打电话给)我们,我们会调用(打电话给)你。这个原则被成为好莱坞原则。 

 

3、模式使用事项 

*装饰者模式的几个缺点: 

(1)有时在设计中加入大量的小类,变得不容易理解。 

(2)有的客户端代码依赖于特定的类型(这是个比较糟糕的习惯,违反了“针对接口编程,而不是针对实现编程”的设计原则),当服务器端引入装饰者模式时,客户端就会出现状况。 

(3)装饰者模式使得实例化组件的复杂度提升。 

PS:工厂(Factory)模式和生成器(Builder)模式对于装饰者(Decorator)模式的这些缺点会有所帮助。 

*空对象(null object)可以用于返回无意义的对象时,它可以承担处理null的责任。有时候空对象也被视为一种设计模式。 

*宏命令(Macro Command)是一个命令队列,它包含了一组实现了同一个命令接口的类。 

*在调用者中用一个堆栈记录连续执行的命令,这样就可以实现每按一次按钮就执行一次撤销操作的连续撤销功能。 

*适配器(Adapter)类看起来很像命令(Command)模式中命令接口的实现类,只不过它不被作为参数传递。 

*类适配器是基于多重继承实现的,因为Java不支持多重继承,因此无法做到。 

*装饰者(Decorator)模式与适配器(Adapter)模式的区别 

(1)装饰者模式与“责任”相关,当涉及到装饰者时,就表示有一些新的行为或责任要加到设计中。 

(2)适配器允许客户使用新的库和子集合,无须改变“任何”已存在的代码,由适配器负责转换即可。 

(3)装饰者不会改变接口,而适配器会改变接口。 

(4)装饰者的工作是扩展被包装对象的行为或责任,并不是“简单传递”就算了。 

(5)装饰者(Decorator)模式与适配器(Adapter)模式的最大不同在于使用它们的意图(或目的)。 

 

*使用最少知识原则的缺点是:更多的“包装类”被创造出来,以处理和其它组件的沟通。这可能导致复杂度和开发时间的增加,并减低运行时的性能。 

*组合(Composite)模式牺牲了单一责任设计原则,换取了透明性(Transprency)。 

*空迭代器(Iterator)是空对象(null object)“设计模式”的又一个例子,之前的例子是“空命令(NullCommand)”。 

*为了保证模板方法定义的算法步骤不被改变,模板方法被声明为final的。 

*钩子(hook)就是回调函数,它可以作为条件影响模板方法类中算法的流程。 

 

代理模式有很多变种,几乎都与控制访问有关,它控制访问的几种方式: 

一、远程代理控制远程对象的访问。 

二、虚拟代理控制创建开销大的资源的访问。 

三、保护代理基于权限控制对资源的访问。 

 

 

*不把控制器的代码(解读视图的输入并操纵模型)放到模型中的原因有两个: 

一、会让模型的代码更复杂。模型将具有两个责任,不但要管理用户界面,还要处理如何控制模型的逻辑。 

二、会造成模型和视图之间的紧耦合,降低了可复用性。通过模型和视图之间的解耦,使设计更有弹性和容易扩展,能容纳改变。 

 

4、其他重要知识 

*面向对象(OO)的四个基本概念是:抽象、封装、继承、多态。 

*继承的好处是实现了代码的复用。 

*良好的OO设计必须具备可复用、可扩展、可维护三个特性。 

*引起代码修改的几种情况: 

1)客户要求不同的做法或新功能。 

2)数据库产品发生改变导致数据格式不兼容。 

3)协议有了新版本。 

4)开发人员水平有了提升,重新实现。 

 

*观察者模式实现了主题对象与观察者对象之间的松耦合,当有新的观察者时,无需修改主题对象的代码,只需要新的观察者对象实现接口。在程序运行的过程中,可以随时注册和删除观察者而不影响主题对象。 

*Java内置了对观察者模式的支持:java.util.Observable类和java.util.Observer接口。 

*java.util.Observable类的局限: 

一、它是一个类,而不是接口,由于Java不支持多重继承,所以主题类无法同时拥有它和另一个超类的行为,这限制了Observable类的复用潜力。违反了“针对接口编程,而不是针对实现编程”的软件设计原则。 

二、它的某些如setChanged()这样的方法被定义为protected,要使用它们就必须继承Observable类,这违反了“多用组合,少用继承”的软件设计原则。 

如果上面两条限制妨碍了你的使用,就应该考虑自己设计实现观察者模式。 

 

*在观察者模式中,传递数据的方式有“推”和“拉”两种,Java内置的实现支持这两种方式,然而较常用的为“推”数据。 

*观察者模式以松耦合的方式在对象之间传递状态,MVC是其代表。 

 

*利用组合(composition)和委托(delegation)可以在运行时实现继承行为的效果,动态地给对象加上新的行为。 

*利用继承扩展子类的行为,是在编译时静态决定的;利用组合的做法,可以在运行时动态地扩展对象的行为。 

*装饰者模式中,装饰者可以在被装饰者的行为之前或之后,加上自己的行为,以实现特性的目的。 

*针对接口编程可以隔离掉系统以后可能发生的一大堆改变。 

*所有工厂模式都用来封装对象的创建。 

*工厂方法模式(Factory Method Pattern)通过让子类来决定该创建的对象是什么,来达到将对象的创建过程封装的目的。 

*在工厂方法模式中包括创建者(Creator)类和产品(Product)类两种类型的类。 

*工厂方法模式可以和策略(Strategy)模式结合起来,在运行时动态地更换工厂类,从而创建不同的产品对象,这是简单工厂所不具有的弹性。 

*依赖倒置原则说明不能让高层组件依赖于底层组件,而是它们都应该依赖于抽象。 

*抽象工厂模式是工厂方法模式的演变。工厂方法模式中,创建者只生产一种类型的产品,而抽象工厂模式中,创建者生产一组不同类型的产品。 

*所有工厂模式都通过减少应用程序和具体类之间的依赖来促进松耦合,即解耦(Decouple)。 

 

*有些对象我们只需要一个,比如说:线程池(threadpool)、缓存(cache)、对话框(Dialog)、处理偏好设置的对象、处理注册表(register)的对象、日志对象,以及充当打印机、显卡等设备的驱动程序对象。这些对象只能有一个实例,如果出现多个实例就会导致程序的行为异常、资源使用过量,或者产生的结果不一致等等问题。 

*单件模式与全局静态变量的区别: 

(1)使用全局静态变量需要程序员之间的约定才能保证只有一个实例,而单件模式无需这样的约定就可以确保只有一个实例被创建。 

(2)静态变量在程序一开始就被创建(这取决于JVM的实现),而单件模式只是在使用时才创建对象。如果这个被创建的对象非常消耗资源,而在程序运行的过程中没有用到它,就会造成很大的浪费,这是静态变量的缺点。 

*在单态模式中,如果不需要这个实例,它就永远不会被创建。这就是“延迟实例化(Lazy Instance)”。 

*单件常用来管理共享的资源,比如数据库连接池或线程池。 

*多线程会影响到单件模式,如果不对它进行处理就会在单件模式下仍然创建多于一个实例。 

在这个方法里使用到了volatile这个关键字,下面对这个“关键的”关键字进行说明: 

Java语言规范指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入和离开同步代码块时才与共享成员变量的原始值进行对比。 而被volatile修饰的成员变量在线程中的私有拷贝每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量的私有拷贝发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 因此volatile关键字是使“双重检查加锁(Double-Checked Locking)”有效的前提。 但是需要注意,在1.4及以前版本的JDK中,JVM对volatile关键字的实现会导致双重检查加锁失效,所以这个方法只适用于1.5(包含)以后版本。 

*可以通过把一个类中的全部方法都定义为静态方法的方式来达到和单件模式同样的效果,但是由于类的初始化顺序由JVM控制,所以可能导致与初始化顺序有关的BUG,而这样的BUG常常难于被发现。当类的初始化比较简单时,可以使用此方法。 

*类加载器会破坏单件模式,因为不同的类加载器可以分别创建同一个单件的对象,单件对象就有了多个实例。解决办法是:自行指定类加载器,并且指定同一个类加载器。 

*在Java 1.2及以前版本中,垃圾收集器有一个BUG,会造成单件在没有全局引用时,被当做垃圾清理掉。在1.2以后的版本中,这个BUG已经得到了修复,因此不用担心了。 

如果使用的是1.2及以前的版本,可以建立一个单件注册表(Register),从而避免单件被垃圾收集器回收。 

*命令(Command)模式通过把接受者当作参数来传递,并且让所有的命令对象都实现一个命令接口的方式,实现了针对接口编程,从而在调用者和接受者之间实现了解耦。 

*适配器(Adapter)模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者;这种做法还有额外的优点,那就是被适配者的任何子类都可以搭配着适配器使用。 

*外观模式在简化接口的同时,依然将系统完整的功能暴露出来,一共需要的人使用。 

*外观模式不仅简化了接口,也将客户从组件的子系统中解耦。 

*适配器(Adapter)模式和外观(Facade)模式都可以包装多个类,前者的目的是将接口重新组织后提供新接口,后者的目的是简化接口。由于它们的差异很细微,所以两者常常同时出现。 

*适配器(Adapter)将一个对象包装起来以改变其接口;装饰者(Decorator)将一个对象包装起来以增加新的行为和责任;而外观(Facade)将一群对象包装起来以简化其接口。 

 

*当我们说“集合(Collection)”的时候,我们指的是一群对象。其存储方式可以是各式各样的数据结构,例如:列表、数组、散列表,无论用什么方式存储,一律可以视为集合。有时候也被称为聚合(aggregate)。 

*迭代器(Iterator)模式把元素之间游走的任务交给了迭代器,而不是聚合对象。这不仅让聚合的接口和实现变得更简洁,也让它专注于管理对象,而不必理会遍历的事情。 

*类的每个责任都有一个潜在的改变区域,多一个责任就意味着多一个改变的区域。要尽量让每个类保持单一责任。 

*既要让每个类都保持单一的责任,也要保证一个责任只指派给一个类。 

*内聚(Cohesion)用来度量一个类或模块紧密地达到单一目的或责任的程度。 

*当一个类或模块被设计成只支持一组相关功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚。 

*“空对象设计模式”带来的好处是客户不用处理null,因此不必担心系统会跑出NullPointerException。 

*当多个对象彼此之间有“整体/部分”的关系,并且你想用一致的方式处理这些对象时(就是让它们看起来一样,有共同的方法可以调用),就需要用组合(Composite)模式。 

*在组合中加入缓存可以提高遍历的性能。 

*模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。 

*好莱坞原则可以尽量减少类之间的过分依赖,防止“依赖腐.败”。 

*策略(Strategy)模式与模板方法(Template Method)模式的区别:策略模式实现了完整的算法,各个算法之间可以互相替换;而模板方法只定义了算法的骨架,其中一部分步骤需要子类来实现。 

*使用状态模式通常会导致设计中类的数目大量增加。 

*状态可以被多个Context实例共享。 

*远程代理(Remote Proxy)模式:可以作为另一个JVM上对象的本地代表。调用代理的方法会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。 

*虚拟代理(Virtual Proxy)模式:作为创建开销大的对象的代表。虚拟代理经常在我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由代理来扮演它的替身。对象创建后,代理就会将请求直接委托给对象。 

*动态代理:Java在java.lang.reflect包中有自己的代理支持。利用这个包你可以在运行时动态地创建代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类。因为实际的代理类是在运行时创建的,因此称这项Java技术为动态代理。 

其他种类的代理: 

一、防火墙代理(Firewall Proxy):控制网络资源的访问,保护资源免受“坏客户”的侵害。 

二、智能引用代理(Smart Reference Proxy):当主题被引用时,进行额外的动作,例如计算一对象被引用的次数。 

三、缓存代理(Caching Proxy):为开销大的计算结果提供暂时存储。它允许多个客户共享结果以减少计算或网络延迟。 

四、同步代理(Synchronization Proxy):在多线程的情况下为主题提供安全的访问。 

五、复杂隐藏代理(Complexity Hiding Proxy):用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也成为外观代理(Facade Proxy)。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。 

六、写入时复制代理(Copy-On-Write Proxy):用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止,这是虚拟代理的变体。 

 

模式的分类: 

创建型:工厂方法(Factory Method)、抽象工厂(Abstract Factory)、单态/单件(Singleton)。 

行为型:状态(State)、策略(Strategy)、观察者(Observer)、模板方法(Template Method)、命令(Command)、迭代器(Iterator)。 

结构型:装饰者(Decorator)、外观(Facade)、代理(Proxy)、适配器(Adapter)、组合(Composite)。 

*设计的原则是用最简单的方式解决问题,而不是一定要用设计模式。 

 

共享智慧的五种方式: 

一、在设计会议中。可以便面陷入实现的细节和引起误解。 

二、和其他开发人员。分享知识。 

三、在架构文档中。可以缩减文档的篇幅,并使阅读者更清晰地理解设计。 

四、在代码注释及命名习惯上。提高代码的可读性。 

五、将志同道合的开发人员集合在一起。 

 

*为实际的扩展使用模式,不要为了假想的需要而使用模式。 

*简单才是王道。最好的设计是不使用设计模式就能设计出最简单的方案。 

*模式是工具而不是规则,需要被简单地调整以符合你的需要。 

*像模式一样,有许多类型的反模式。包括开发反模式,OO反模式,组织反模式和领域特定反模式。 

*反模式告诉你为何这个解决方案从长远来看会造成不好的影响。 

5、其他模式及其优缺点。 

你可能感兴趣的:(设计模式,面向对象,软件设计)