1.责任链模式的定义
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链。当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。简单来说就是发送者发送请求给一个处理请求的链表。
2.责任链模式的优缺点
责任链模式是一种行为型模式(在之前设计模式的专栏中其他文章有介绍,这里就不在表述),其主要优缺如下。
优点:
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if--else 语句。
- 责任分担,每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
3.责任链的结构
责任链模式的组成由以下三个组成,分别是:
- Handler(处理者):定义一个处理请求的接口,可以选择把处理请求传递到责任链的下一个处理器。
- ConcreteHandler(具体处理者):具体处理者实现了处理请求的接口。如果它能够处理该请求,就进行处理,否则将请求传递到下一个处理者。
- Client(客户端):创建责任链并向链上的处理者发送请求。
// 处理者接口
abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
// 具体处理者A
class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("A")) {
System.out.println("ConcreteHandlerA 处理了请求: " + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 具体处理者B
class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("B")) {
System.out.println("ConcreteHandlerB 处理了请求: " + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 客户端
public class Client {
public static void main(String[] args) {
// 创建处理器
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
// 构造责任链
handlerA.setNextHandler(handlerB);
// 处理请求
handlerA.handleRequest("A"); // 输出: ConcreteHandlerA 处理了请求: A
handlerA.handleRequest("B"); // 输出: ConcreteHandlerB 处理了请求: B
handlerA.handleRequest("C"); // 无输出,因为没有处理者能够处理 "C" 请求
}
}
解释:
- ConcreteHandlerA 和 ConcreteHandlerB 都是责任链上的处理者,它们根据请求的内容决定是否处理请求。
- Client 创建了一个责任链,其中 handlerA 先处理请求,如果它不能处理,就把请求传递给 handlerB。
- 通过这种方式,责任链可以处理不同类型的请求,而不需要在请求发送者和处理者之间存在强耦合。
这种模式在需要动态地决定处理请求的对象或者请求需要被多个对象处理时特别有用。
接下来,我们来实现一个具体场景,比如我们要开发一个请假流程控制系统,请假一天以下,只需要小组长同意即可,请假1-3天还需要部门经理同意,请假3-7天还需要总经理同意才行,
代码实现如下:
先定义一个请假用的假条类:
public class LeaveRequest {
//姓名
private String name;
//请假天数
private int num;
//请假天数
private String content;
public LeaveRequest(String name, int num, String content) {
this.name = name;
this.num = num;
this.content = content;
}
public String getName() {
return name;
}
public int getNum() {
return num;
}
public String getContent() {
return content;
}
}
其次,再定一个抽象处理类,这是责任链的基本构成:
public abstract class Handler {
//这里使用final static是定义常量,然后这里使用protected的原因是因为这些常量我需要在子类中能够调用它们
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;
//该领导处理的请假天数区间
private int numStart;
private int numEnd;
//领导上级还有领导
private Handler nextHandler;
//设置请假天数范围
public Handler(int numStart) {
this.numStart = numStrat;
}
//设置请假天数范围,这里使用了构造函数的重载,因为传一个参数的时候是只传入7天以上的情况
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
//设置上级领导
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
//提交请假条,这里开始改变了,注意一下,这里只是一个提交函数,里面调用了抽象处理方法,这部分代码逻辑设计有点类似于模板模式。(关注我,在我设计模式专栏中已更新!)
//看到没,方法用了final修饰,和模板模式一样,要防止子类去修改顶级逻辑骨架。
public final void submit(LeaveRequest leaveRequest) {
if (this.numStart == 0) {
return;
}
//请假天数达到领导处理要求
if(leaveRequest.getNum() >= this.numStart) {
//关键点在这,submit方法就是模板函数的相当于一个处理模板,里面给你定义好顶级逻辑骨架
this.handlerLeave(leaveRequest);
//如果还有上级 并且请假天数超过当前领导的处理范围
if(this.nextHandler != null && leaveRequest.getNum() > numEnd) {
//继续向上级领导提交
this.nextHandler.submit(leaveRequest);
} else {
System.out.printIn("流程结束!!!");
}
}
}
//各级领导处理请假条方法
protected abstract void handlerLeave(LeaveRequest leave);
}
然后,去实现具体处理类,这里有三个具体处理类,分别是小组长,经理,总经理:
小组长类:
public class GroupLeader extends Handler {
//1-3天的假
public GroupLeader() {
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
@Override
protected void handlerLeave(LeaveRequest leave) {
System.out.printIn(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!");
System.out.printIn("小组长审批通过:同意!");
}
}
经理类:
public class Manager extends Handler {
//1-3天的假
public Manager() {
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
@Override
protected void handlerLeave(LeaveRequest leave) {
System.out.printIn(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!");
System.out.printIn("经理审批通过:同意!");
}
}
总经理:
public class GeneralManager extends Handler {
//1-3天的假
public GeneralManager() {
super(Handler.NUM_SEVEN);
}
@Override
protected void handlerLeave(LeaveRequest leave) {
System.out.printIn(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "!");
System.out.printIn("总经理审批通过:同意!");
}
}
测试类:
public class Client {
public static void main(String[] args) {
//请假条
LeaveRequest leave = new LeaveRequest("小庄",3,"出去旅游");
//各位领导
Manager manager = new Manager();
GroupLeader groupLeader = new GroupLeader();
GeneralManager generalManager = new GeneralManager();
//这里就是客户端中要构造责任链
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
//提交到责任链头,开始处理
groupLeader.submit(leave);
}
}
输出结果:
小庄请假3天,出去旅游
小组长审批通过:同意!
接下来继续讲解迭代器模式!
5.迭代器的定义
迭代器模式(Iterator Pattern)是一种设计模式,它提供了一种方法来顺序访问一个聚合对象(如列表或集合)中的各个元素,而不需要暴露该对象的内部表示。迭代器模式在许多编程语言和库中都有广泛的应用,尤其是在处理集合类数据结构时。
在java中迭代器就是Iterator
Iterator
是 Java 集合框架中的一个接口,用于遍历集合中的元素。它提供了一种统一的方式来访问不同类型的集合(如 List
、Set
、Map
等)中的元素,而不需要了解集合的具体实现。
在java中有Iterator就是迭代器的重要应用,迭代器模式在设计模式中没有很大重要性!了解就好!
6.Iterator
接口的主要方法
Iterator
接口主要有以下三个方法:
-
boolean hasNext()
:- 用于检查集合中是否还有未遍历的元素。
- 如果有下一个元素可供遍历,返回
true
;否则返回false
。
-
E next()
:- 返回集合中的下一个元素,并将迭代器向前移动。
- 如果没有更多的元素可以返回,会抛出
NoSuchElementException
异常。
-
void remove()
:- 从集合中移除调用
next()
方法时返回的最后一个元素。每次调用remove()
方法必须紧跟在next()
方法之后。 - 如果在没有调用
next()
方法之前调用remove()
,或remove()
被多次调用,则会抛出IllegalStateException
异常。 - 注意:并不是所有的集合都支持
remove()
操作,如果不支持会抛出UnsupportedOperationException
异常。
- 从集合中移除调用
7.Iterator
的使用示例
下面是一个简单的例子,演示了如何使用 Iterator
接口遍历一个 ArrayList
集合:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
// 创建一个ArrayList集合
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 获取Iterator对象
Iterator<String> iterator = list.iterator();
// 使用Iterator遍历集合
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
// 可以选择在迭代过程中移除元素
if ("Banana".equals(item)) {
iterator.remove();
}
}
// 打印集合内容,验证元素是否被移除
System.out.println("After iteration: " + list);
}
}
运行结果如下:
Apple
Banana
Cherry
After iteration: [Apple, Cherry]
Iterator
与 for-each
循环
在 Java 5 之后,引入了增强的 for
循环(即 for-each
循环),它实际上是基于 Iterator
实现的。使用 for-each
循环可以更加简洁地遍历集合:
for (String item : list) {
System.out.println(item);
}
8.Iterator
与 ListIterator
Iterator
是一个通用的接口,适用于任何 Collection
。但是对于 List
,Java 提供了一个功能更强的 ListIterator
接口,它扩展了 Iterator
,允许双向遍历(即可以向前和向后遍历),并且可以在遍历过程中修改元素。
ListIterator
的主要方法包括:
boolean hasPrevious()
:是否有前一个元素。E previous()
:返回前一个元素,并将迭代器向后移动。void add(E e)
:在当前位置添加一个元素。
注意事项
-
Fail-fast 机制:大多数 Java 集合的
Iterator
实现是“快速失败”(fail-fast)的,这意味着如果在使用迭代器遍历集合的过程中,集合结构被修改(如添加、删除元素),除了通过迭代器自身的remove()
方法外,迭代器会抛出ConcurrentModificationException
异常。这是为了防止出现不一致的状态。 -
线程安全:
Iterator
本身不是线程安全的,如果需要在多线程环境中使用,可以考虑使用Collections.synchronizedCollection()
或使用并发集合类(如CopyOnWriteArrayList
)。
9.总结
Iterator
是 Java 集合框架中非常重要的一部分,提供了一种标准的方式来遍历集合中的元素。它的主要优点是隐藏了集合的内部实现,使得代码更加灵活和通用。了解和使用 Iterator
是 Java 开发中必备的技能之一。