Part 1 精讲设计模式
1.9 门面模式和状态模式
田超凡
2019年11月5日
转载请注明原作者
1 门面模式(Facade)
门面模式,又叫外观模式或包装模式(Facade),是一种结构型设计模式。使用门面模式隐藏了各个子系统的复杂性,并向客户端提供了一个可以统一访问多个子系统的接口,这个接口使得子系统更容易被访问或者使用,实现客户端调用和子系统具体实现逻辑之间彻底解耦,可以理解为是对不同子系统业务逻辑代码的细粒度拆分,然后使用统一的一个接口进行组装和调用,客户端直接调用这个封装好的接口即可实现一次调用全部执行,使得客户端调用更加灵活,同时提高代码可读性,既保证了单一职责原则(每个子系统不同业务方法职责单一),也满足了开闭原则(对扩展开放,对修改关闭,不会因为代码扩展而影响已经存在的既定代码,扩展性强)。
2 门面模式实现原理
场景引入:现在需要实现支付接口回调方面的业务,要求在接收到支付接口回调参数之后,写入操作日志、修改订单状态、调用积分子系统新增积分、调用SMS子系统发送短信。要求每一个不同业务操作完全分离开来,对外只暴露一个支付回调接口方法供客户端调用。
这种场景由于是需要在一个操作方法中执行大量不同业务类型的操作,我们可以把每一个不同的业务操作封装为单独的接口和实现类并注入到Spring容器中,然后定义一个支付回调接口基于Spring DI依赖注入进行动态组装业务操作类(当业务量逐步增大时,每一个调用业务逻辑都会成为单独的子系统,这样也有利于系统拆分),最终在一个支付回调方法中统一组装调用。
这种做法实际上是基于门面模式实现的,简单来说,门面模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。门面模式实现原理图如下,主要包含3类角色。
客户角色(Custormer):通过调用门面角色Facede来完成要实现的功能。
门面角色(Facade):外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
子系统角色(Child System):实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
3 门面模式特点
优点:
1.客户端调用和子系统彻底解耦,简单易用。门面模式使用门面角色(Facade)作为客户端和子系统沟通的“桥梁”,门面角色组装所有客户端发起一次请求需要调用的所有子系统方法,客户端直接调用门面角色中的方法即可执行子系统的方法,对客户端而言,调用更加方便快捷,客户端不关心子系统内部业务逻辑具体实现和如何组装的,客户端只调用门面角色;门面角色接收到客户端的调用信号后执行动态组装好的子系统调用逻辑,调用的所有子系统的业务代码执行完毕之后,门面角色把执行结果直接返回给客户端调用者,子系统的具体实现对于客户端而言是完全不可见的,这种实现机制在每个子系统业务逻辑逐渐变得比较复杂时,代码的可读性和可维护性都非常强。
2.层次划分清晰。当涉及到在同一个业务操作中需要进行大量不同业务、不同环境(本地或者第三方)下的子系统服务调用时,层次划分清晰,调用每个子系统的操作都可以显而易见的知道调用原因,代码可读性强,隐藏了子系统内部实现细节。
4 门面模式代码实现
需要重构代码
|
@Slf4j
|
创建业务逻辑封装
|
@Component
@Slf4j @Component
@Component
|
创建门面接口
|
@Component
|
5 状态模式(State)
状态模式允许一个对象在其内部状态改变的时候改变其行为,这个对象看上去就像是改变了它的类一样,对象的状态不同,所要执行的行为也截然不同。状态模式可以用来替代不同分支需要执行不同行为操作的多重if判断结构(不同分支执行的操作依赖于对象的状态,不同状态需要实现的行为完全不同),提高代码可扩展性和程序执行效率。
6 状态模式实现原理
1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。 通常,有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
7 状态模式代码实现
public interface OrderState {
public void orderService();
}
@Slf4j
@Component
public class AlreadySignedOrderState implements OrderState {
@Override
public void orderService() {
log.info(">>>切换为已经签收状态..");
}
}
@Slf4j
@Component
public class InTransitOrderState implements OrderState {
@Override
public void orderService() {
log.info(">>>切换为正在运送状态...");
}
}
@Slf4j
@Component
public class ShippedAlreadyOrderState implements OrderState {
public void orderService() {
log.info(">>>切换为已经发货状态..");
}
}
public class Test {
public static void main(String[] args) {
ContextState contextState = new ContextState(new AlreadySignedOrderState());
contextState.switchStateOrder();
}
}
8 状态模式和策略模式对比
1、状态模式重点在各状态之间的切换从而做不同的事情,而策略模式更侧重于根据具体情况选择策略,并不涉及切换。(执行的是相同的操作行为,只是实现策略不同)
2、状态模式不同状态下做的事情不同,而策略模式做的都是同一件事,例如聚合支付平台,有支付宝、微信支付、银联支付,虽然策略不同,但最终做的事情都是支付,也就是说他们之间是可替换的。反观状态模式,各个状态的同一方法做的是不同的事,不能互相替换。
状态模式封装了对象的状态,而策略模式封装算法或策略。因为状态是跟对象密切相关的,它不能被重用;而通过从Context中分离出策略或算法,我们可以重用它们。
在状态模式中,每个状态通过持有Context的引用,来实现状态转移;但是每个策略都不持有Context的引用,它们只是被Context使用。状态模式的实现核心永远针对的是对象的状态,不同状态执行的行为完全不同,做的是完全不同的事。策略模式的实现核心永远针对的是同一个行为的不同实现策略,同一个行为的实现策略不同,但是本质都是做同一件事。
补充说明:
- 门面模式在Spring IOC/DI中运用的非常普遍,尤其是不同Bean的实现行为聚集在同一个方法中进行调用的情况下,都采用的是门面模式。门面模式的核心就是,客户端调用和子系统具体实现彻底隔离开来,提供门面角色接口来对每次调用需要使用到的子系统组件进行统一组装和调用,对外只暴露一个包装好的执行方法供客户端调用,实际运用中在使用门面模式的时候,尽可能在调用的地方统一平行调用,在定义的地方各自扩展,互不影响(在门面角色中只做调用,具体的业务逻辑实现都在不同的子系统中封装成方法,门面角色只提供动态组装),尽可能满足开闭原则(对扩展开放,对修改关闭),实现高内聚低耦合,提高代码可扩展性、可复用性、可伸缩性、可读性、可维护性。
- 状态模式和策略模式在实现表现形式上十分相似,都是解决多重if判读问题,但是具体设计思想和实现机理有着本质差别。状态模式针对同一个对象不同状态实现不同的操作,不同分支具有完全不同的行为,策略模式针对同一个实现行为采用不同策略来优化执行过程,在保证同一个实现行为不变的前提下采用不同的策略实现,不同分支只是实现策略不同,但是整体都是做的同一件事,实现的是相同的行为。
转载请注明原作者