24种设计模式(三)

24种设计模式

第20章 解释器模式

第21章 装饰模式

第22章 责任链模式

第23章 桥接模式

第24章 访问者模式

Github 源码下载

第20章 解释器模式(Interpreter)

1.解释器模式的定义

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
这里的文法,简单点说就是我们俗称的“语法规则”。

2. 解释器模式的结构和说明

image
■ AbstractExpression:定义解释器的接口,约定解释器的解释操作。

■ TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。

■ NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象。可以有多种非终结符解释器。

■ Context:上下文,通常包含各个解释器需要的数据或是公共的功能。

■ Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成为使用解释器对象描述的抽象语法树,然后调用解释操作。

3. 解释器模式示例代码
public class InterpreterDemo {
    public static void main(String[] args) {

    }
}
class ContextInterpret{

}
abstract class AbstractExcepression{
    abstract void interpret(ContextInterpret contextInterpret);

}

class TerminalExpression extends AbstractExcepression{
    @Override
    void interpret(ContextInterpret contextInterpret) {
        System.out.println("TerminalExpression");
    }
}

class NonterminalExpression extends AbstractExcepression{
    @Override
    void interpret(ContextInterpret contextInterpret) {
        System.out.println("NonterminalExpression");
    }
}

4. 解释器模式的优缺点

解释器模式有以下优点。

■ 易于实现语法
在解释器模式中,一条语法规则用一个解释器对象来解释执行。对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就可以了,其他的都不用管。

■ 易于扩展新的语法
正是由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易。扩展了新的语法,只需要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。

解释器模式的缺点是不适合复杂的语法。

如果语法特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨的,再加上有可能会需要构建多个抽象语法树。所以解释器模式不太适合于复杂的语法,对于复杂的语法,使用语法分析程序或编译器生成器可能会更好一些。

5.解释器模式的本质

解释器模式的本质:分离实现,解释执行。

解释器模式通过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开;然后选择需要被执行的功能,并把这些功能组合成为需要被解释执行的抽象语法树;再按照抽象语法树来解释执行,实现相应的功能。

6.何时选用解释器模式

建议在以下情况中选用解释器模式。

当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式。

在使用解释器模式的时候,还有两个特点需要考虑,一个是语法相对应该比较简单,太复杂的语法不适合使用解释器模式;另一个是效率要求不是很高,对效率要求很高的情况下,不适合使用解释器模式。

第21章 装饰模式(Decorator)

1.装饰模式的定义

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活

2. 装饰模式的结构和说明

image

■ Component:组件对象的接口,可以给这些对象动态地添加职责。

■ ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责。

■ Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。

注意
注意这个被装饰的对象不一定是最原始的那个对象了,也可能是被其他装饰器装饰过后的对象,反正都是实现的同一个接口,也就是同一类型。

■ ConcreteDecorator:实际的装饰器对象,实现具体要向被装饰对象添加的功能。

3. 装饰模式示例代码
public class DecoratorDemo {
}
abstract class ComponentDecorator{

    abstract void opreation();
}

class ConcreteComponentDecorator extends ComponentDecorator{
    @Override
    void opreation() {
        System.out.println(" do ConcreteComponentDecorator");
    }
}

abstract class Decorator extends ComponentDecorator{
    private ComponentDecorator component;

    public Decorator(ComponentDecorator component) {
        this.component = component;
    }

    @Override
    void opreation() {
        component.opreation();
    }
}

class ConcreteDecoratorA extends Decorator{
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public ConcreteDecoratorA(ComponentDecorator component) {
        super(component);
    }

    @Override
    void opreation() {
        super.opreation();
    }
}

class ConcreteDecoratorB extends Decorator{

    public ConcreteDecoratorB(ComponentDecorator component) {
        super(component);
    }
    
    public void doSomething(){
        System.out.println("so something");
    }
    @Override
    void opreation() {
        super.opreation();
        doSomething();
    }
}

4. 装饰模式的优缺点

装饰模式有以下优点。

■ 比继承更灵活
从为对象添加功能的角度来看,装饰模式比继承更灵活。继承是静态的,而且一旦继承所有子类都有一样的功能。而装饰模式采用把功能分离到每个装饰器当中,然后通过对象组合的方式,在运行时动态地组合功能,每个被装饰的对象最终有哪些功能,是由运行期动态组合的功能来决定的。
■ 更容易复用功能
装饰模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,使实现装饰器变得简单,更重要的是这样有利于装饰器功能的复用,可以给一个对象增加多个同样的装饰器,也可以把一个装饰器用来装饰不同的对象,从而实现复用装饰器的功能。
■ 简化高层定义
装饰模式可以通过组合装饰器的方式,为对象增添任意多的功能。因此在进行高层定义的时候,不用把所有的功能都定义出来,而是定义最基本的就可以了,可以在需要使用的时候,组合相应的装饰器来完成所需的功能。

装饰模式的缺点是:会产生很多细粒度对象。

5.装饰模式的本质

装饰模式的本质:动态组合。

动态是手段,组合才是目的。这里的组合有两个意思,一个是动态功能的组合,也就是动态进行装饰器的组合;另外一个是指对象组合,通过对象组合来实现为被装饰对象透明地增加功能。

但是要注意,装饰模式不仅可以增加功能,而且也可以控制功能的访问,完全实现新的功能,还可以控制装饰的功能是在被装饰功能之前还是之后来运行等。

总之,装饰模式是通过把复杂功能简单化、分散化,然后在运行期间,根据需要来动态组合的这样一个模式。

6.何时选用装饰模式

建议在以下情况中选用装饰模式。

■ 如果需要在不影响其他对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式,这几乎就是装饰模式的主要功能。

■ 如果不适合使用子类来进行扩展的时候,可以考虑使用装饰模式。因为装饰模式是使用的“对象组合”的方式。所谓不适合用子类扩展的方式,比如,扩展功能需要的子类太多,造成子类数目呈爆炸性增长。

第22章 职责链模式(Chain of Responsibility)

1.职责链模式的定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

2. 职责链模式的结构和说明

image
■ Handler:定义职责的接口,通常在这里定义处理请求的方法,可以在这里实现后继链。

■ ConcreteHandler:实现职责的类,在这个类中,实现对在它职责范围内请求的处理,如果不处理,就继续转发请求给后继者。

■ Client:职责链的客户端,向链上的具体处理对象提交请求,让职责链负责处理。

3. 职责链模式示例代码
public class ChainDemo {
    public static void main(String[] args) {
        Handler  handler1 = new ConcreteHandler1();
        Handler  handler2 = new ConcreteHandler2();

        handler1.setSuccessor(handler2);
        handler1.handleRequest();
    }
}
abstract class Handler{
    protected Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    abstract void handleRequest();
}

class ConcreteHandler1 extends Handler{
    @Override
    void handleRequest() {
        boolean condition = false;
        //满足条件,自己处理,否则向下传递
        if(condition){
            System.out.println("do ");
        }else{
            successor.handleRequest();
        }
    }
}
class ConcreteHandler2 extends Handler{
    @Override
    void handleRequest() {
        System.out.println("ConcreteHandler2 do it !");
    }
}
4. 职责链模式的优缺点

职责链模式有以下优点。
■ 请求者和接收者松散耦合
在职责链模式中,请求者并不知道接收者是谁,也不知道具体如何处理,请求者只是负责向职责链发出请求就可以了。而每个职责对象也不用管请求者或者是其他的职责对象,只负责处理自己的部分,其他的就交给其他的职责对象去处理。也就是说,请求者和接收者是完全解耦的。
■ 动态组合职责
职责链模式会把功能处理分散到单独的职责对象中,然后在使用的时候,可以动态组合职责形成职责链,从而可以灵活地给对象分配职责,也可以灵活地实现和改变对象的职责。

职责链模式有以下缺点。

■ 产生很多细粒度对象
职责链模式会把功能处理分散到单独的职责对象中,也就是每个职责对象只处理一个方面的功能,要把整个业务处理完,需要很多职责对象的组合,这样会产生大量的细粒度职责对象。

■ 不一定能被处理
职责链模式的每个职责对象只负责自己处理的那一部分,因此可能会出现某个请求,把整个链传递完了,都没有职责对象处理它。这就需要在使用职责链模式的时候,需要提供默认的处理,并且注意构建的链的有效性。

5.职责链模式的本质

职责链模式的本质:分离职责,动态组合。

分离职责是前提,只有先把复杂的功能分开,拆分成很多的步骤和小的功能处理,然后才能合理规划和定义职责类。可以有很多的职责类来负责处理某一个功能,让每个职责类负责处理功能的某一个方面,在运行期间进行动态组合,形成一个处理的链,把这个链运行完,功能也就处理完了。

动态组合才是职责链模式的精华所在,因为要实现请求对象和处理对象的解耦,请求对象不知道谁才是真正的处理对象,因此要动态地把可能的处理对象组合起来。由于组合的方式是动态的,这就意味着可以很方便地修改和添加新的处理对象,从而让系统更加灵活和具有更好的扩展性。

6.何时选用职责链模式

建议在以下情况中选用职责链模式。

■ 如果有多个对象可以处理同一个请求,但是具体由哪个对象来处理该请求,是运行时刻动态确定的。这种情况可以使用职责链模式,把处理请求的对象实现成为职责对象,然后把它们构成一个职责链,当请求在这个链中传递的时候,具体由哪个职责对象来处理,会在运行时动态判断。

■ 如果你想在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式。职责链模式实现了请求者和接收者之间的解耦,请求者不需要知道究竟是哪一个接收者对象来处理了请求。

■ 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式。职责链模式能动态地构建职责链,也就是动态地来决定到底哪些职责对象来参与到处理请求中来,相当于是动态地指定了处理一个请求的职责对象集合。

第23章 桥接模式(Bridge)

1.桥接模式的定义

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

2. 桥接模式的结构和说明

image

■ Abstraction:抽象部分的接口。通常在这个对象中,要维护一个实现部分的对象引用,抽象对象里面的方法,需要调用实现部分的对象来完成。这个对象中的方法,通常都是和具体的业务相关的方法。

■ RefinedAbstraction:扩展抽象部分的接口。通常在这些对象中,定义跟实际业务相关的方法,这些方法的实现通常会使用Abstraction中定义的方法,也可能需要调用实现部分的对象来完成。

■ Implementor:定义实现部分的接口。这个接口不用和Abstraction中的方法一致,通常是由Implementor接口提供基本的操作。而Abstraction中定义的是基于这些基本操作的业务方法,也就是说Abstraction定义了基于这些基本操作的较高层次的操作。

■ ConcreteImplementor:真正实现Implementor接口的对象。

3. 桥接模式示例代码
public class BridgeDemo {
    public static void main(String[] args) {
        Implementor implementor = new ConcreteImplementor();
        Abstraction abstraction = new RefinedAbstraction(implementor);
        abstraction.operation();
    }
}
interface Implementor{
    void operation();
}
abstract class Abstraction{
    protected Implementor implementor;

    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }

    public void operation(){
        implementor.operation();
    }
}
class RefinedAbstraction extends  Abstraction{
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("桥接完成,再做点事情");
    }
}
class ConcreteImplementor implements Implementor{
    @Override
    public void operation() {
        System.out.println("这是原始方法做的");
    }
}
4. 桥接模式的优点

■ 分离抽象和实现部分

桥接模式分离了抽象部分和实现部分,从而极大地提高了系统的灵活性。让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。

■ 更好的扩展性

由于桥接模式把抽象部分和实现部分分离开了,而且分别定义接口,这就使得抽象部分和实现部分可以分别独立地扩展,而不会相互影响,从而大大地提高了系统的可扩展性。

■ 可动态地切换实现

由于桥接模式把抽象部分和实现部分分离开了,所以在实现桥接的时候,就可以实现动态的选择和使用具体的实现。也就是说一个实现不再是固定的绑定在一个抽象接口上了,可以实现运行期间动态地切换。

■ 可减少子类的个数

根据前面的讲述,对于有两个变化纬度的情况,如果采用继承的实现方式,大约需要两个纬度上的可变化数量的乘积个子类;而采用桥接模式来实现,大约需要两个纬度上的可变化数量的和个子类。可以明显地减少子类的个数。

5.桥接模式的本质

桥接模式的本质:分离抽象和实现。

桥接模式最重要的工作就是分离抽象部分和实现部分,这是解决问题的关键。只有把抽象部分和实现部分分离开了,才能够让它们独立地变化;只有抽象部分和实现部分可以独立地变化,系统才会有更好的可扩展性和可维护性。

6.何时选用桥接模式

建议在以下情况中选用桥接模式。

■ 如果你不希望在抽象部分和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象部分和实现部分分开,然后在程序运行期间来动态地设置抽象部分需要用到的具体的实现,还可以动态地切换具体的实现。

■ 如果出现抽象部分和实现部分都能够扩展的情况,可以采用桥接模式,让抽象部分和实现部分独立地变化,从而灵活地进行单独扩展,而不是搅在一起,扩展一边就会影响到另一边。

■ 如果希望实现部分的修改不会对客户产生影响,可以采用桥接模式。由于客户是面向抽象的接口在运行,实现部分的修改可以独立于抽象部分,并不会对客户产生影响,也可以说对客户是透明的。

■ 如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。

第24章 访问者模式(Visitor)

1. 访问者模式的定义

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

2. 访问者模式的结构和说明

image

■ Visitor:访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能。

■ ConcreteVisitor:具体的访问者实现对象,实现要真正被添加到对象结构中的功能。

■ Element:抽象的元素对象,对象结构的顶层接口,定义接受访问的操作。

■ ConcreteElement:具体元素对象,对象结构中具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。

■ ObjectStructure:对象结构,通常包含多个被访问的对象,它可以遍历多个被访问的对象,也可以让访问者访问它的元素。可以是一个复合或是一个集合,如一个列表或无序集合。

3. 访问者模式示例代码
import java.util.ArrayList;
import java.util.Collection;

public class VisitorDemo {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();

        ConcreteElementA concreteElementA = new ConcreteElementA();
        ConcreteElementB concreteElementB = new ConcreteElementB();

        objectStructure.addElement(concreteElementA);
        objectStructure.addElement(concreteElementB);

        Visitor visitor = new ConcreteVisitorA();
        objectStructure.handleRequest(visitor);
    }
}
interface Visitor{
    void visitConcreteElemenA(ConcreteElementA concreteElementA);

    void visitConcreteElemenB(ConcreteElementB concreteElementB);
}

abstract class Element{
    abstract void accept(Visitor visitor);
}

class ConcreteElementA extends Element{
    @Override
    void accept(Visitor visitor) {
        visitor.visitConcreteElemenA(this);
    }

    public void opreation(){
        System.out.println("已有操作A");
    }
}

class ConcreteElementB extends Element{
    @Override
    void accept(Visitor visitor) {
        visitor.visitConcreteElemenB(this);
    }

    public void opreation(){
        System.out.println("已有操作B");
    }
}
class ConcreteVisitorA implements Visitor{
    @Override
    public void visitConcreteElemenA(ConcreteElementA concreteElementA) {
        System.out.println("A添加点东西");
        concreteElementA.opreation();
    }

    @Override
    public void visitConcreteElemenB(ConcreteElementB concreteElementB) {
        System.out.println("B添加点东西");
        concreteElementB.opreation();
    }
}

class ObjectStructure{
    private Collection<Element> collection = new ArrayList<Element>();

    public void handleRequest(Visitor visitor){
        for (Element element : collection){
            element.accept(visitor);
        }
    }

    public void addElement(Element e){
        collection.add(e);
    }
}

4. 访问者模式的优缺点

访问者模式有以下优点。

■ 好的扩展性
能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

■ 好的复用性
可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

■ 分离无关行为
可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

访问者模式有以下缺点。
■ 对象结构变化很困难
不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。

■ 破坏封装
访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。

5.访问者模式的本质

访问者模式的本质:预留通路,回调实现。

仔细思考访问者模式,它的实现主要是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发技术,利用预先定义好的通路,回调到访问者具体的实现上。

6.何时选用访问者模式

建议在以下情况中选用访问者模式。
■ 如果想对一个对象结构实施一些依赖于对象结构中具体类的操作,可以使用访问者模式。

■ 如果想对一个对象结构中的各个元素进行很多不同的而且不相关的操作,为了避免这些操作使类变得杂乱,可以使用访问者模式。把这些操作分散到不同的访问者对象中去,每个访问者对象实现同一类功能。

■ 如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作,可以使用访问者模式。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值