设计模式—— 十六 :责任链模式



什么是责任链模式?

职责链模式的定义:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关 系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。)

责任链模式的重点是在“链”上,由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果,

通用类图如图16-1所示:

图16-1:责任链模式模式通用类图

在这里插入图片描述

责任链模式的核心在“链”上,“链”是由多个处理者ConcreteHandler组成的,包含如下几个角色:

● Handler(抽象处理者):

 抽象处理者实现三个职责:

  • 一是定义一个请求的处理方法handleMessage,唯一对外开放的方法;
  • 二是定义一个链的编排方法setNext,设置下一个处理者;
  • 三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo。
public abstract class Handler {
	private Handler nextHandler; // 每个处理者都必须对请求做出处理

	public final Response handleMessage(Request request) {
		Response response = null;
		// 判断是否是自己的处理级别
		if (this.getHandlerLevel().equals(request.getRequestLevel())) {
			response = this.echo(request);
		} else { // 不属于自己的处理级别
					// 判断是否有下一个处理者
			if (this.nextHandler != null) {
				response = this.nextHandler.handleMessage(request);
			} else {
				// 没有适当的处理者,业务自行处理
			}
		}
		return response;
	}

	// 设置下一个处理者是谁
	public void setNext(Handler _handler) {
		this.nextHandler = _handler;
	}

	// 每个处理者都有一个处理级别
	protected abstract Level getHandlerLevel();

	// 每个处理者都必须实现处理任务
	protected abstract Response echo(Request request);
}

在处理者中涉及三个类:Level类负责定义请求和处理级别,Request类负责封装请 求,Response负责封装链中返回的结果,该三个类都需要根据业务产生,可以在实际应用中完成相关的业务填充:

public class Level {
	//定义一个请求和处理等级
}

public class Request {
	//请求的等级 
	public Level getRequestLevel(){ return null; }
}

public class Response {
	//处理者返回的数据
}

● ConcreteHandler(具体处理者):

public class ConcreteHandler1 extends Handler{

	/**
	 * 设置自己的处理级别
	 */
	@Override
	protected Level getHandlerLevel() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 定义自己的处理逻辑
	 */
	@Override
	protected Response echo(Request request) {
		// TODO Auto-generated method stub
		return null;
	}

}

定义了三个处理者,代码都是一样,此处不重复贴出。

● Client(客户端):在客户端或高层模块中对链进行组装,并传递请求,返回结果。

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//声明所有的处理节点
		Handler handler1 = new ConcreteHandler1(); 
		Handler handler2 = new ConcreteHandler2(); 
		Handler handler3 = new ConcreteHandler3(); 
		//设置链中的阶段顺序1-->2-->3 
		handler1.setNext(handler2); 
		handler2.setNext(handler3); 
		//提交请求,返回结果 
		Response response = handler1.handleMessage(new Request());
	}

}

在实际应用中,一般会有一个封装类对责任模式进行封装,也就是替代Client类,直接返回链中的第一个处理者,具体链的设置不需要高层次模块关系,这样,更简化了高层次模 块的调用,减少模块间的耦合,提高系统的灵活性。


为什么要用责任链模式

这里有一个业务场景,在万恶的封建社会,女子讲究“三从四德”,“三从”是指“未嫁从父、既嫁从夫、夫死从子”。


不使用责任链模式

以批判的态度去分析这个业务场景,类图如下:

图16-2:腐朽的“三从”类图

在这里插入图片描述
IHandler是三个有决策权对象的接口,IWomen是女性的代码。下面是实现:

  • IWomen(妇女接口):
public interface IWomen {
	//获得个人状况 
	public int getType(); 
	//获得个人请示 
	public String getRequest();
}
  • Women(妇女实现类):
public class Women implements IWomen{
	/** 通过一个int类型的参数来描述妇女的个人状况 * 1--未出嫁 * 2--出嫁 * 3--夫死 */
	private int type = 0;
	// 妇女的请示
	private String request = "";

	// 构造函数传递过来请求
	public Women(int _type, String _request) {
		this.type = _type;
		this.request = _request;
	}

	// 获得自己的状况
	public int getType() {
		return this.type;
	}

	// 获得妇女的请求
	public String getRequest() {
		return this.request;
	}
}
  • IHandler(有处理权的人员接口):
public interface IHandler {
	//一个女性(女儿、妻子或者母亲)要求逛街,你要处理这个请求 
	public void HandleMessage(IWomen women);
}
  • 实现类:有处理权的人对妇女的请求进行处理,分别有三个实现类,在女儿没有出嫁之前父亲是有决定权的;在女性出嫁后,丈夫有决定权;在女性丧偶后,对母亲提出的请求儿子有决定权;
public class Father implements IHandler {
	// 未出嫁的女儿来请示父亲
	public void HandleMessage(IWomen women) {
		System.out.println("女儿的请示是:" + women.getRequest());
		System.out.println("父亲的答复是:同意");
	}
}

public class Husband implements IHandler {
	// 妻子向丈夫请示
	public void HandleMessage(IWomen women) {
		System.out.println("妻子的请示是:" + women.getRequest());
		System.out.println("丈夫的答复是:同意");
	}
}

public class Son implements IHandler {
	// 母亲向儿子请示
	public void HandleMessage(IWomen women) {
		System.out.println("母亲的请示是:" + women.getRequest());
		System.out.println("儿子的答复是:同意");
	}
}
  • 场景类:
public class Client {

	public static void main(String[] args) {
		// 随机挑选几个女性
		Random rand = new Random();
		ArrayList<IWomen> arrayList = new ArrayList();
		for (int i = 0; i < 5; i++) {
			arrayList.add(new Women(rand.nextInt(4), "看戏"));
		}
		// 定义三个请示对象
		IHandler father = new Father();
		IHandler husband = new Husband();
		IHandler son = new Son();
		for (IWomen women : arrayList) {
			if (women.getType() == 1) {
				// 未结婚少女,请示父亲
				System.out.println("\n--------女儿向父亲请示-------");
				father.HandleMessage(women);
			} else if (women.getType() == 2) {
				// 已婚少妇,请示丈夫
				System.out.println("\n--------妻子向丈夫请示-------");
				husband.HandleMessage(women);
			} else if (women.getType() == 3) {
				// 母亲请示儿子
				System.out.println("\n--------母亲向儿子请示-------");
				son.HandleMessage(women);
			} else {
				// 暂时什么也不做
			}
		}
	}

}

首先是通过随机方法产生了5个古代妇女的对象,然后看她们是如何就看戏这件事去请示的,运行结果如下:

在这里插入图片描述

OK,业务已经实现了,但是明显这个业务实现是存在问题的。

● 职责界定不清晰
对女儿提出的请示,应该在父亲类中做出决定,父亲有责任、有义务处理女儿的请示, 因此Father类应该是知道女儿的请求自己处理,而不是在Client类中进行组装出来,也就是说原本应该是父亲这个类做的事情抛给了其他类进行处理。

● 代码臃肿
我们在Client类中写了if…else的判断条件,而且能随着能处理该类型的请示人员越多,if…else的判断就越多。

● 耦合过重
这是什么意思呢,我们要根据Women的type来决定使用IHandler的那个实现类来处理请 求。有一个问题是:如果IHandler的实现类继续扩展怎么办?如果修改Client类,那就违背了开闭原则。

● 异常情况欠考虑
妻子只能向丈夫请示吗?如果妻子(比如一个现代女性穿越到古代了,不懂什么“三从四德”)向自己的父亲请示了,父亲应该做何处理?我们的程序上可没有体现出来,逻辑失败了!


引入责任链模式

针对上面的问题,这时候就该考虑引入责任链模式了。

可以抽象成这样一个结构,女性的请求先发送到父亲类,父亲类一看是自己要处理的,就 作出回应处理,如果女儿已经出嫁了,那就要把这个请求转发到女婿来处理,那女婿一旦去天国报道了,那就由儿子来处理这个请求,类似于如图16-3所示的顺序处理图:

图16-3:女性请示的顺序处理图

在这里插入图片描述

父亲、丈夫、儿子每个节点有两个选择:要么承担责任,做出回应;要么把请求转发到后序环节。

根据责任链模式,修改后的类图如下:

图16-4:使用责任链模式后的女子“三从”类图

在这里插入图片描述

实现如下:

  • 改造后的Handler类:
public abstract class Handler {
	public final static int FATHER_LEVEL_REQUEST = 1;
	public final static int HUSBAND_LEVEL_REQUEST = 2;
	public final static int SON_LEVEL_REQUEST = 3;
	// 能处理的级别
	private int level = 0;
	// 责任传递,下一个人责任人是谁
	private Handler nextHandler;

	// 每个类都要说明一下自己能处理哪些请求
	public Handler(int _level) {
		this.level = _level;
	}

	// 一个女性(女儿、妻子或者是母亲)要求逛街,你要处理这个请求
	public final void HandleMessage(IWomen women) {
		if (women.getType() == this.level) {
			this.response(women);
		} else if (this.nextHandler != null) {
			// 有后续环节,才把请求往后递送
			this.nextHandler.HandleMessage(women);
		} else {
			// 已经没有后续处理人了,不用处理了
			System.out.println("---没地方请示了,按不同意处理---\n");
		}
	}

	/** 如果不属于你处理的请求,你应该让她找下一个环节的人,如女儿出嫁了, * 还向父亲请示是否可以逛街,那父亲就应该告诉女儿,应该找丈夫请示 */
	public void setNext(Handler _handler) {
		this.nextHandler = _handler;
	}

//有请示那当然要回应
	protected abstract void response(IWomen women);
}

在这里也用到模板方法模式,在模板方法中判断请求的级别和当前能够处理的级别,如果相同则调用基本方法,做出 反馈;如果不相等,则传递到下一个环节,由下一环节做出回应,如果已经达到环节结尾, 则直接做不同意处理。基本方法response需要各个实现类实现,每个实现类只要实现两个职 责:一是定义自己能够处理的等级级别;二是对请求做出回应。

三个实现类如下:三个实现类分别处理不同等级的请求

  • 父亲类:
public class Father extends Handler {
	// 父亲只处理女儿的请求
	public Father() {
		super(Handler.FATHER_LEVEL_REQUEST);
	}

	// 父亲的答复
	protected void response(IWomen women) {
		System.out.println("--------女儿向父亲请示-------");
		System.out.println(women.getRequest());
		System.out.println("父亲的答复是:同意\n");
	}
}
  • 丈夫类:
public class Husband extends Handler {
	// 丈夫只处理妻子的请求
	public Husband() {
		super(Handler.HUSBAND_LEVEL_REQUEST);
	}

//丈夫请示的答复 
	protected void response(IWomen women) {
		System.out.println("--------妻子向丈夫请示-------");
		System.out.println(women.getRequest());
		System.out.println("丈夫的答复是:同意\n");
	}
}
  • 儿子类:
public class Son extends Handler {
	// 儿子只处理母亲的请求
	public Son() {
		super(Handler.SON_LEVEL_REQUEST);
	}

	// 儿子的答复
	protected void response(IWomen women) {
		System.out.println("--------母亲向儿子请示-------");
		System.out.println(women.getRequest());
		System.out.println("儿子的答复是:同意\n");
	}
}
  • Women类的接口没有任何变化

  • 实现类稍微有些变化:为了展示结果清晰一点,输出请求的来源

public class Women implements IWomen {
	/**
	 * 通过一个int类型的参数来描述妇女的个人状况 1--未出嫁 * 2--出嫁 * 3--夫死
	 */
	private int type = 0;
	// 妇女的请示
	private String request = "";

	// 构造函数传递过来请求
	public Women(int _type, String _request) {
		this.type = _type; // 为了便于显示,在这里做了点处理
		switch (this.type) {
		case 1:
			this.request = "女儿的请求是:" + _request;
			break;
		case 2:
			this.request = "妻子的请求是:" + _request;
			break;
		case 3:
			this.request = "母亲的请求是:" + _request;
		}
	}

	// 获得自己的状况
	public int getType() {
		return this.type;
	}

	// 获得妇女的请求
	public String getRequest() {
		return this.request;
	}
}

  • 场景类:
public class Client {
	public static void main(String[] args) {
		// 随机挑选几个女性
		Random rand = new Random();
		ArrayList<IWomen> arrayList = new ArrayList();
		for (int i = 0; i < 5; i++) {
			arrayList.add(new Women(rand.nextInt(4), "我要看戏"));
		}
		// 定义三个请示对象
		Handler father = new Father();
		Handler husband = new Husband();
		Handler son = new Son();
		// 设置请示顺序
		father.setNext(husband);
		husband.setNext(son);
		for (IWomen women : arrayList) {
			father.HandleMessage(women);
		}
	}
}

在Client中设置请求的传递顺序,先向父亲请示,不是父亲应该解决的问题,则由父亲 传递到丈夫类解决,若不是丈夫类解决的问题则传递到儿子类解决,最终的结果必然有一个 返回,其运行结果如下所示:

在这里插入图片描述

业务调用类Client不用再去做判断到底是需要谁去处理,而且Handler抽象类的子类可以继续增加下去,只需要扩展传递链而已,调用类可以不用了解变化过程,甚至是谁在处理这个请求都不用知道。


责任链模式优缺点

责任链模式优点

  • 责任链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会 被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构, 由客户端负责链的创建,降低了系统的耦合度。
  • 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的 引用,可简化对象的相互连接。
  • 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动 态的增加或修改来增加或改变处理一个请求的职责。
  • 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新 建链即可,从这一点来看是符合“开闭原则”的。

责任链模式缺点

  • 由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链 的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
  • 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响, 而且在进行代码调试时不太方便。
  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。

责任链模式应用场景

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

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只 需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中 处理者之间的先后次序。


⇐⇐ 设计模式—— 十五 :命令模式

⇒⇒ 设计模式—— 十七:装饰器模式



参考:

【1】:《设计模式之禅》
【2】:《design-pattern-java》
【3】:《研磨设计模式》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
针对23种设计模式,分别写了demo并画了类图帮助理解。 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 其实还有两类:并发型模式和线程池模式。 二、设计模式的六大原则 1、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。 2、里氏代换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle) 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。 5、迪米特法则(最少知道原则)(Demeter Principle) 为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle) 原则是尽量使用合成/聚合的方式,而不是使用继承。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三分恶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值