设计模式实践-组合模式
什么是组合模式?什么时候用?
组合模式,也称为部分整理模式。该模式将一组相似的对象,当作一个对象处理,并将对象组合成树形结构,提供统一获取方法,以此来忽略对象和对象间的差别。简单来讲,就是将对象组合为树形结构来处理外部的调用,内部进行递归分发,最后获取结果返回(是不是很像ViewGrop和View的关系,和事件处理机制,没错他们就是用了组合模式!)。
怎么实现组合模式?
组合模式涉及以下对象:
-
Component:抽象的节点,可以是接口,也可以是抽象类,是节点的基类。
-
Composite,存储子节点。
-
Leaf,没有子节点的叶子节点。
组合模式,分发onBackPressed
安卓中,系统给我提供了onBackPressed()来处理返回键,我们一般在该回调里面去拦截返回键,例如比较常用的双击退出应用。在做直播模块的时候,遇到一个问题,界面上有几个自定义View是悬浮的,当按下返回键时需要他们去隐藏,所以只能在onBackPressed()中将所有需要的View在这里判断,如果没有隐藏,先隐藏,然后拦截,最后都隐藏完了,再调用super.onBackPressed()进行finish掉Activity。
- 类似代码是这样的
@Override
public void onBackPressed() {
//公告正在显示,必须先关闭,拦截
if (vNoticeFlowView != null && vNoticeFlowView.isShow()) {
vNoticeFlowView.hide();
return;
}
//工具箱正在显示,必须先关闭,拦截
if (vToolBoxDrawer != null && vToolBoxDrawer.isOpen()) {
vToolBoxDrawer.close();
return;
}
//分享布局正在显示,必须先关闭,拦截
if (isShowShareLayout) {
toggleShareLayout(false);
return;
}
super.onBackPressed();
}
-
嗯,看起来没毛病呀,现在的确是没毛病,但是如果一多...后续界面越来越复杂,这里就会变得很臃肿。而且也不利于分层,说到分层,有个类我们经常用,平常我们也会用它来分层,没错,就是Fragment。但是谷歌是不建议一个界面太多Fragment的,而且还由于Fragment有各种奇奇怪怪的bug,例如重叠,点击穿透,回退栈异常等等...(越说越可怕了)
-
曾经做过一个应用,界面上的自定义View非常多,他们都需要响应返回键,那么代码是这样的,13寸的电脑,一屏幕都是onBackPressed()...有代码洁癖的我,有点难受。
@Override
public void onBackPressed() {
//...
ToggleAreaMapRouteListEvent toggleRouteListEvent = EventBusUtil.getStickyEvent(ToggleAreaMapRouteListEvent.class);
if (toggleRouteListEvent != null && toggleRouteListEvent.isShow()) {
ToggleAreaMapRouteListEvent.send(false);
return;
}
ToggleAreaMapSubScenicListEvent toggleSubScenicListEvent = EventBusUtil.getStickyEvent(ToggleAreaMapSubScenicListEvent.class);
if (toggleSubScenicListEvent != null && toggleSubScenicListEvent.isShow()) {
ToggleAreaMapSubScenicListEvent.send(false);
return;
}
ToggleAreaMapVoiceTypeListEvent toggleVoiceTypeListEvent = EventBusUtil.getStickyEvent(ToggleAreaMapVoiceTypeListEvent.class);
if (toggleVoiceTypeListEvent != null && toggleVoiceTypeListEvent.isShow()) {
ToggleAreaMapVoiceTypeListEvent.send(false);
return;
}
ToggleAreaMapAreaServiceEvent toggleAreaMapAreaServiceEvent = EventBusUtil.getStickyEvent(ToggleAreaMapAreaServiceEvent.class);
if (toggleAreaMapAreaServiceEvent != null && toggleAreaMapAreaServiceEvent.isShow()) {
ToggleAreaMapAreaServiceEvent.send(false);
return;
}
if (ToggleScenicMapOfflineEvent.getEvent().isShow()) {
ToggleScenicMapOfflineEvent.getEvent().send(false, OfflineHolder.TYPE_AREA);
return;
}
if (ToggleScenicAuthEvent.getEvent().isShow()) {
ToggleScenicAuthEvent.getEvent().send(false, ScenicMapAuthHolder.AUTH_TYPE_AREA);
return;
}
//...
super.onBackPressed();
}
- 当时一直想解决这个问题,但是一直没找到解决方案,如今学习了设计模式-组合模式,就可以解决问题了。
实现步骤
- Component组件接口,叶子接口,定义组件事件入口方法和添加子组件(没错组件是可以嵌套的!),而我们的onBackPressed()是带返回值的,返回值代表是否已经被处理。
public interface Component {
/**
* 注册子组件
*/
void registerComponent(Class<?> componentClazz, Component component);
/**
* 解注册子组件
*/
void unRegisterComponent(Component component);
/**
* 获取子组件
*
* @param componentClazz 子组件的接口
*/
<T extends Component> T getComponentInterface(Class<T> componentClazz);
/**
* 返回键处理
*/
boolean onBackPressed();
}
- 一般组件还会有基类,定义通用流程逻辑和方法。子组件怎么嵌套呢,其实就是内部有一个List集合保存,在调用时,先遍历子组件调用对应的事件方法,返回值为true则已处理,那么我就不处理了,返回值为false则没有处理,那么父组件可以做处理。(好熟悉,这不就是事件分发机制中的dispatchTouchEvent()吗)
public abstract class BaseComponent implements Component, LifecycleObserver {
/**
* 子组件集合
*/
private Map<Class<?>, Component> mChildComponents = new LinkedHashMap<>();
/**
* 生命周期
*/
private LifecycleOwnerExt mLifecycleOwner;
public BaseComponent(LifecycleOwnerExt owner) {
mLifecycleOwner = owner;
owner.getLifecycle().addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
protected void onLifecycleCreate() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
protected void onLifecycleResume() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
protected void onLifecyclePause() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
protected void onLifecycleDestroy() {
}
public LifecycleOwnerExt getLifecycleOwner() {
return mLifecycleOwner;
}
public Context getContext() {
return mLifecycleOwner.getActivity();
}
public BaseLingJiActivity getActivity() {
return (BaseLingJiActivity) mLifecycleOwner.getActivity();
}
public String getString(@ArrayRes int resId, int index) {
return getActivity().getResources().getStringArray(resId)[index];
}
public String getString(@StringRes int resId) {
return getActivity().getString(resId);
}
public String getString(@StringRes int resId, Object... formatArgs) {
return getActivity().getString(resId, formatArgs);
}
@Override
public void registerComponent(Class<?> componentClazz, Component component) {
if (component == null) {
return;
}
if (mChildComponents.containsKey(componentClazz)) {
throw new IllegalArgumentException("Component已添加,请勿重复添加");
}
mChildComponents.put(componentClazz, component);
}
@Override
public void unRegisterComponent(Component component) {
Class<?> clazz = null;
for (Map.Entry<Class<?>, Component> entry : mChildComponents.entrySet()) {
if (entry.getValue() == component) {
clazz = entry.getKey();
}
}
if (clazz != null) {
mChildComponents.remove(clazz);
}
}
@Override
public <T extends Component> T getComponentInterface(Class<T> componentClazz) {
return (T) mChildComponents.get(componentClazz);
}
@CallSuper
@Override
public boolean onBackPressed() {
boolean isHandle;
for (Map.Entry<Class<?>, Component> entry : mChildComponents.entrySet()) {
isHandle = entry.getValue().onBackPressed();
if (isHandle) {
return true;
}
}
return false;
}
}
- 组件实现类,也就是树形结构中的树叶,例如公告组件,一个悬浮公告View,需要返回键进行拦截,先掉隐藏自己。
//公告组件接口,继承Component接口。
public interface INoticeComponent extends Component {
//其他无关Api...
}
public class NoticeComponent extends BaseComponent implements INoticeComponent {
@Override
public boolean onBackPressed() {
//内嵌子组件处理了,那么我就不处理了
boolean isHandle = super.onBackPressed();
if (isHandle) {
return true;
}
//公告正在显示,则先关闭
if (vNoticeFlowView != null && vNoticeFlowView.isShow()) {
vNoticeFlowView.hide();
return true;
} else {
return false;
}
}
}
- 树叶有了,那么轮到树干。RoomComponentProvider,它实现了IComponentProvider接口,IComponentProvider接口则是继承Component接口,所以其实它也是一个组件,只是它是最根层的,可以说就是RootComponent。
其实组合模式有个缺点:就是获取子组件类型只能使用通用接口类型,如果需要获取具体类型就只能单独定义获取方法,方法内部强转后返回。
//组件提供者
public interface IComponentProvider extends Component {
//定义一些其他获取组件的Api
}
public class RoomComponentProvider implements IComponentProvider {
//方法实现,其他的和普通的组件没有区别
}
- 有了树干和树叶,还需要事件源头进行事件分发,即为我们的Activity。
public abstract class AudioLiveRoomActivity extends AppCompatActivity {
//组件提供者
private RoomComponentProvider mComponentProvider;
@Override
public void onCreate() {
//...省略其他常规设置
mComponentProvider = new RoomComponentProvider();
}
@Override
public void onBackPressed() {
if (mComponentProvider != null) {
//组件树中组件所有都没有处理,则调用Activity父类处理
if (!mComponentProvider.onBackPressed()) {
super.onBackPressedSupport();
}
} else {
super.onBackPressedSupport();
}
}
}
分发onActivityResult
如果onBackPressed的分发还不够,那就来处理onActivityResult吧!Activity间跳转自然少不了交互,例如:
-
调用Pay组件库进行支付,例如支付信息是通过onActivityResult返回。
-
调用发送图片消息,调用系统选择图片功能,图片信息通过onActivityResult返回。
-
其他我们的自定义界面,也需要使用onActivityResult返回数据。
等等,如果一个界面的startActivityForResult()非常多,onActivityResult的处理过程就非常多的if-else,那么onActivityResult()方法体就会很大,而且耦合在了Activity。
- 繁杂的代码
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PAY_REQUEST_CODE) {
//支付
} else if (requestCode == IMAGE_PICK_REQUEST_CODE) {
//选择图片
} else if (requestCode == XXX_REQUEST_CODE) {
//自定义
}
//...只要够复杂,这里就会满屏幕都是if-else
}
- 那么使用组合模式是怎么拆分呢?
分发套路
其实有了上文的分发onBackPressed,那么分发onActivityResult就是照葫芦画瓢了。
-
在Component接口中添加onActivityResult方法。
-
BaseComponent组件基类中,复写onActivityResult方法,遍历内部组件进行分发。
-
需要处理onActivityResult的子类组件,复写onActivityResult方法进行处理。
没错,只要这3步,即可分发事件,后续再需要分发事件,则也是按照以上3步即可!
实现步骤
- Component提供onActivityResult
public interface Component {
//省略其他方法...
/**
* onActivityResult()处理,返回true为已处理,false为未处理
*/
boolean onActivityResult(int requestCode, int resultCode, Intent data);
//省略其他方法...
}
- BaseComponent中分发给子组件
public abstract class BaseComponent implements Component {
@CallSuper
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
boolean isHandle;
for (Map.Entry<String, Component> entry : mChildComponents.entrySet()) {
isHandle = entry.getValue().onActivityResult(requestCode, resultCode, data);
if (isHandle) {
return true;
}
}
return false;
}
}
- 例如支付组件,需要接收支付结果
public class PayComponent extends BaseComponent implements IPayComponent {
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
//先调用子组件处理,没有处理则自己处理
boolean isHandle = super.onActivityResult(requestCode, resultCode, data);
if (isHandle) {
return true;
}
//...拿取支付信息
}
}
总结
组合模式的优缺点
优点:
-
可以清晰的定义类的层次结构,对外接收处理时,外部并不需要关心内部的复杂情况,外部并不清楚接收方是单个对象还是一组有结构的对象。
-
添加新的枝干和节点时,增加新的分支类即可。内部采用递归组合进行事件分发,可以很好将类职责分拆。
缺点:
- 获取组件只能返回基类类型,使用时需要必要的类型检查。(类似Context的getSystemService())