设计模式实践-外观模式
什么是外观模式?什么时候用?
外观模式,也称为门面模式。其实日常生活中,有很多外观模式的影子,例如我们日常工作中,领导层,决定要开发一个直播模块,领导层给中层(部门老大)发出命令,中层则分配到特定的执行层(我们开发)。领导层无需知道下面的人是怎么分配的,谁和谁配合,只要发出命令,验收结果即可。
场景
-
例如刚完成的直播间模块,用户角色的改变,UI界面上很多部件的状态、样式、点击事件等都需要切换,这篇文章就不一一列举了,不是本文重点,避免跑题。
-
直播间总共有以下角色:
- 普通用户
- 上座用户
- 房管
- 老师(主播)
- 禁言用户
- 游客
- 可以看到直播间的角色是非常多的,也不排除以后会增加其他角色,例如其他pk、助场老师等。这就涉及到了角色切换,切换的地方又不止一处,有的是由接口调用回调时切换,有的是IM触发而切换等等...那么需要切换的组件大于3个以上,每处都需要写相同的代码,而后续再加组件也需要切换时,就需要每处都要修改,这时就可以使用外观模式来统一分发切换,外部调用方无需知道哪些组件需要进行切换,只需要做要发起切换的动作行为即可。
怎么实现外观模式?
- Facade,门面接口,对外提供的Api接口。
- 子系统接口,每个子系统都需要实现。
外观模式实践,统一处理直播间角色改变
如果正常逻辑去编写我们的代码,就会有非常多的if-else,所以每个UI组件我都用状态模式封装了一遍,外部只需要调用切换角色API即可,无需关注内部是怎么切换的,是显示、隐藏还是Remove、Add。而角色改变的时机并不是只有一处,那么调用各个组件的切换方法就会到处都是,为了解决这种情况我们使用外观模式,统一调用外观,外观再分发给每个需要角色切换的组件。
实现步骤
-
IRoleSwitcher接口,角色切换接口,定义了每种角色的切换方法,这里由于我们的门面和其他组件的操作是一样的,所以合并给一个接口。
-
Component接口,子系统接口,定义通用组件方法,每个子系统都会自己有一个接口继承它,并提供子系统的特定Api。
-
IBottomBarComponent,底部栏组件接口,实现了Component接口和IRoleSwitcher接口。
-
IApplyForSeatComponent,上座按钮组件接口,实现了Component接口和IRoleSwitcher接口。
-
IToolBoxComponent,辅助工具栏组件接口,实现了Component接口和IRoleSwitcher接口。
-
IRoleComponent,角色组件,就是我们外观,Facade,负责分发切换事件给上面的3个组件,由于我们门面Api和子系统Api是一致的,所以本身也实现了IRoleSwitcher操作接口。同样实现了Component作为组件使用。(非必须,只是我们的场景比较简单,基本就是分发切换事件,而其他需要不同子系统配合的场景,门面Api一般和子系统是不一样的)
别废话,上代码~
- Component组件接口和IRoleSwitcher接口。
public interface Component {
}
public interface IRoleSwitcher {
/**
* 设置为普通用户角色
*/
void setNormalUserRole();
/**
* 设置为管理员角色
*/
void setManagerRole();
/**
* 设置为上座用户角色
*/
void setSeatUpRole();
/**
* 设置为老师角色
*/
void setTeacherRole();
/**
* 设置为游客角色
*/
void setVisitorRole();
/**
* 设置为游客禁言角色
*/
void setForbidSpeakRole();
}
- IBottomBarComponent,底部栏组件接口和实现类
public interface IBottomBarComponent extends Component, IRoleSwitcher {
//...其他Api方法,不是重点
}
public class BottomBarComponent extends BaseRoomComponent implements IBottomBarComponent {
@Override
public void setNormalUserRole() {
//底部栏切换到普通用户角色样式
...
}
@Override
public void setManagerRole() {
//底部栏切换到普通用户角色样式
...
}
@Override
public void setSeatUpRole() {
//底部栏切换到上座用户角色样式
...
}
@Override
public void setTeacherRole() {
//底部栏切换到老师用户角色样式
...
}
@Override
public void setVisitorRole() {
//底部栏切换到游客角色样式
...
}
@Override
public void setForbidSpeakRole() {
//底部栏切换到禁言用户角色样式
...
}
}
- IApplyForSeatComponent,上座按钮组件
public interface IApplyForSeatComponent extends Component, IRoleSwitcher {
//...省略其他Api
}
public class ApplyForSeatComponent extends BaseRoomComponent implements IApplyForSeatComponent {
//...实现方法和BottomBarComponent是一致的,所以不再列举
}
- IToolBoxComponent,辅助工具栏组件
public interface IToolBoxComponent extends Component, IRoleSwitcher {
//...省略其他Api
}
public class ToolBoxComponent extends BaseRoomComponent implements IToolBoxComponent {
//...实现方法和BottomBarComponent是一致的,所以不再列举
}
- IRoleComponent,角色组件,中介类
public class RoleComponent extends BaseRoomComponent implements IRoleComponent {
@Override
public void setNormalUserRole() {
getComponentProvider().getBottomBarComponent().setNormalUserRole();
getComponentProvider().getApplyForSeatComponent().setNormalUserRole();
getComponentProvider().getToolBoxComponent().setNormalUserRole();
}
@Override
public void setManagerRole() {
getComponentProvider().getBottomBarComponent().setManagerRole();
getComponentProvider().getApplyForSeatComponent().setManagerRole();
getComponentProvider().getToolBoxComponent().setManagerRole();
}
@Override
public void setSeatUpRole() {
getComponentProvider().getBottomBarComponent().setSeatUpRole();
getComponentProvider().getApplyForSeatComponent().setSeatUpRole();
getComponentProvider().getToolBoxComponent().setSeatUpRole();
}
@Override
public void setTeacherRole() {
getComponentProvider().getBottomBarComponent().setTeacherRole();
getComponentProvider().getApplyForSeatComponent().setTeacherRole();
getComponentProvider().getToolBoxComponent().setTeacherRole();
}
@Override
public void setVisitorRole() {
getComponentProvider().getBottomBarComponent().setVisitorRole();
getComponentProvider().getApplyForSeatComponent().setVisitorRole();
getComponentProvider().getToolBoxComponent().setVisitorRole();
}
@Override
public void setForbidSpeakRole() {
getComponentProvider().getBottomBarComponent().setForbidSpeakRole();
getComponentProvider().getApplyForSeatComponent().setForbidSpeakRole();
getComponentProvider().getToolBoxComponent().setForbidSpeakRole();
}
}
- 切换调用处调用
mDataStore = getDataStore();
//监听房间信息改变切换角色。
mDataStore.getLiveRoomViewModel().getRoomInformationLiveData().observe(getLifecycleOwner(), new Observer<RoomInformationModel>() {
@Override
public void onChanged(@Nullable RoomInformationModel roomInformationModel) {
if (roomInformationModel == null) {
return;
}
IMRoomInfoModel roomInfoModel = roomInformationModel.getRoomInfo();
IMSendUserModel currentUser = mDataStore.getLiveRoomViewModel().getCurrentUserLiveData().getValue();
if (currentUser == null) {
return;
}
//这里简单期间,省略了决定角色的生成,决定后通知其他组件切换样式
IRoleComponent roleComponent = getComponentProvider().getRoleComponent();
//老师角色
if (currentUser.isTeacher()) {
roleComponent.setTeacherRole();
} else if (currentUser.isNormalUser()) {
//普通用户角色
roleComponent.setNormalUserRole();
} else if (currentUser.isSeatUpUser()) {
//上座用户角色
roleComponent.setSeatUpRole();
} else if (currentUser.VisitorRole()) {
//游客角色
roleComponent.setVisitorRole();
} else if (currentUser.isForbidSpeakUser()) {
//禁言用户角色
roleComponent.setForbidSpeakRole();
}
}
});
总结
-
外观模式的优点:提供统一入口方法给外部,隐藏内部子系统的细节。
-
外观模式的缺点,外观类没有遵循开闭原则,当我们的业务变更时,可能就需要修改外观类,增加新的逻辑和子系统类。