1、TIPS
(1)生成器模式(Builder)
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
解决问题:
要实现同样的构建过程可以创建不同的表现——>先把构建过程独立出来,在生成器模式中将其称为指导者,由它来指导装配过程,但是不负责每步具体的实现。必须要有能具体实现每步的对象,在生成器模式中称这些实现对象为生成器。
采用生成器模式的解决方案:
指导者是可以重用的构建过程,生成器是可以被切换的具体实现。
Builder:生成器接口,定义创建一个Product对象所需的各个部件的操作;
ConcreteBuilder:具体的生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,还提供一个让用户获取组装完成后的产品对象的方法;
Director:指导者,主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象。
Product:产品,表示被生成器构建的负责对象,包含多个部件。
使用生成器模式的例子:
生成器模式的重心在于分离构建算法和具体的构造实现。
Builder模式都存在两个部分:一个部分是部件构造和产品装配,另一个部分是整体构建的算法;
在使用生成器模式时,大多数情况下是不知道最终构建出来的产品是什么样的。因此,一般是不需要对产品定义抽象接口的。
采用Builder模式构建复杂的对象,通常会对Builder模式进行一定的简化。大致如下:
a、不需要定义Builder接口,直接提供一个具体的构建器类就可以了;
b、去掉指导者,由Client来指导构建器类去构建需要的复杂对象;
在实际开发中,若构建器对象和被构建的对象分开,可能会导致同包内的对象不使用构建器来构建对象,而是直接去使用new来构建对象,这会导致错误。因此,可以将构建器对象和被构建对象合并。实现方式是将类内联化,即把构建器对象合并到被构建对象里。
生成器模式的优点:
a、松散耦合:生成器模式可以用同一个构建算法构建出表现上完全不同的产品,实现产品构建和产品表现上的分离。生成器模式将产品构建的过程和具体产品的表现松散耦合,使得构建算法可以复用,具体产品表现灵活方便地扩展和切换。
b、更好的复用性。
生成器模式的本质:分离整体构建算法和部件构造;
何时选用生成器模式:
a、若创建对象的算法,应独立于该对象的组成部分以及它们的装配方式时;
b、若同一个构建过程有着不同的表示时;
(2)责任链模式
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
解决问题:
客户端发出一个请求,会有很多对象都可以来处理这个请求,且不同对象的处理逻辑是不一样的。希望在处理中,处理流程可以灵活变动,处理请求的对象能方便修改或被替换掉。让请求者和接收者解耦,这样就可以动态组合接收者了。
首先责任链模式会定义一个所有处理请求的对象都要继承实现的抽象类,这有利于随时切换新的实现;
其次每个处理请求对象只实现业务流程中的一步业务处理;
最后责任链模式会动态组合这些处理请求的对象,把它们按照流程动态组合起来,并要求它们依次调用,这样就动态实现了流程。
采用责任链模式的解决方案:
Handler:定义职责的接口,通常在这里定义处理请求的方法,可以在这里实现后继链。
ConcreteHandler:实现职责的类,在这个类中,实现对在它职责范围内请求的处理,若不处理,就继续转发请求给后继者。
Client:职责链的客户端,向链上的具体处理对象提交请求,让职责链负责处理。
使用责任链模式的例子:
客户端如何使用责任链:最重要的就是要先构建责任链,然后才能使用。
当客户端发出请求时,客户端并不知道谁会真正处理它的请求,客户端只知道他提交请求的第一个对象。从第一个处理对象开始,整个职责链中的对象,要么自己处理请求,要么继续转发给下一个接收者。
责任链模式例子的调用过程:
何时构建链:
按照实现的地方来说,分为:
a、可以实现在客户端提交请求前组合链。也就是在使用时动态组合链,称为外部链;
b、也可以在Handler里实现链的组合。
c、还有一种就是在各个职责对象中,由各个职责对象自行决定后续的处理对象。
谁来处理:
这个是在运行时期动态决定的。当请求被传递到某个处理对象时,这个对象会按照已经设定好的条件来判断是否属于自己处理的范围,若是就处理,若不是就转发请求给下一个对象。
责任链模式的各个职责对象实现的功能,相互之间没有关联的,是自己实现属于自己处理的那份功能。
处理多种请求,通用请求的处理方式:
首先定义一个通用的调用框架,用一个通用的请求对象封装请求传递的参数。然后定义一个通用的调用方法,这个方法不区分具体业务,所有的业务都是这个方法。在通用请求对象中有一个业务标记用来区分业务,到了职责对象中,愿意处理就和原来使用一样的处理方式,若不愿意处理,则传递到下一个处理对象就可以了。
这种设计方式的好处是有了新的业务,只要添加实现新功能的对象就可以了。(对于具体进行职责处理的类,比较好的方式是扩展出子类来,然后在子类中实现新加入的业务,也可以直接在原来的对象上改)
接口应保持稳定,接口一改,需要修改的地方就太多了。频繁修改接口不是一个好方法!
最好一个业务一个对象,若确实有公共的数据,可以定义公共的父类,最好不要让不同的业务使用同一个对象,容易混淆。
在标准的职责链中,一个请求在职责链中传递,只要有一个对象处理了这个请求,就会停止。
功能链:一个请求在职责链中传递,每个职责对象负责处理请求的某一方面的功能。处理完成后,继续向下传递请求,当请求通过很多职责对象处理后,功能也就完成了。
责任链模式的优点:
a、请求者和接收者松耦合,即请求者和接收者完全解耦。
请求者只是负责向职责链发出请求就可以。
b、动态组合职责
职责链模式把功能处理分散到单独的职责对象中,在使用时,动态组合职责形成职责链,灵活地给对象分配职责,灵活地实现和改变对象的职责。
缺点:
a、会产生很多细粒度对象。因为每个职责对象只处理一个方面的功能,要把整个业务处理完,需要很多职责对象的组合,因此会有大量的细粒度职责对象。
b、不一定能被处理
可能会出现某个请求,把整个链传递完了,都没有职责对象处理它,这就需要在使用职责链模式时,需要提供默认的处理。
责任链模式的本质:分离职责,动态组合;
何时使用责任链模式:
a、若有多个对象可以处理同一个请求,但具体由哪个对象处理该请求是运行时刻动态确定的。此时可以用职责链模式;
b、若想在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,此时可以使用职责链模式;
c、若想要动态指定处理一个请求的对象集合,可以使用职责链模式;
职责链和装饰模式:
两者可以相互模拟实现对方的功能。
装饰模式能动态给被装饰对象添加功能,要求装饰器对象和被装饰的对象实现相同的接口。职责链模式实现 动态的职责组合,标准的功能是有一个对象处理就结束。
(3)装饰模式(Decorator)
定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
解决问题:
能灵活给对象增加或减少功能,能动态组合功能。
在装饰模式的实现中,为了能实现和原来使用被装饰对象的代码无缝结合,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现 类中,转调被装饰的对象,在转调的前后添加新的功能。
采用装饰模式的解决方案:
Component:组件对象的接口,可以给这些对象动态添加职责。
ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是给这个对象添加职责。
Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,即持有一个被装饰的对象。
ConcreteDecorator:实际的装饰器对象,实现具体要向被装饰对象添加的功能。
使用装饰器的客户端,首先需要创建被装饰的对象,然后创建需要的装饰对象,最后是组合装饰对象,依次对前面的对象进行装饰。
使用装饰模式的实际例子:(将多种计算奖金的方式分散到不同的装饰器对象中,每个装饰器相当于计算奖金的一个部分)
装饰模式的组合和调用过程:(是一个递归的调用方法)
装饰器:实现了对被装饰对象的某些装饰功能;
各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行装饰组合时,才没有先后顺序的限制,即先装饰谁和后装饰谁应该是一样的。
装饰器和组件类的关系:
装饰器一定要实现和组件类一致的接口,这样组合完成的装饰才能递归调用下去;
退化形式:
若仅仅想要添加一个功能,可以不必设计装饰器的抽象类,直接在装饰器中实现和组件一样的接口,然后实现相应的装饰功能即可。建议最好还是设计上装饰器的抽象类,有利于程序的扩展。
装饰模式和AOP
AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善OOP。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点的功能模块织入到面向对象的软件系统中,从而实现横切关注点的模块化。
AOP能将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,减少了系统的重复代码,降低模块间的耦合度,有利于可维护性。
不使用AOP:
使用AOP:
在这里,可以认为业务功能对象是被装饰的对象,而各个公共的模块是装饰器,可以透明地给业务功能对象增加功能。
装饰模式和AOP要实现的功能是类似的,但AOP实现的方法不同,会更加灵活,更加可配置。
装饰模式的优点:
a、易复用功能
装饰模式把一系列复杂的功能分散到每个装饰器中,一般一个装饰器只实现一个功能。
b、简化高层定义
装饰模式可通过组合装饰器的方式,为对象添加任意多的功能。因此,在进行高层定义时,不用把所有的功能都定义出来,而是定义最基本的就可以了,在需要使用时,组合相应的装饰器来完成所需的功能。
缺点:会产生很多细粒度对象。功能越复杂,需要的细粒度对象越多(即装饰器越多,因为一个装饰器只实现一个功能)。
装饰模式的本质:动态组合。包括动态功能的组合(即动态进行装饰器的组合)和对象组合(即通过对象组合来实现被装饰对象透明增加功能)
装饰模式是通过把复杂功能简单化、分散化,然后在运行期间,根据需要来动态组合的模式。
何时选用装饰模式:
a、若需要在不影响其它对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式。
b、若不适合使用子类来进行扩展时。因为扩展功能需要的子类太多,易造成子类数目呈爆炸性增长。
装饰模式和适配器模式,都有一个共同的别名:Wrapper。
适配器模式是用来改变接口的,而装饰器模式是用来改变对象功能的;
装饰模式改变的是经过前一个装饰器装饰后的对象。
(4)代理模式
定义:为其它对象提供一种代理以控制对这个对象的访问。
解决问题:
由于客户访问多条用户数据时,基本上只需要看到用户的姓名,因此可以考虑 刚开始从数据库查询返回的用户数据只有用户编号和用户姓名,当客户想要详细查看某个用户数据时,再根据用户编号到数据库中获取完整的用户数据。这样,可以在满足客户功能 的前提下,减少对内存的消耗。
这种做法最合适的场景是:客户大多数情况下只需要查看用户编号和姓名,而少量的数据需要查看详细数据。
采用代理模式 的解决方案:
使用代理模式的实际例子:
HIbernate的Lazy Load就是使用代理实现的。
代理模式是通过创建一个代理对象,用这个代理对象去代表真实的对象,客户端得到这个代理对象后,就跟得到了真实对象一样来使用。
客户端操作代理,代理操作真正的对象。
虚代理:起初每个代理对象只有用户编号和姓名数据,直到需要时,才会把整个用户的数据装载到内存中来;
复制一个大的对象很销毁资源,若这个被复制的对象从上次操作以来,根本就没有被修改过,那么再复制这个对象没有必要。于是使用代理来延迟复制的过程,等到对象被修改时才真正对它进行复制。
保护代理时一种控制对原始对象访问的代理。保护代理会检查调用者是否具有请求所必需的访问权限,若没有相应的权限,就不会调用目标对象。
在Java中,远程代理的典型体现是RMI技术。要想把远程代理搞清楚,就需要把RMI搞清楚。
Java的动态代理基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。要实现类的代理,可以使用cglib。
动态代理实现时,Subject接口上定义了很多方法,但动态代理类始终只有一个invoke方法。当Subject接口发生变化时,动态代理的接口不需要跟着变化。
代理类的实现:
a、要实现InvocationHandler接口;
b、需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好过后,返回被代理的目标对象的接口,以利于客户端的操作;
c、需要实现invoke方法,在这个方法里,具体判断当前是在调用什么方法,需要如何处理。
远程代理:隐藏了一个对象存在于不同的地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象。
何时选用代理模式:
a、需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理。
b、需要按照需要创建开销很大的对象的时候,可以使用虚代理。
适配器模式主要用来解决接口之间不匹配的问题,通常是为所适配的对象提供一个不同的接口。
装饰模式的目的是为了让你不生成子类就可以给对象添加职责。
(5)策略模式:做某事的策略。指完成某个操作可能有多种方法,使用者根据需要选择合适的策略。
定义:定义一系列的算法,把它们一个个封装起来,且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
解决问题:
固定调用各种算法,使得切换调用不同的算法很麻烦,每次都要修改if-else中的调用代码——>将算法实现和使用算法解耦,即具体的算法实现和使用算法要在不同类中。这样,算法可独立于使用它的客户而变化,只要客户端动态选择使用不同的算法,然后设置到上下文对象中,在实际调用时,可以调用到不同的方法。
采用策略模式的解决方案:
Strategy:策略接口,用来约束一系列具体的测量算法。Context使用这个接口来调用具体的策略实现定义的算法。
ConcreteStrategy:具体的策略实现,也就是具体的算法实现。
Context:上下文,负责和具体的策略类交互。通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法。(此时需要把上下文作为策略接口方法中的参数)
策略模式的功能是把具体的算法实现从具体的业务处理中独立出来,把它们实现成单独的算法类,从而形成一系列的算法。策略模式的重心是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。不是如何来实现算法。
策略模式就是把各个平等的具体实现封装到单独的策略实现类了,然后通过上下文来与具体的策略类进行交互。因此多个if-else语句可以考虑使用策略模式。
策略算法在实现上相互独立,相互之间没有依赖。
谁来选择具体的策略算法:有两个地方进行具体策略的选择;
a、一个是在客户端,当使用上下文时,由客户端来选择具体的策略算法;
b、还有一个就是客户端不管,由上下文来选择具体的策略算法。
运行时策略的唯一性。即策略模式在同一个时刻只能使用一个具体的策略实现对象。
策略模式的调用还有一种情况:就是把Context作为参数来传递给Strategy。此时,策略实现对象可以从上下文获取所需要的数据,方式是通过回调上下文的方法来获取这些数据。
通过扩展上下文对象来准备新的算法需要的数据,也可以通过策略的构造方法来传入新算法需要的数据。
容错恢复机制:
程序运行时,正常情况下应该按照某种方式来做,若按照某种方式来做发生错误的话,系统不会崩溃,也不会就此不能继续向下运行了,而是有容忍出错的能力,不但能容忍程序运行出现错误,还提供出现错误后的备用方案,也就是恢复机制,来代替正常执行的功能,使程序继续向下运行。
对于一系列算法懂得实现上存在公共功能的情况,策略模式可以有以下三种实现方式:
a、在上下文当中实现公共功能,让所有具体的策略算法回调这些方法;
b、将策略的接口改成抽象类,然后在其中实现具体算法的公共功能;
c、为所有的策略方法定义一个抽象的父类,让这个父类去实现策略的接口,然后在这个父类中去实现公共的功能;
策略模式的优点:
a、定义一系列算法,实现让这些算法可以相互替换;
b、避免多重条件语句。
策略模式的一系列策略算法是平等的,可以互换。写在一起就是通过if-else结构来组织。若此时具体的算法实现中又有条件语句,就构成了多重条件语句。
缺点:
a、在运行时只有一个算法被使用。对于需要嵌套使用多个算法的情况时,可以考虑使用装饰模式或责任链、AOP等方式实现。
b、客户必须了解每种策略的不同。
因为需要让客户端来选择具体使用哪种策略,这就需要客户了解所有的策略,而且这样也暴露了策略的具体实现。
策略模式的本质:分离算法,选择实现。
何时选用策略模式:
a、出现有许多相关的类,仅仅是行为有差别的情况下,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换;
b、出现同一个算法,有很多不同实现的情况下,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次;
c、需要封装算法中,有与算法相关数据的情况下,可以使用策略模式来避免暴露这些跟算法相关的数据结构;
d、出现抽象一个定义了很多行为的类,且是通过多个if-else语句来选择这些行为的情况下,可以使用策略模式来代替这些条件语句。
(6)模板方法模式
定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
将运算步骤看做是算法的骨架,把具体的不同的步骤实现延迟到子类去实现。
解决问题:
当做两个独立的模块来实现,当需要扩展功能,则此两个模块都需要修改。重复或相似代码太多、扩展不方便——>将两个模块合起来看,将重复或相似的代码抽取出来做成公共的功能。这样,扩展时,若出现有相同的功能,直接扩展到公共功能就可以了。
定义一个父类,在其中定义一个模板方法来定义算法骨架,把父类无法确定的实现,延迟到具体的子类实现。
采用模板方法模式的解决方案:
AbstractClass:抽象类。用来定义算法骨架和原语操作,具体的子类通过重定义这些原语操作来实现一个算法的各个步骤。在这个类里面,可以提供算法中通用的实现。
ConcreteClass:具体实现类。用来实现算法骨架中的某些步骤,完成与特定子类相关的功能。
模板方法模式的功能在于固定算法骨架,而让具体算法实现可扩展。
抽象类不一定包含抽象方法;而有抽象方法的类一定是抽象类。
抽象类中是可以有具体的实现方法的,而接口中所有的方法都是没有具体的实现的。
程序设计有一个很重要的思考点——变与不变。即分析程序中哪些功能是可变的,哪些功能是不变的,将不变的部分抽象出来,进行公共的实现,把变化的部分分离出去,用接口来封装隔离,或者是用抽象类来约束子类行为。
模板方法模式很好体现了这一点。模板类实现的就是不变的方法和算法的骨架,需要变化的地方,都通过抽象方法,把具体实现延迟到子类中。从而使系统能有更好的复用性和扩展性。
new谁就调用谁的方法(多态);
作为父类的模板会在需要时,调用子类相应的方法,也就是由父类来找子类,而不是让子类来找父类。——这其实是一种反向控制结构。本来应该是子类来调用父类的方法,因为父类根本就不知道子类,而子类是知道父类的。但在模板方法模式里,是父类来找子类。
应用Java回调来实现模板方法模式,在实际开发中使用的也非常多,算是模板方法模式的一种变形实现。
通过回调在接口中定义的方法,调用到具体的实现类中的方法,本质是利用动态绑定技术。在这种实现中,可以不把实现类写出单独的类,而是使用匿名内部类来实现回调方法。
a、先定义一个模板方法需要的回调接口。
在这个接口中需要把所有可以被扩展的方法都要定义出来。实现时,可以不扩展,直接转调模板中的默认实现。
b、模板类不再是抽象的类了,所有的抽象方法都没有了。对于模板方法,需要添加一个参数,传入回调接口。在模板方法实现中,除了在模板中固定的实现外,所有可能被扩展的方法,都应该通过回调接口进行调用。
c、因为是直接在调用的地方传入回调的实现,通常可以通过匿名内部类的方式来实现回调接口,当然实现成为具体类也是可以的。
模板方法模式的两种实现方式:使用继承的方式和使用Java回调。
命令模式时也提到了Java回调,通过退化命令模式来实现了Java回调的功能。命令模式可以作为模板方法模式的一种替代实现,因为可以使用Java回调来实现模板方法模式。
模板方法模式的优点:
a、实现代码复用。模板方法模式是一种实现代码复用的很好的手段。通过把子类的公共功能抽取,把公共部分放到模板中去实现。
缺点:
a、算法骨架不容易升级。
模板方法的本质:固定算法骨架。
模板方法模式主要是通过制定模板,把算法步骤固定下来,至于谁来实现,模板可以自己提供实现,也可以由子类去实现,还可以通过回调机制让其他类来实现。
何时选用模板方法模式:
a、需要固定定义算法框架,实现一个算法的不变的部分,并把可变的行为留给子类实现的情况。
b、各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现。
c、需要控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,只允许在这些点进行扩展。
模板方法模式与策略模式的区别:
模板方法封装的是算法的框架,这个算法骨架不变,变化的是算法中某些步骤的具体实现;
策略模式是把某个步骤的具体实现算法封装起来,所有封装的算法对象是等价的,可以相互替换。
设计模板:
找到不变与变之处,就可以设计模板了。先定义出各自的实现步骤,也就是定义好各自的算法骨架,然后把变化的部分定义为原语操作或钩子操作,若一定要子类实现的就定义为原语操作;在模板中提供默认实现,不强制子类实现的功能定义为钩子操作。
将结果集映射成为对象的方法可以做成公共的——通过反射机制实现。
模板方法定义了一个算法的步骤,允许子类为一个或多个步骤提供实现;
钩子就是回调函数,它可以作为条件影响模板方法类中算法的流程。
模板方法的抽象类可以定义具体方法、抽象方法和钩子。
钩子是一种方法,它在抽象类中不做事,或者只做默认的事,子类可以选择要不要覆盖它。
将决策权放在高层模块中,以便决定如何以及何时调用底层模块。
策略模式和模板方法模式都封装算法,前者使用组合,后者使用继承。
(7)迭代器模式
提供一种顺序访问集合对象中各个元素的方法,而又不暴露其内部的表示(即数据结构)。
2、PS
(1)数据结构:数据的存储,如列表、数组、散列表等。
(2)内聚用来度量一个类或模块紧密地达到单一目的或责任的程度。
(3)组合模式:将对象组合成树状结构来表现“整体/部分”的层级结构,让客户以一致的方式来处理个别对象以及对象组合。
在树状结构中,带有子元素的元素称为节点;没有子元素的元素称为叶子节点。
(4)