背景
工作中遇到了这样的处理场景:一个 BaseView
类监听一个来自 ViewModel
的事件,事件是一个 enum Case
类型,因此事件的取值范围是一个有限集合:{ CASE_1, CASE_2, CASE_3 }
。
业务逻辑是:{ CASE_1, CASE_2 }
是所有继承自 BaseView
的类都需要处理的事件,因此合理的设计是将 { CASE_1, CASE_2 }
的处理逻辑写在 BaseView
中。如下所示:
class BaseView {
...
private void handleCases(Case case) {
switch (case) {
case CASE_1:
// 处理 CASE_1
break;
case CASE_2:
// 处理 CASE_2
break;
default:
handleUncaughtCase(case);
break;
}
}
protected void handleUncaughtCase(Case case) {
// 这个父类的默认处理,同时 protected 方法可以被子类重写,子类可以处理剩下的 case
}
}
class ChildView extends BaseView {
@Override
protected void handleUncaughtCase(Case case) {
switch (case) {
case CASE_3:
// 处理 CASE_3
break;
}
}
}
平行责任链模式(处理者之间是平行关系,每个处理者之间不知道对方的存在)
上面小节中的写法只是一种实例,读者也可以将它换成 if ... else ...
的写法,或者将 handleCases()
、handleUncaughtCase()
等函数抽象成一个接口,BaseView
的子类实现这些接口并且根据自己所关心的 case
做自己的处理。
但是,不论怎么样的变体写法,都无法避免这样的缺陷:父类已经处理过的 case
,子类有机会再次处理。从设计模式上来讲,ChildView
的开发人员完全有可能不知道 { CASE_1, CASE_2 }
已经被父类处理过了,从而在 ChildView
中又用另外的逻辑处理了一遍 { CASE_1, CASE_2 }
。初级程序员一个经常犯的错误就是认为自己代码逻辑中的任何假设前提逻辑,其他开发人员都会清楚地知晓。因此我们一定要从设计模式上约束子类不会再有机会处理 { CASE_1, CASE_2 }
。
下面开始对上面的处理逻辑进行一个责任链模式的改造。其实上面的每一个 case
的处理都是一个输入,没有输出。我们可以给处理逻辑加上一个 Boolean
的输出,表示该 case
是否已经被处理。如果已经被处理,则不再将 case
传给链中的下一个处理逻辑。
因此,case
的处理逻辑是一个典型的 Function<Case, Boolean>
。在父类中持有 Function<Case, Boolean>
列表,这就是责任链列表。父类从 ViewModel
中接收 case
事件,然后遍历责任链列表中的每一个 Function
,当有一个 Function
返回 true
时,则停止遍历。这样就避免了上面传统写法中,子类可能重复处理的问题。
class BaseView {
protected List<Function<Case, Boolean>> handlerList = new ArrayList<>();
private void handleCases(Case case) {
boolean handled = false;
for (Function<Case, Boolean> handler : handlerList) {
if (handler.apply(case)) { // 一旦 case 被处理过,责任链就中止
handled = true;
break;
}
}
if (!handled) {
catchUnhandledCase(case);
}
}
protected void addHandler() {
handlerList.add(case -> {
if (case == CASE_1) {
// 处理 CASE_1
return true;
} else if (case == CASE_2) {
// 处理 CASE_2
return true;
} else {
return false;
}
})
}
}
class ChildView extends BaseView {
override fun addHandler() {
super.addOperatorHandler()
handlerList.add(Function { case ->
when (case) {
CASE_3 -> {
// 处理 CASE_3
return@Function true
}
else -> return@Function false
}
})
}
}
总结
上面的责任链模式有这样的特点:
- 处理者相互之间不知道对方的存在,处理的优先级由分发者(持有
handlerList
的父类)决定。当每个处理者需要处理的事件是互斥的时候,处理者之间其实不存在优先级。但是当每个处理者需要处理的事件有交集时,先收到事件的处理者可以选择中断责任链的调用,也可以选择不中断责任链调用,让该事件继续被下游的处理者处理。 - 适合继承的类结构。子类所关心的事件集合是父类的拓展(因为继承关系,因此子类也具有父类的能力)。
优先级的责任链
同样是来自工作中的实例:由于我们的 App 是一个国际化的 App,因此很多策略是需要考虑到全球不同地区的策略的。比如地图,国内使用高德地图,而国外使用谷歌地图,但是日本地区使用 HERE 地图。
这里对业务的抽象就是,对于地区这个事件,有 3 个处理者关心,且这 3 个处理者所关心的具体值是互斥关系,因此不能用上面的平行责任链,而需要使用本节的优先级责任链模式。即一个处理者如果不能成功处理,则会主动调用下一个处理者,直到该事件被处理为止。
我们先定义一个 MapProvider
的抽象类,它主要提供 3 个方法:
- 业务方法,根据地区类型事件提供具体的地图实例
- 事件在责任链上分发的方法
- 设置下一个处理者
MapProvider
的方法
public abstract class MapProvider {
protected MapProvider nextProvider;
// 1. 业务方法,根据地区类型事件提供具体的地图实例
abstract InternalMapView requestMapView(AreaCode code);
// 2. 事件在责任链上分发的方法
public InternalMapView dispatchMapViewRequest(AreaCode code) {
InternalMapView mapView = requestMapView(code);
if (mapView != null) {
return mapView;
} else {
if (nextProvider != null) {
mapView = nextProvider.dispatchMapView(code);
}
}
return mapView;
}
// 3. 设置下一个处理者 MapProvider
public MapProvider next(MapProvider nextProvider) {
this.nextProvider = nextProvider;
return this;
}
}
然后有 3 个具体的 MapProvider
类:AMapMapProvider
, GoogleMapProvider
, HereMapProvider
。在一个管理它们处理顺序的类里,实例化它们,并设置成链。如下所示:
class MapView {
public InternalMapView initialise(AreaCode code) {
MapProvider head = new AMapProvider()
.setNext(new GoogleMapProvider())
.setNext(new HereMapProvider());
head.dispatchMapViewRequest(code);
}
}
上面的代码已经非常易懂了。相比于第一种写法,每个处理者需要显式地设置下一个处理者,这种设置的优先级决定了事件处理的优先级。本质上和第一种写法没有区别,只是第一种写法天然地有类上面的继承关系,导致它更适合持有 List
的平行责任链模式。
网友评论