软件设计模式-行为型模式

news2024/9/24 19:25:06

行为型模式

  • 行为型模式是对在不同的对象之间划分责任和算法的抽象化
  • 通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,他们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响其他对象的运行

image-20230102220931215

行为型模式可分为类行为型模式和对象行为型模式

  • 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类和子类的职责
  • 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为对象行为型模式主要通过对象关联等方式来分配两个或多个类的职责,根据合成复用原则,系统中要尽量使用关联关系来取代继承关系,因此大部分行

职责链模式

image-20230102222815841

职责链可以是一条直线,一个环或者一个树形结构,最常见的职责链是直线型。即沿着一条单向的链来传递请求

链上的每一个对象都是请求处理者,职责链模式可以将请求的处理者组织成一条链,并使请求沿着链传递,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理谢姐以及请求的传递,只需将请求发送到链上即可。将请求的发送者和请求的处理者解耦,这就是职责链模式的模式动机

职责链模式:避免请求发送者与接受者偶合在一起,让多个对象都有可能接受请求,将这些对象连接成一条链,并且沿着这条链传递请求,知道有对象处理它为止。由于英文翻译的不同,职责链模式又称为责任链模式,它是一种对象行为型模式

image-20230102223330946

public absrtact class Handler
{
    protected Handerle successor;
    public void setSuccessor(Handler successor)
    {
        this.sussessor = successor;
    }
    public abstract void handleRequest(String request);
}
public class ConcreateHandler extends Handler
{
    public void handleRequest(String request)
    {
        if(请求request满足条件)
        {
            ...//处理请求
        }
        Else
        {
            this.successor.handleRequest(request);//转发请求
        }
    }
}

image-20230102223918431

模式分析

  • 在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链
  • 请求在这条链上传递,直到链上的某一个对象处理此请求为止
  • 发出这个请求的客户端并不知道链上的那一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态的重新组织链和分配责任

实例:审批假条

OA系统需要提供一个假条审批的模块,如果员工请假天数小于3天,主任可以审批该假条;如果员工请假天数大于等于3天,小于10天,经理可以审批;如果员工请假天数大于等于10天,小于30天,总经理可以审批;如果超过30天,总经理也不能审批,提示相应的拒绝信息。

image-20230102224254791

public abstract class Leader{
    protected String name;
    protected Leader successor;
    public Leader(String name){
	this.name=name;
     }
   public void setSuccessor(Leader successor){						this.successor=successor;
}
public abstract void handleRequest(LeaveRequest request);
}
public class Director extends Leader{
   public Director(String name){
	super(name);
	}
    public void handleRequest(LeaveRequest request)
	{
	     if(request.getLeaveDays()<3){
			System.out.println("主任" + name + "审批员工" + request.getLeaveName() + "的请假条,请假天数为" + request.getLeaveDays() + "天。");
	   }
	   else	{			     
                     if(this.successor!=null)
	       {
		this.successor.handleRequest(request);
	         }
	   }
	}
}
public class GeneralManager extends Leader{
	public GeneralManager(String name)	{
		super(name);
	}
	
	public void handleRequest(LeaveRequest request){
		if(request.getLeaveDays()<30){
			System.out.println("总经理" + name + "审批员工" + request.getLeaveName() + "的请假条,请假天数为" + request.getLeaveDays() + "天。");
		}
		else{
                System.out.println("莫非" + request.getLeaveName() + "想辞职,居然请假" + request.getLeaveDays() + "天。");
		}
	}
}
public class LeaveRequest{
    private String leaveName;
    private int leaveDays;
	
      public LeaveRequest(String leaveName,int leaveDays) {
	this.leaveName=leaveName;
	this.leaveDays=leaveDays;
       }	
       public void setLeaveName(String leaveName) {
	this.leaveName = leaveName; 
       }
       public void setLeaveDays(int leaveDays) {
	this.leaveDays = leaveDays; 
         }
        public String getLeaveName() {
	return (this.leaveName); 
        }
          public int getLeaveDays() {
	return (this.leaveDays); 
       }	
}
public class ViceGeneralManager extends Leader{
    public ViceGeneralManager(String name){
		super(name);
    }
    public void handleRequest(LeaveRequest request){
          if(request.getLeaveDays()<20)	{
	System.out.println("副总经理" + name + "审批员工" + request.getLeaveName() + "的请假条,请假天数为" + request.getLeaveDays() + "天。");
            }	else
	{
			if(this.successor!=null)
			{
				this.successor.handleRequest(request);
			}
		}
	}
}
public class Client{
	public static void main(String args[])	{
		Leader objDirector,objManager,objGeneralManager,objViceGeneralManager;
		
		objDirector=new Director("王明");
		objManager=new Manager("赵强");
		objGeneralManager=new GeneralManager("李波");
		objViceGeneralManager=new ViceGeneralManager("肖红");
		
		objDirector.setSuccessor(objManager);
		objManager.setSuccessor(objViceGeneralManager);
		objViceGeneralManager.setSuccessor(objGeneralManager);
			
		LeaveRequest lr1=new LeaveRequest("张三",2);
		objDirector.handleRequest(lr1);
			
		LeaveRequest lr2=new LeaveRequest("李四",5);
		objDirector.handleRequest(lr2);
		
		LeaveRequest lr3=new LeaveRequest("王五",15);
		objDirector.handleRequest(lr3);
						
		LeaveRequest lr4=new LeaveRequest("赵六",25);
		objDirector.handleRequest(lr4);			
	}
}

模式有优缺点

  • 降低耦合度
  • 可简化对象的相互连接
  • 增强给对象指派职责的灵活性
  • 增加新的请求处理类很方便

职责链模式的缺点

  • 不能保证请求一定被接受
  • 系统性能将受到一定影响,而且在进行代码调试时不方便,可能会造成循环调用

在以下情况下可以使用职责链模式

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

模式应用

java的异常处理机制

try
{
    …… 			
}
catch(ArrayIndexOutOfBoundsException e1)
{
    ……
}
catch(ArithmeticException e2)
{
    ……
}
catch(IOException e3)
{
    ……
}
finally
{
    ……
}

早期的javaAut事件模型,事件浮生(Event bubbling)机制

jabascript事件浮生机制

image-20230102225929388

image-20230102230201357

image-20230102230206080

观察者模式

image-20230102230250020

image-20230102230256091

•股票行情与分析软件的设计

•股票行情发生变化了,软件必须让与该股票相关的各种指标报告也作相应的变化。

•证交所:提供基本的股票行情数据对象

•各软件开发商:设计自己的软件,提供各种指标分析报告。

•如何做到:不管你设计什么样的分析软件,行情数据对象不用修改。

image-20230102230340622

image-20230102230347614

模式动机

建立一种对象与对象之间以来的关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在此,发生改变的对象成为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互关系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机

模式定义

观察者模式,定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象全部都得到通知并被自动更新,观察者模式又叫做发布-订阅模式,模型-视图模式,源-监听器模式或从属者模式。观察者模式一种对象行为型模式

image-20230102230707930

  • subject:目标
  • concreatesubject:具体目标
  • observer:观察者
  • concreateobserver:具体观察者

模式分析

  • 观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统
  • 这一模式的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生变化,所有观察者都将得到通知
  • 作为对这个通知的相应,每个观察者都将及时更新自己的状态,以与目标状态同步,这种发布–订阅。目标是通知的发布者,他发出通知时并不需要知道谁是她的观察者,可以由任意数目的观察者并=订阅他并接收通知

典型的抽象目标类代码如下

import java.util.*;
public abstarct class Subject
{
    protected ArrayList observers = new ArrayList();
    public abstract void attach(Obserber obs);
    public abstract void detach(Obserber obs);
    public abstract void notify();
}
public class ConcreteSubject extends Subject
{
	public void attach(Observer observer)
	{
		observers.add(observer);
	}
	
	public void detach(Observer observer)
	{
		observers.remove(observer);
	}
	
	public void notify()
	{
		for(Object obs:observers)
		{
			((Observer)obs).update();
		}
	}	
} 
public interface Observer
{
    public void update();
}
典型的具体观察者代码如下所示
public class ConcreateObserver implements Observer
{
    public void update()
    {
        //具体更新代码
    }
}
客户端代码
Subject subject = new ConcreateSubject();
Observer observer = new ConcreateObserver();
subject.attach(observer);
subject.notify();

观察者模式实例与解析

实例一:猫狗与老鼠

假设猫是老鼠和狗的观察目标,老鼠和狗是观察者,猫叫老鼠跑,狗也跟着叫,使用观察者模式描述该过程。

image-20230102233348975

import java.util.*;
public abstract class MySubject
{
    protected ArrayList observers = new ArrayList();
    //注册方法
    public void attach(MyObserver observer)
    {
        observers.add(obseber);
    }
    //注销方法
        public void detach(MyObserver observer)
     {
	observers.remove(observer);
     }
	public abstract void cry(); //抽象通知方法
}
public class Cat extends Mysubject
{
    public void cry()
    {
        sout("猫叫!");
        sout("-------")for(Object obs:observers)
        {
            ((MyObservers)obs.response();
        }
    }
}
public interface MyObserver
             {
                 void response()//抽象响应方法
             }

public class Dog implements MyObserver
{
    public void response()
    {
        sout('狗跟着叫');
    }
}
public class pig implements MyObserver
{
    public void response(){
        sout(“猪没有反应”)
    }
}
public class Client
{
	public static void main(String a[])
	{
        Mysubject subject = new Cat();
        MyObserver obs1,obs2,obs3,;
        obs1 = new Mounse();
        obs2 = new Mouse();
        obs3 = new Dog();
        subject.attach(obs1);
        subject.attach(obs2);
        subject.attach(obs3);
        MyObserver obs4;
        obs4 = new Pig();
        subject.attach(obs4);
        subject.cry();
    }
}

观察者模式的优点

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合
  • 观察者模式支持广播通信
  • 观察者模式符合开闭原则

观察模式的缺点

  • 如果一个观察目标对象有很多直接或间接的观察者的话,将所有的观察者都通知会花费很多时间
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发他们之间进行循环调用,可能导致系统崩溃
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化

模式适用环境

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这些方面封装在独立的对象中使他们可以各自独立的改变和复用
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象发生改变,可以降低对象之间的耦合度
  • 一个对象必须通知其他对象,而并不知道这些对象是谁
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象,可以使用观察者模式创建一种链式触发机制

模式应用

在jdk1.1版本及以后的各个版本中,事件处理模型采用基于观察者模式的委派事件模型

在dem中,时间的发布者称为事件源,而订阅者叫做事件监听器,在这个过程中还可以通过事件对象来传递与事件相关的信息,可以在事件监听者的实现类中实现事件处理,因此事件监听对象又可以被称为事件处理对象。

事件源对象、时间监听对象和事件对象构成了java事件处理模型的三要素

  • 除了awt中的事件处理之外,java语言解析xml的技术sax2以及servlet技术的事件处理机制都基于dem,他们都是观察者模式的应用
  • 观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式

MVC模式是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。观察者模式可以用来实现MVC模式,观察者模式中的观察目标就是MVC模式中的模型(Model),而观察者就是MVC中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。

image-20230102235035481

命令模式

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道被请求的操作是哪个。我们只需在程序运行时制指定具体的请求接受者即可,此时可以使用命令模式来进行设计,使得请求发送者与请求接受者消除彼此之间的耦合,让对象之间的调用关系更加灵活

image-20230102235240753

在购买开关时,我们并不知道它将来到底用于控制什么电器,也就是说,开关与电灯、排气扇并无直接关系,一个开关在安装之后可能用来控制电灯,也可能用来控制排气扇或者其他电器设备

用户通过他来发送一个开灯请求,而电灯是开灯请求的最终接受者和处理者,在图中,开关和电灯之间并不存在直接耦合关系,他们通过电线连接在一起,使用不同的电线可以连接不同的请求接受者,只需更换一根电线,相同的发送者(开关)即可对应不同的接受者(电器)

命令模式可以对发送者和接受者完全解耦,发送者与接受者直接没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机

Sunny软件公司开发人员为公司内部OA系统开发了一个桌面版应用程序,该应用程序为用户提供了一系列自定义功能键,用户可以通过这些功能键来实现一些快捷操作。Sunny软件公司开发人员通过分析,发现不同的用户可能会有不同的使用习惯,在设置功能键的时候每个人都有自己的喜好,例如有的人喜欢将第一个功能键设置为“打开帮助文档”,有的人则喜欢将该功能键设置为“最小化至托盘”,为了让用户能够灵活地进行功能键的设置,开发人员提供了一个“功能键设置”窗口,该窗口界面如图所示:

通过如图所示界面,用户可以将功能键和相应功能绑定在一起,还可以根据需要来修改功能键的设置,而且系统在未来可能还会增加一些新的功能或功能键

//FunctionButton:功能键类,请求发送者
class FunctionButton {
	private HelpHandler help; //HelpHandler:帮助文档处理类,请求接收者
	
//在FunctionButton的onClick()方法中调用HelpHandler的display()方法
    public void onClick() {
		help = new HelpHandler();
		help.display(); //显示帮助文档
	}
}

如果使用上述代码,将给系统****带来什么问题?

(1) 由于请求发送者和请求接收者之间存在方法的直接调用,耦合度很高,更换请求接收者必须修改发送者的源代码,如果需要将请求接收者HelpHandler改为WindowHanlder(窗口处理类),则需要修改FunctionButton的源代码,违背了“开闭原则”

(2) FunctionButton****类在设计和实现时功能已被固定,如果增加一个新的请求接收者,如果不修改原有的FunctionButton类,则必须增加一个新的与FunctionButton功能类似的类,这将导致系统中类的个数急剧增加。由于请求接收者HelpHandler、WindowHanlder等类之间可能不存在任何关系,它们没有共同的抽象层,因此也很难依据“依赖倒转原则”来设计FunctionButton。

(3) 用户无法按照自己的需要来设置某个功能键的功能,一个功能键类的功能一旦固定,在不修改源代码的情况下无法更换其功能,系统缺乏灵活性。

模式动机

在软件开发中,我们经常需要像某些对象发送请求,调用其中的某个或某些方法,但是并不知道请求的接受者是谁,也不知道被请求的操作是哪个,此时我们特别希望能够以一种松耦合扥方式来设计软件,使得请求发送者与请求接受者能够消除彼此之间的耦合,让对象之间的调用关系更加灵活,可以灵活指定请求接受者以及被请求的操作,命令模式为此类问题提供了一个较为完美的解决方案

image-20230103000412235

  • 抽象命令类 Command
  • concraetecmmand具体命令类
  • Invoker调用者
  • Receiver接收者

命令模式本质是队请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分隔开,灭一个名利都是一个操作,请求的一方发出请求要求执行一个操作,接受的一方收到请求,并执行相应的操作,命令模式允许请求的一方和接受的一方独立开来,使得请求的一方不必知道接受请求一方的接口,更不必指导请求如何被接受,操作是否被执行,何时被执行,以及是怎么被执行的

命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体才与具体的请求接受者相关联。在最简单的抽象命令类类中只包含了一个抽象的execute方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体制定一个请求的接受者,不同的具体命令类提供了execute方法的不同实现,并调用不同接受者的请求处理方法。

实例一:电视机遥控器

电视机是请求的接受者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机不同的操作。抽象命令角色由一个命令接口来扮演。有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作,打开电视机,管壁电视机,,和切换频道,显然电视机遥控器就是一个典型的命令模式应用实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nn0ngw0Y-1672710695897)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103054012538.png)]

class Television
{
    public void open()
    {
        Console.WriteLine("打开电视机");
    }
    public void close()
    {
        Console.WriteLine("关闭电视机");
    }
    public void changeChannel()
    {
        Console.WriteLine("切换电视频道")
    }
}
public interface AbstractCommand
{
    void execute();
}
class TVChangedCommand:AbstractCommand
{
    private Television tv;
    public TVChangeCommand(){
        tv = new Television();
        
    }
    public void execute()
    {
        tv.changeChannel();
    }
}
 class TVCloseCommand : AbstractCommand
    {
        private Television tv;
        public TVCloseCommand()
        {
            tv = new Television();
        }
        public void execute()
        {
            tv.close();
        }
    }
 class TVOpenCommand : AbstractCommand
    {
        private Television tv;

        public TVOpenCommand()
        {
            tv = new Television();
        }
        public void execute()
        {
            tv.open();
        }
    }
class Controller
{
    private AbstractCommand openCommand,closeCommand,changeCommmand;
    public Controller(AbstractCommand openCommand,AbstractCommand closeCommand,AbstractCommand changeCommand)
    {
        this.openCommand=openCommand;
        this.closeCommand=closeCommand;
        this.changeCommand=changecmmand;
    }
    public void open()
    {
        openCommand.executer();
    }
    public void change()
    {
        changeCommand.execute();
    }
    public void close()
    {
        closemand.execute();
    }
}

实例二:功能键设置

为了用户使用方便,某系统提供了一些功能件,用户可以自定义功能键的功能,如功能键FunctionButtton可以用于退出系统,也可以用来打开帮助界面displayhelpclass,用户可以通过修改配置文件来改变功能键的用途,现使用命令模式来设计该系统,使得功能键类与功能类之间解耦,相同的功能键可以对应不同的功能

命令模式的优点

  • 降低系统的耦合度
  • 新的命令可以很容易加入系统中
  • 可以比较容易的设计一个命令队列和宏命令(组合命令)
  • 可以方便的实现队请求的undo和redo

命令模式的缺点

使用命令模式可能会导致某些系统有过多的具体命令类,因为针对每一个命令都需要设计一个具体命令类,因此某些系统系统可能需要大量具体命令类,这将影响命令模式的使用

适用环境

  • 系统需要将请求调用者和请求接受者解耦,使得调用者和接受者不直接交互
  • 系统需要在不同的时间指定请求,将请求排队和执行请求
  • 系统需要支持命令的撤销undo操作和回复redo操作
  • 系统需要将一组操作组合在一起,即支持宏命令

模式应用

  1. java语言使用命令模式实现AWT/SWING GUI委派事件模型,在AWT/Swing,Frame。button等界面组件是请求发送者,而AWT提供的事件监听器接口和事件适配器类是抽象命令接口,用户可以自己写抽象命令接口的子类来实现事件处理,即实现具体命令类,而在具体命令类中可以调用业务处理方法来实现该事件的处理,对于界面组件而言,只需要了解命令接口即可,无需关心接口的实现,组件类并不关心实际操作,而操作由用户来实现
  2. 很多系统都提供了宏命令功能,如unix平台的shell编程,可以将多条命令封装在一个命令对象中,只需要一条简单的命令即可执行一个命令序列,这也是命令模式的应用实例之一

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXkZ1CIK-1672710695898)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103060201357.png)]

  • 宏命令又称为组合命令,他是命令模式和组合模式联用的产物
  • 宏命令也是一个具体命令,不过它包含了对其他命令对象的应用,在调用宏命令的execute方法时,降低归调用它所包含的每个成员命令的execute方法,一个宏命令的成员对象可以是简单命令,还可以继续是宏命令,执行一个宏命令将执行多个具体命令。从而实现对命令的批处理
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFP0JSK7-1672710695898)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103060346047.png)]

策略模式

完成一项任务,往往可以有很多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务

在软件开发多个途径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,也能够方便的增中也常常遇到类似的情况,实现某一个功能有新的解决途径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FAz92pdd-1672710695899)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103060527557.png)]

在软件系统中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。

这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。

除了提供专门的查找算法类之外,还可以在客户端程序中直接包含算法代码,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

为了解决这些问题,可以定一些独立的类来封装不同的算法,每一个类封装在一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略,为了保证策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对对应一个具体策略类

策略模式定义一些列算法,将每一个算法封装起来,并让他们可以相互替换,策略模式让算法独立与使用他的客户而变化,策略模式是一种对象行为型模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V8mEVm9h-1672710695900)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103061246060.png)]

context:环境类

strategy:抽象策略类

concreateStategy:具体策略类

策略模式是一个比较容易理解和使用的设计模式,策论模式是对算法的封装,他把算法的责任和算法本身分隔开,委派给不同的对象管理,策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类,用一句话来说就是准备一组算法,并将每一个算法封装起来,使得他们可以互换

不同策略模式的代码
public class Context
{
    public void algorithm(String type)
    {
        if(type=="strategyA")
        {
            //算法a
        }
        else if(type=='strategyB')
        {
            //算法b
        }
        else if(type=="strategyC")
        {
            //算法c
        }
    }
}

重构之后的抽象策略类

public abstract class AbstractStrategy
{
    public abstract void algorithm();
}
重构之后的具体策略类
 public class ConcreateStragyA extends AbstractStrategy
 {
     public void algorithm()
     {
         //算法a
     }
 }
重构之后的环境类
public class Context
{
    private AbstractStragety stragy
     public void setStrategy(AbstractStrategy strategy)
    {
        this.strategy= strategy;
    }
	public void algorithm()
    {
        strategy.algorithm();
    }
}
客户端代码
Context context = new Context();
AbstractStrategy strategy;
strategy = new ConcreateStrategyA();
context.setStrategy(strategy);
cntext.algorithm();

模式分析

在策略模式中,应当由客户端自己决定在上面情况下使用什么具体策略角色

策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中退休的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定,这在一定程度上提高了系统的灵活性,但是客户端需要理解所有策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程序上增加了客户端的使用难度

实例1:排序策略

实例一:排序策略

•某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,也能够灵活地增加新的排序算法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhlLlicn-1672710695900)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103063414951.png)]

interface sort
{
    int[] sort(int[] arr);
}
class BubbleSort implements Sort
{
    {
        public int[] sort(int[] arr)
        {
            int len = arr.Length;

            for (int i = 0; i < len; i++)
            {
                for (int j = i+1; j < len; j++)
                {
                    int temp;
                    if (arr[i] > arr[j])
                    {
                        temp = arr[i];
                        arr[i] = arr[j];
                        arr[j] = temp;
                    }
                }
            }
            Console.WriteLine("冒泡排序");
            return arr;
        }
    }
}

 class SelectionSort : Sort    {
        public int[] sort(int[] arr)   {
            int len = arr.Length;
            int temp;

            for (int i = 0; i < len; i++)          {
                temp = arr[i];
                int j;
                int smalllestLocation = i;

                for ( j = i+1; j < len; j++)
                {
                    if (arr[j] < temp)
                    {
                        temp = arr[j];
                        smalllestLocation = j;
                    }
                }

                arr[smalllestLocation] = arr[i];
                arr[i] = temp;
            }
            Console.WriteLine("选择排序");
            return arr;
        }
    }
 class InsertionSort : Sort    {
        public int[] sort(int[] arr)        {
            int len = arr.Length;

            for (int i = 1; i < len; i++)            {
                int j;
                int temp = arr[i];
                for (j = i; j >0; j--)
                {
                    if (arr[j - 1] > temp)
                    {
                        arr[j] = arr[j - 1];
                    }
                    else
                    {
                        break;
                    }
                    arr[j] = temp;                    
                }               
            }
            Console.WriteLine("插入排序");
            return arr;
        }
    }
class ArrayHandler
{
    private Sort sortObj;
    public int[] sort(int[] arr)
    {
        sortObj.sort(arr);
        return arr;
    }
    public void setSortObj(Sort sortObj)
        {
            this.sortObj=sortObj;
        }

}

实例二:旅行出行策略

•旅游出行方式可以有多种,如可以乘坐飞机旅游,也可以乘火车旅游,如果有兴趣自行车游也是一种极具乐趣的出行方式。不同的旅游出行方式有不同的实现过程,客户根据自己的需要选择一种合适的旅游方式。在本实例中我们用策略模式来模拟这一过程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PdjuKK9P-1672710695901)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103064045260.png)]

策略模式的优点

  • 策略模式提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或者行为,也可以灵活的增加新的算法或行
  • 策略模式体用了管理员相关的算法族的办法,策略模式提供了可以替换继承关系的办法,
  • 使用策略模式可以避免使用多重条件转移语句

策略模式的缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
  • 策略模式将造成产生很多策略类,可以通过享元模式在一定程度上减少对象的数量

使用环境

  • 如果一个系统里面有很多类,他们之间的区别仅仅在于他们的行为,那么使用策略模式可以动态的让一个对象的许多行为中选择一种行为
  • 如果一个对象有很多行为,如果不用恰当的模式,在这些行为就只好使用多重的条件选择语句来实现,不希望客户端知道复杂的预算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性

模式应用

Java SE的容器布局管理就是策略模式应用的一个经典实例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3PapSfPs-1672710695901)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103064635527.png)]

微软公司提供的演示项目PetShop 4.0中使用策略模式来处理同步订单和异步订单的问题。

•在Petshop 4.0的BLL子项目中有一个OrderAsynchronous类和一个OrderSynchronous类,它们都继承自IOrderStrategy。OrderSynchronous以一种同步的方式处理订单,而OrderAsynchronous先将订单放在队列里,然后再对队列里的订单进行处理,以一种异步方式对订单进行处理。而在BLL中的Order类里通过反射机制从配置文件中读取策略配置的信息以决定到底是使用哪种订单处理方式。只需要修改配置文件即可更改订单处理方式,提高了系统的灵活性。

策略模式与状态模式

可以通过环境类状态的个数来决定是使用策略模式还是状态模式

•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。

•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。

•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。

•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

状态模式

在很多情况下,一个对象的行为取决一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态对象,这样的对象状态是从事先定义好的一系列值中取出的,当一个这样的对象与外部事件产生互动时,内部状态就会改变,从而使得系统的行为也随之改变。

在UML中可以使用状态图来描述对象状态的变化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXb5zKri-1672710695902)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103065338044.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-urUnPXC3-1672710695903)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103065342322.png)]

模式定义

状态模式:允许一个对象在其内部状态改变时改变他的行为,对象看起来似乎修改了他的类,其别名为状态对象,状态模式是一种对象行为型模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FfMfWQH-1672710695904)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103065717646.png)]

context:环境类

state:抽象状态类

concreateState:具体状态类

模式分析

状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为

状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,,而对象的每一种具体状态类都集成了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTshTPel-1672710695905)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103065954466.png)]

if(state=="空闲"){
	if(预订房间)	{
		预订操作;
		state="已预订";
	}
	else if(住进房间)	{
		入住操作;
		state="已入住";
	}
}
else if(state=="已预订"){
	if(住进房间)	{
		入住操作;
		state="已入住";
	}
	else if(取消预订)	{
		取消操作;
		state="空闲";
	}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ukYaEeq-1672710695905)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103070335885.png)]

//重构之后的空闲状态类实例代码
if(“预订房间)
{
    预定操作;
    context.setState(new 已预订状态类());    
}
else if(朱进房间)
{
    入驻操作;
    context.setState(new 已入住状态类());
}

在状态模式结构中需要理解环境类与抽象状态类的的作用:

  • 环境类实际上就是拥有状态的对象,环境类有时候可以充当状态管理器的角色,,可以在环境类对状态进行切换状态
  • 抽象状态类可以是抽象类,也可以是首届口。不同状态类就是继承这个父类的不同子类,状态类的产生是由于环境类存在多个状态,同时还满足两个条件:这些状态经常需要切换,在不同的状态下对象的行为不同,因此可以将不同对象下的行为单独提取封装在具体的状态类中,使得环境类对象在其内部状态改变时可以改变他的行为。对象看起来似乎修改了他的类、而实际上是由于切换到不同的具体状态类实现的。由于环境类可以设置为任意具体状态类,因刺激他针对抽象状态进行编程,在程序运行时可以将任意具体状态类的对象设置到环境类中,从而使得环境类可以改变内部状态,并且改变行为。

实例一:论坛用户等级

在某论坛系统中,用户可以发表留言,发表留言将增加积分;用户也可以回复留言,回复留言也将增加积分;用户还可以下载文件,下载文件将扣除积分。该系统用户分为三个等级,分别是新手、高手和专家,这三个等级对应三种不同的状态,这三种状态分别定义如下:

(1) 如果积分小于100分,则为新手状态,用户可以发表留言、回复留言,但是不能下载文件。如果积分大于等于1000分,则转换为专家状态;如果积分大于等于100分,则转换为高手状态。

(2) 如果积分大于等于100分但小于1000分,则为高手状态,用户可以发表留言、回复留言,还可以下载文件,而且用户在发表留言时可以获取双倍积分。如果积分小于100分,则转换为新手状态;如果积分大于等于1000分,则转换为专家状态;如果下载文件后积分小于0,则不能下载该文件。

(3) 如果积分大于等于1000分,则为专家状态,用户可以发表留言、回复留言和下载文件,用户除了在发表留言时可以获取双倍积分外,下载文件只扣除所需积分的一半。如果积分小于100分,则转换为新手状态;如果积分小于1000分,但大于等于100,则转换为高手状态;如果下载文件后积分小于0,则不能下载该文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGG3p7v5-1672710695906)(C:/Users/xiaozhao/AppData/Roaming/Typora/typora-user-images/image-20230103075509524.png)]

public abstract class AbstractState
{
    protected ForumAccount acc;
    private int point;
    public int Point;
   {
      get { return point; }
      set { point = value; }
   }
    private string stateName;
    public string StateName
 	{
      get { return stateName; }
       set { stateName = value; } 
    }
    public abstract void checkState(int score);
    public abstract void downloadFile(int score);
    public abstract void writeNote(int score);
    public abstract void replyNote(int score);


}
public class ForumAccount
{
    pribate Abstract state;
    pribate String name;
    public ForumAccount(string name)        {
            this.name = name;
            this.state = new PrimaryState(this);
            Console.WriteLine(this.name+"注册成功!");
       }
public void setState(AbstractState state)    {
            this.state = state;
        }
        public AbstractState getState()  {
            return this.state;
        }
        public string getName()    {
            return this.name;
        }
        public void downloadFile(int score)    {
            state.downloadFile(score);
        }
        public void writeNote(int score)     {
            state.writeNote(score);
        }
        public void replyNote(int score)     {
            state.replyNote(score);
        }

}
public class High State extends AbstractState
{
   public HighSate(ForumAccount acc)
   {
       this.acc=acc;
       this.point=acc.getState().point;
       this.StateName="专家";
       
   }
    public override void writeNote(int score)
    {
        sout(acc.getName()+"发布留言"+"积分增加"+score*2);
        this.point=this.point+score*2;
        checkState(score);
        Console.writeLine("剩余积分"+this.Point+"当前级别"+acc.getState().StateName);
        
    }
    public overide void checkState(int score)
    {
        if(Point<0)
        {
            sout("余额不足,文件下载失败");
            this.point+=score;
        }
        else if(Point<=100)
        {
            acc.setState(new PrimaryState(acc));
        }
        else if(Point<1000)
        {
            acc.setState(new MiddleState(acc));
        }
    }
     public  override void downloadFile(int score)     {
            Console.WriteLine(acc.getName()+"下载文件,扣						除"+score+"/2积分" );
            this.Point -= score / 2;
            checkState(score);
            Console.WriteLine("剩余积分" + this.Point + ",当前级别" + acc.getState().StateName);
            
        }
        public override void replyNote(int score)
        {
            Console.WriteLine("回复留言加分" + score);
            this.Point += score;
            checkState(score);
            Console.WriteLine("你的当前积分为" + Point + ",当前级别为" + acc.getState().StateName);
        }
    }

}
 class MiddleState:AbstractState     {
        public MiddleState(ForumAccount acc)       {         
            this.acc = acc;
            this.Point = acc.getState().Point;      
            this.StateName = "高手";
        }
        public override void writeNote(int score)   {
            Console.WriteLine(acc.getName()+"发布留言"+"积分增加"+score*2);
            this.Point = this.Point + score * 2;
            checkState(score);
            Console.WriteLine("剩余积分"+this.Point +",当前级别"+acc.getState().StateName);
        }
        public override void checkState(int score)   {
            if (Point >= 1000)      {
                acc.setState(new HighState(acc));
            }
            else if(Point <0)     {
                Console.WriteLine("余额不足,下载失败");
                this.Point = this.Point + score;
            }
            else if (Point <=100)   {
                acc.setState(new PrimaryState(acc));
            }
        }
 public override void replyNote(int score)
        {
            Console.WriteLine("回复留言加分" + score);
            this.Point += score;
            checkState(score);
            Console.WriteLine("你的当前积分为" + Point + ",当前级别为" + acc.getState().StateName);
        }

        public override void downloadFile(int score)
        {
            Console.WriteLine("下载该文件需扣分" + score);
            this.Point -= score;
            checkState(score);
            Console.WriteLine("你的当前积分为" + Point + ",当前级别为" + acc.getState().StateName);
        }
    }
public class PrimaryState:AbstractState    {
        public PrimaryState(ForumAccount acc)   {
            this.acc = acc;
            if (acc.getState() != null)       {
                this.Point = acc.getState().Point;
            }
            else
                this.Point = 0;
            this.StateName = "新手";
        }
        public override void replyNote(int score)        {
            Console.WriteLine("回复留言加分" + score);
            this.Point += score;
            checkState(score);
            Console.WriteLine("你的当前积分为" + Point + ",当前级别为" + acc.getState().StateName);
        }

        public override void checkState(int score)  {
            if (Point >= 1000)       {
                acc.setState(new HighState(acc));
            }
            else if(Point >=100)   {
                acc.setState(new MiddleState(acc));
            }
        }       
public override void writeNote(int score)    {
            Console.WriteLine("发布留言加分" + score);
            this.Point += score;
            checkState(score);
            Console.WriteLine("你的当前积分为" + Point + ",当前级别为" + acc.getState().StateName);
        }

        public override void downloadFile(int score)   {
            Console.WriteLine("对不起,"+acc.getName()+",你没有下载权限" );            
        }
    }
static void Main(string[] args)       {
            ForumAccount account = new ForumAccount("张三");
            account.writeNote(20);
            Console.WriteLine("-----------------------");          
            account.downloadFile(20);
            Console.WriteLine("-----------------------");
            account.replyNote(100);          
            Console.WriteLine("-----------------------");           
            account.writeNote(40);
            Console.WriteLine("-----------------------");
            account.downloadFile(80);         
            Console.WriteLine("-----------------------");
            account.downloadFile(150);
            Console.WriteLine("-----------------------");
            account.writeNote(1000);
            Console.WriteLine("-----------------------");
            account.downloadFile(80);
            Console.WriteLine("-----------------------");

            Console.Read();
        }

实例二:银行账户

•在某银行系统定义的账户有三种状态:

(1) 如果账户(Account)中余额(balance)大于等于0,此时账户的状态为绿色(GreenState),即正常状态,表示既可以向该账户存款(deposit)也可以从该账户取款(withdraw);

(2) 如果账户中余额小于0,并且大于等于-1000,则账户的状态为黄色(YellowState),即欠费状态,此时既可以向该账户存款也可以从该账户取款;

(3) 如果账户中余额小于-1000,那么账户的状态为红色(RedState),即透支状态,此时用户只能向该账户存款,不能再从中取款。

•现用状态模式来实现状态的转化问题,用户只需要执行简单的存款和取款操作,系统根据余额数量自动转换到相应的状态。

image-20230103081652560

状态模式的优点

  • 封装了转换规则
  • 枚举可能的抓住昂泰,在枚举状态之前需要确定状态种类
  • 将所有于某个状态有关的行为放到一个类中,并且可以方便的增加新的状态,只需要改变对象状态即可改变对象的行为
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数

状态模式的缺点

  • 状态模式的使用必然会增加系统类和对象的个数
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的修改模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码

使用情况

  • 对象的行为依赖于他的状态(属性)并且可以根据他的状态改变而改变他的相关行为
  • 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便的增加和删除状态,使客户类与类库之间的耦合增强,在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态

模式应用

  1. 状态模式在工作流或游戏等类型的软件的得以广泛使用,甚至可以用与这些系统的核心功能设计,如在政府OA办公系统中,一个批文的状态有多种,尚未办理,正在办理正在批示,正在审核,已经完成等各种状态,而且批文状态不同时对批文的操作也有所差异,使用状态模式可以描述工作流对象的状态以及不同状态下他所具有的行为

  2. 在目前主流的RPG(Role Play Game,角色扮演游戏)中,使用状态模式可以对游戏角色进行控制,游戏角色的升级伴随着其状态的变化和行为的变化。对于游戏程序本身也可以通过状态模式进行总控,一个游戏活动包括开始、运行、结束等状态,通过对状态的控制可以控制系统的行为,决定游戏的各个方面,因此可以使用状态模式对整个游戏的架构进行设计与实现。

模式扩展

在有些情况下需要多个环境对象共享同一个状态,如果希望在系统中实现多个环境对象实例共享一个或多个状态对象,那么需要将这些状态对象定义为环境的静态成员对象

简单状态模式与可切换状态的状态模式

简单状态模式:简单状态模式是指状态都相互独立,状态之间无须进行转换的状态模式,这是最简单的一种状态模式,对于这种状态模式,每个状态类都封装与状态相关的操作,而无须关心状态的切换,可以在客户端直接实例化状态类,然后将状态对象设置到环境类中,如果是这种简单的状态模式,他遵循开闭原则,在客户端可以针对抽象类进行编程,而将具体状态类写到配置文件中,同时增加新的状态类对原有系统也不造成任何影响

  1. 可切换状态的状态模式:大多数的状态模式都是可以切换状态的状态模式,在实现状态切换时,在具体状态类内部需要调用环境类Context的setState()方法进行状态的转换操作,在具体状态类中可以调用到环境类的方法,因此状态类与环境类之间通常还存在关联关系或者依赖关系。通过在状态类中引用环境类的对象来回调环境类的setState()方法实现状态的切换。在这种可以切换状态的状态模式中,增加新的状态类可能需要修改其他某些状态类甚至环境类的源代码,否则系统无法切换到新增状态。

模板方法模式

image-20230103091638557

#模板方法模式的是基于继承的代码服用基本技术,模板方法模式的结构和用户也是面向对象涉及的核心之一,在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中

在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑,不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意,模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式

模式定义

模板方法模式定义一个操作算的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法某些特定步骤。模板方法是一种类行为型模式。

image-20230103092246250

  • 模板方法模式是一种类的行为型模式,在它的结构图只有类之间的继承关系,没有对象关联关系
  • 在模板方法模式的使用过程中,要求开发抽象类和开发具体子类的设计师之间进行协作,一个设计是负责给出一个算法的轮廓和骨架,另一些设计师负责给出这个算法的各个逻辑步骤。实现这些具体逻辑步骤的方法成为基本方法,而将这些基本法汇总起来的方法称为模板方法,模板方法模式的名字从此而来
  • 模板方法:一个模板方法是定义在抽象类中的,把基本操作方法组合在一起形成一个算法或总行为的方法
  • 基本方法:基本方法是实现算法各个步骤的方法,是模板方法发 的组成部分
    • 抽象方法
    • 具体方法
    • 钩子方法
……
public void template()
{
    open();
    display();
    if(isPrint())
    {
        print();
    }
}
public boolean isPrint()
{
    return true;
}
……

典型的抽象类代码如所示
public abstract class AbstractClass
{
    public void templateMethod()//模板方法
    primitiveOperation1();
    primitiveOperation2();
    primitiveOperation3();
    public void primitiveOperation1()//基本方法--具体方法
    {
        //实现代码
    }
    public abstract void primitiveOperation2();//基本方法--抽象方法
    public void primitiveOperation3()//基本方法--钩子方法
    {
        
    }
}
典型的具体自类代码如下所示
public class ConcreateClass extends AbstractClass
{
    public void primitiveOperation2()
    {
        //实现代码
        
    }
    public void primitiveOperation3()
    {
        //实现代码
    }
}

在模板方法模式中,由于面向对象的多态性,子类对象在运行时将覆盖父类对象,子类中定义的方法也将覆盖父类中定义的方法,由此在程序运行时,具体子类的基本方法覆盖父类中定义的基本方法,子类的钩子方法也将覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现类对父类行为的反向控制

银行业务办理流程

•在银行办理业务时,一般都包含几个基本步骤,首先需要取号排队,然后办理具体业务,最后需要对银行工作人员进行评分。无论具体业务是取款、存款还是转账,其基本流程都一样。现使用模板方法模式模拟银行业务办理流程。image-20230103093140676

public abstract class BankTemplateMethod
{
    public void takmeNumber()
    {
        console.writeLine("取号排队");
    }
    public abstract void transact();
    public void evaluate()
    {
        console.writeLine("反馈评分")
    }
    public void process()
    {
        takeNumber();
        transact();
        evaluate();
    }
}
class Deposit:BankTemplateMethod
    {
        public override void transact()
        {
            Console.WriteLine("存款");
        }
    }
 class Transfer : BankTemplateMethod
    {
        public override void transact()
        {
            Console.WriteLine("转账");
        }
    }
 class Withdraw:BankTemplateMethod
    {
        public override void transact()
        {
            Console.WriteLine("取款");
        }
    }
Client{
    psvm
    {
        BankTemplateMethod bank;
        bank = new Deposit();
        bank.process();
        Console.read();
    }
}

实例二:数据库操作模板

•对数据库的操作一般包括连接、打开、使用、关闭等步骤,在数据库操作模板类中我们定义了connDB()、openDB()、useDB()、closeDB()四个方法分别对应这四个步骤。对于不同类型的数据库(如SQL Server和Oracle),其操作步骤都一致,只是连接数据库connDB()方法有所区别,现使用模板方法模式对其进行设计。

image-20230103094028157

模板方法模式的优点

  • 模板方法模式在一个列中形式化的定义算法,而由他的子类实现细节的处理
  • 模板方法模式是一种代码的基本技术,模板方法模式导致一种方向的控制结构,通过一个父类调用其子类的操作,通过对于子类的扩展增加新的行为,符合开闭原则

模板方法模式的优点

  • 模板方法模式在一个类中形式化定义算法,而由他的子类实现细节的处理
  • 模板方法模式是一种代码复用的基本技术
  • 模板方法模式导致一种返乡的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合开闭原则

模板方式的缺点

每个不同的实现都需要定义一个子类,这将导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合单一智者原则,使得类的内聚性得以提高

在以下情况中可以使用模板方法模式

  • 一次性实现一个算法的不变的部分,将可变的行为留给子类进行实现
  • 个子类中共的行为硬背提取出来并集中到一个公共子类中以避免代码重复
  • 对一些复杂的算法进行分割,,将其算法固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现
  • 控制子类的扩展

模式应用

(1) 模板方法模式广泛应用于框架设计(如Spring,Struts等)中,以确保父类控制处理流程的逻辑顺序(如框架的初始化)。

……
public void runBare() throws Throwable {
 setUp();
 try {
  runTest();
 }
 finally {
  tearDown();
 }
}
……

模板方法模式鼓励我们恰当使用继承,此模式可以用来改写一些相同功能的相关类,将可服用的一般性的行为diamante移到父类里面,,而将特殊化的代码移到子类里面,这也进一步说明,虽然继承复用存在一些问题,但是在某些情况下还是可以给开发人员带来方便,模板方法模式就是体现继承优势的模式之一

  • 在模板方法模式中,子类不显示调用弗雷德方法,,而是通过覆盖子类的方法实现某些具体的业务逻辑,父类控制子类的调用,这种机制被称为好莱坞原则,
  • 在模板方法模式中,好莱坞原则体现在,子类不需要调用父类,而通过父类来调用子类,将某些步骤的实现卸载子类中由父类来控制整个过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/137400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据赋能的未来,看向嵌入式BI

数据分析能力越来越成为消费者和企业的必备品应用程序&#xff0c;复杂程度各不相同&#xff0c;从简单地一个网页或门户上托管一个可视化或仪表板&#xff0c;到在一个云服务上实现数据探索、建模、报告和可视化创建的应用程序。BI的实现方式越来越多&#xff0c;无论规模大小…

南京晓庄操作系统期末复习【大题】

操作系统期末复习大题第六章磁盘调度寻道时间与移动次数转换I/O中断请求第五章地址转换页面置换第四章动态分区地址转换第三章银行家算法处理机调度算法第二章进程同步第一章多道运行时间第六章 磁盘调度 前提小知识&#xff1a; 1.先来先服务&#xff08;FCFS&#xff09;:…

ros版本apollo7.0.0规划控制算法

apollo.ros-7.0.0 上次给大家带来了之前学习apollo时开发的内容apollo.ros-1.0.0和apollo.ros-3.0.0&#xff0c;主要是针对apollo 1.0.0和3.0.0版本进行了ros1下的移植和规划控制算法的学习。本次在之前工作的基础上&#xff0c;针对apollo 7.0.0版本&#xff0c;进行了ros1下…

第二章:Linux常见指令以及权限理解

系列文章目录 文章目录系列文章目录前言一、Linux下基本概念指令操作操作系统的概念命令选项文件的概念Linux文件结构文件路径Linux下一切借文件二、Linux下基本指令ls&#xff1a; 显示当前目录下的文件名mkdir/rmdir&#xff1a;在当前路径下创建或删除目录pwd&#xff1a; 显…

国产智能2/4DIN+2/4 继电器输入输出MODBUS RTU数据采集IO模块

MODBUS RTU数据采集IO模块简介 DAMx 系列模块为 2/4 路开关量输入监测、2/4 路继电器输出控制模块。通讯接口为 1 路 RS-485 口&#xff0c;MODBUS-RTU 通讯协议。DC9&#xff5e;36V 电源供电。 DAM 系列模块可应用于各种工业自动化测量与控制系统中。开关量输出可控制中间继电…

educoder头歌数据结构 查找 第2关:实现散列查找(答案无错AC版)

本文已收录于专栏 &#x1f332;《educoder数据结构与算法_大耳朵宋宋的博客-CSDN博客》&#x1f332; 任务描述 本关要求通过补全函数ILH_InsKey和ILH_DelKey来分别实现插入和删除操作。 相关知识 本关讨论散列存储&#xff0c;散列函数使用除留余数法&#xff0c;冲突解决…

shell第六天作业——正则表达式与grepsed

题目 一、正则表达式与grep 1、显示/etc/rc.d/init.d/README文件中以不区分大小的h开头的行&#xff1b; 2、显示/etc/passwd中以sh结尾的行; 3、显示/etc/fstab中以#开头&#xff0c;且后面跟一个或多个空白字符&#xff0c;而后又跟了任意非空白字符的行&#xff1b; 4、…

Lemon LemonLime 中 SPJ Special Judge 使用 实践 入门 a

入门&#xff1a;题目&#xff0c;以整数形式给定圆的半径&#xff0c;输出该圆的周长&#xff0c;该圆的面积。比赛目录如下&#xff1a;标准输入输出数据如下&#xff1a;circle1.in1circle1.ans6.283185 3.141593circle2.in2circle2.ans12.566370 12.566370circle3.in3circl…

【IIC/I2C--温湿度传感器——GPIO模拟IIC协议】

IIC/I2C--温湿度传感器——GPIO模拟IIC协议IIC总线时序起始信号停止信号数据传输信号应答和非应答信号寻址IIC协议1.开始传输&#xff1a;2.发送您的数据&#xff1a;3.结束传输&#xff1a;4.注意&#xff1a;编写代码实验结果IIC总线时序 起始信号 1&#xff09;起始信号和停…

装饰器模式

装饰器模式 1.装饰器模式介绍 初看上图感觉装饰器模式有点像俄罗斯套娃、某众汽车&#x1f695;&#xff0c;而装饰器的核心就是再不改原有类的基础上给类新增功能。不改变原有类&#xff0c;可能有的小伙伴会想到继承、AOP切面&#xff0c;当然这些方式都可以实现&#xff0c…

Java 并发编程知识总结【四】

5. 线程通信 5.1 Lock 接口 是什么&#xff1a;Lock 实现提供比使用 synchronized 方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化&#xff0c;可能具有完全不同的属性&#xff0c;并且可以支持多个相关联的对象 Condition Lock 接口的实现 ReentrantLock …

论文的未来走向及创新(1)

论文题目 Zero-Shot Learning across Heterogeneous Overlapping Domains 基准方法 Nave Bayes (Unigram) P(utterance∣domain)P(utterance | domain)P(utterance∣domain) is modeled with Nave Bayes model with features being word unigrams in the utterance. Nave Ba…

centos7修改默认yum源为国内源

centos7修改默认yum源为国内源 文章目录centos7修改默认yum源为国内源操作系统环境信息修改yum源步骤修改后效果yum下载的默认镜像地址速度很慢&#xff0c;一般会更改镜像源为国内的镜像地址&#xff0c;比如163&#xff0c;阿里云等&#xff0c;本文介绍下修改yum源的步骤。操…

【回答问题】ChatGPT上线了!给我推荐20个比较流行的图像分割模型!

给我推荐20个比较流行的图像分割模型 U-Net Mask R-CNN FCN DeepLab PSPNet LinkNet UNet SegNet Tiramisu DenseASPP ICNet ENet DANet DenseSeg Fast-SCNN BiSeNet DUNet MaskLab Dense UNet Attention U-Net给我推荐20个比较流行的图像分割模型源码 好的&#xff0c;下面是…

JMeter自定义函数

JMeter二次开发需求一般集中在2个方向&#xff1a;针对请求类&#xff0c;即需要和服务器进行通信的&#xff0c;我们一般是做java请求的二次开发针对数据处理类型&#xff0c;是不需要和服务器进行通信&#xff0c;是在客户端完成的&#xff0c;一般是做JMeter的自定义函数除了…

【谷粒商城基础篇】商品服务开发:属性分组、平台属性

谷粒商城笔记合集 分布式基础篇分布式高级篇高可用集群篇简介&环境搭建项目简介与分布式概念&#xff08;第一、二章&#xff09;基础环境搭建&#xff08;第三章&#xff09;整合SpringCloud整合SpringCloud、SpringCloud alibaba&#xff08;第四、五章&#xff09;前端知…

若依RuoYi整合短信验证码登录

背景&#xff1a;若依默认使用账号密码进行登录&#xff0c;但是咱们客户需要增加一个短信登录功能&#xff0c;即在不更改原有账号密码登录的基础上&#xff0c;整合短信验证码登录。 一、自定义短信登录 token 验证 仿照 UsernamePasswordAuthenticationToken 类&#xff0c…

使没有sudo权限的普通用户可以使用容器

一、基本思路将普通用户加入docker组二、ubuntu组管理命令1、配置文件&#xff08;1&#xff09;文件&#xff1a;/etc/group&#xff08;2&#xff09;权限&#xff1a;①超级用户可读可写②普通用户只读2、查看组&#xff08;1&#xff09;命令cat /etc/group&#xff08;2&a…

【从零开始学习深度学习】34. Pytorch-RNN项目实战:RNN创作歌词案例--使用周杰伦专辑歌词训练模型并创作歌曲【含数据集与源码】

目录RNN项目实战使用周杰伦专辑歌词训练模型并创作歌曲1.语言模型数据集预处理1.1 读取数据集1.2 建立字符索引1.3 时序数据的2种采样方式1.3.1 随机采样1.3.2 相邻采样小结2. 从零实现循环神经网络并进行训练预测2.1 one-hot向量表示2.2 初始化模型参数2.3 定义模型2.4 定义预…

2023 年更新计划

前言 2023 年&#xff0c;会继续更新这个 CSDN 博客了&#xff1b; 看了一下博客数据&#xff0c;有些惨不忍睹&#xff0c;不过之前的内容质量并不高&#xff0c;从头来过吧&#xff1b; 当初个人娱乐写的 STM32 学习笔记&#xff0c;莫名受欢迎&#xff0c;不出意外的话&am…