图解设计模式+代码(四):行为型模式

模板方法模式

1、定义与特点

  • 定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式
  • 优点:
    • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
    • 它在父类中提取了公共的部分代码,便于代码复用。
    • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  • 缺点:
    • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
    • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度

2、结构与实现

模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。

  • 模式的结构:
    • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
      • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
      • 基本方法:是整个算法中的一个步骤,包含以下几种类型。
        • 抽象方法:在抽象类中申明,由具体子类实现。
        • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
        • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
    • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
  • 模式的实现:image.pngimage.png
public class TemplateMethodPattern{
    public static void main(String[] args){
        AbstractClass tm=new ConcreteClass();
        tm.TemplateMethod();
    }
}
//抽象类
abstract class AbstractClass{
    public void TemplateMethod() //模板方法{
        SpecificMethod();
        abstractMethod1();          
        abstractMethod2();
    }  
    public void SpecificMethod() //具体方法{
        System.out.println("抽象类中的具体方法被调用...");
    }   
    public abstract void abstractMethod1(); //抽象方法1
    public abstract void abstractMethod2(); //抽象方法2
}
//具体子类
class ConcreteClass extends AbstractClass{
    public void abstractMethod1(){
        System.out.println("抽象方法1的实现被调用...");
    }   
    public void abstractMethod2(){
        System.out.println("抽象方法2的实现被调用...");
    }
}

3、应用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

4、扩展

  • 在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如图 3 所示。
public class HookTemplateMethod{
    public static void main(String[] args){
        HookAbstractClass tm=new HookConcreteClass();
        tm.TemplateMethod();
    }
}
//含钩子方法的抽象类
abstract class HookAbstractClass{
    //模板方法
    public void TemplateMethod(){
        abstractMethod1();
        HookMethod1();
        if(HookMethod2()){
            SpecificMethod();   
        }
         abstractMethod2();
    }  
    //具体方法
    public void SpecificMethod(){
        System.out.println("抽象类中的具体方法被调用...");
    }
    public void HookMethod1(){}  //钩子方法1
    //钩子方法2
    public boolean HookMethod2(){
        return true;
    }
    public abstract void abstractMethod1(); //抽象方法1
    public abstract void abstractMethod2(); //抽象方法2
}
//含钩子方法的具体子类
class HookConcreteClass extends HookAbstractClass{
    public void abstractMethod1(){
        System.out.println("抽象方法1的实现被调用...");
    }   
    public void abstractMethod2(){
        System.out.println("抽象方法2的实现被调用...");
    }   
    public void HookMethod1(){
        System.out.println("钩子方法1被重写...");
    }
    //重写钩子方法2
    public boolean HookMethod2(){
        return false;
    }
}

策略模式

1、定义与特点

  • 定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
  • 优点:
    • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
    • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
    • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
    • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
    • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
  • 缺点:
    • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
    • 策略模式造成很多的策略类。

2、结构与实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性

  • 模式的结构:
    • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
    • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
    • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
  • 模式的实现:image.pngimage.png
public class StrategyPattern{
    public static void main(String[] args){
        Context c=new Context();
        Strategy s=new ConcreteStrategyA();
        c.setStrategy(s);
        c.strategyMethod();
        System.out.println("-----------------");
        s=new ConcreteStrategyB();
        c.setStrategy(s);
        c.strategyMethod();
    }
}
//抽象策略类
interface Strategy{
    public void strategyMethod();    //策略方法
}
//具体策略类A
class ConcreteStrategyA implements Strategy{
    public void strategyMethod(){
        System.out.println("具体策略A的策略方法被访问!");
    }
}
//具体策略类B
class ConcreteStrategyB implements Strategy{
  public void strategyMethod(){
      System.out.println("具体策略B的策略方法被访问!");
  }
}
//环境类
class Context{
    private Strategy strategy;
    public Strategy getStrategy(){
        return strategy;
    }
    public void setStrategy(Strategy strategy){
        this.strategy=strategy;
    }
    public void strategyMethod(){
        strategy.strategyMethod();
    }
}

3、应用场景

  • 策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多:
    • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
    • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
    • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
    • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
    • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为

4、扩展

  • 在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度,其结构图如图 5 所示。

命令模式

1、定义与特点

  • 定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
  • 优点:
    • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
    • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
    • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
    • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  • 缺点:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。

2、结构与实现

  • 模式的结构:
    • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
    • 具体命令(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
    • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
    • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
  • 模式的实现:image.pngimage.png
public class CommandPattern{
    public static void main(String[] args){
        Command cmd=new ConcreteCommand();
        Invoker ir=new Invoker(cmd);
        System.out.println("客户访问调用者的call()方法...");
        ir.call();
    }
}
//调用者
class Invoker{
    private Command command;
    public Invoker(Command command){
        this.command=command;
    }
    public void setCommand(Command command){
        this.command=command;
    }
    public void call(){
        System.out.println("调用者执行命令command...");
        command.execute();
    }
}
//抽象命令
interface Command{
    public abstract void execute();
}
//具体命令
class ConcreteCommand implements Command{
    private Receiver receiver;
    ConcreteCommand(){
        receiver=new Receiver();
    }
    public void execute(){
        receiver.action();
    }
}
//接收者
class Receiver{
    public void action(){
        System.out.println("接收者的action()方法被调用...");
    }
}

3、应用场景

  • 当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互。
  • 当系统需要随机请求命令或经常增加或删除命令时,命令模式比较方便实现这些功能。
  • 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
  • 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。

4、扩展

  • 在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令,其具体结构图如图 3 所示。
import java.util.ArrayList;
public class CompositeCommandPattern{
    public static void main(String[] args){
        AbstractCommand cmd1=new ConcreteCommand1();
        AbstractCommand cmd2=new ConcreteCommand2();
        CompositeInvoker ir=new CompositeInvoker();
        ir.add(cmd1);
        ir.add(cmd2);
        System.out.println("客户访问调用者的execute()方法...");
        ir.execute();
    }
}
//抽象命令
interface AbstractCommand{
    public abstract void execute();
}
//树叶构件: 具体命令1
class ConcreteCommand1 implements AbstractCommand{
    private CompositeReceiver receiver;
    ConcreteCommand1(){
        receiver=new CompositeReceiver();
    }
    public void execute(){
        receiver.action1();
    }
}
//树叶构件: 具体命令2
class ConcreteCommand2 implements AbstractCommand{
    private CompositeReceiver receiver;
    ConcreteCommand2(){
        receiver=new CompositeReceiver();
    }
    public void execute(){
        receiver.action2();
    }
}
//树枝构件: 调用者
class CompositeInvoker implements AbstractCommand{
    private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>();   
    public void add(AbstractCommand c){
        children.add(c);
    }   
    public void remove(AbstractCommand c){
        children.remove(c);
    }   
    public AbstractCommand getChild(int i){
        return children.get(i);
    }   
    public void execute(){
        for(Object obj:children){
            ((AbstractCommand)obj).execute();
        }
    }    
}
//接收者
class CompositeReceiver{
    public void action1(){
        System.out.println("接收者的action1()方法被调用...");
    }
    public void action2(){
        System.out.println("接收者的action2()方法被调用...");
    }
}

责任链模式(职责链模式)

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了

1、定义与特点

  • 定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
  • 优点:
    • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
    • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
    • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
    • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
    • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
  • 缺点:
    • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
    • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
    • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

2、结构与实现

  • 模式的结构:
    • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
    • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
    • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
  • 模式的实现:image.pngimage.png
public class ChainOfResponsibilityPattern{
    public static void main(String[] args){
        //组装责任链 
        Handler handler1=new ConcreteHandler1(); 
        Handler handler2=new ConcreteHandler2(); 
        handler1.setNext(handler2); 
        //提交请求 
        handler1.handleRequest("two");
    }
}
//抽象处理者角色
abstract class Handler{
    private Handler next;
    public void setNext(Handler next){
        this.next=next; 
    }
    public Handler getNext(){
        return next; 
    }   
    //处理请求的方法
    public abstract void handleRequest(String request);       
}
//具体处理者角色1
class ConcreteHandler1 extends Handler{
    public void handleRequest(String request){
        if(request.equals("one")) {
            System.out.println("具体处理者1负责处理该请求!");       
        }els{
            if(getNext()!=null){
                getNext().handleRequest(request);             
            }else{
                System.out.println("没有人处理该请求!");
            }
        } 
    } 
}
//具体处理者角色2
class ConcreteHandler2 extends Handler{
    public void handleRequest(String request){
        if(request.equals("two")) {
            System.out.println("具体处理者2负责处理该请求!");       
        }else{
            if(getNext()!=null){
                getNext().handleRequest(request);             
            }else{
                System.out.println("没有人处理该请求!");
            }
        } 
    }
}

3、应用场景

  • 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
  • 可动态指定一组对象处理请求,或添加新的处理者。
  • 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求

4、扩展

  • 职责链模式存在以下两种情况:
    • 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
    • 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。

 

更多设计模式的分享,请查看https://blog.csdn.net/pp_l_ly/category_9961646.html

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值