在很多种情况下,一个对象的行为取决于一个或多个动态变化的属性,这些属性称为状态。例如水,液态时可以流动,气态时可以扩散,结冰时成了固态,这时既不能流动也不能扩散。例如使用键盘输入字符,当Caps Lock开启时,处于大写状态,这时输入的是大写字母,当Caps Lock关闭时,输入的是小写字母。例如用手机打电话,有信号并且话费充足时,才可以打电话,当没信号或者是处于欠费状态,就无法打电话了。
上面的例子都很接近于生活,浅显易懂。如果要对上面的这些逻辑进行编码实现,其实用if…else就可以,但当涉及的状态和行为很多时,这一大坨的代码就会变得很繁杂,维护起来很费力,也不利于扩展,当有新的状态时,就要对原来的代码进行比较大的改动,这时我们就需要考虑使用状态模式来让我们的代码变得更优雅,便于维护,便于扩展。
定义:允许一个对象在其内部状态改变时改变他的行为。
结构:
- Context:环境角色,定义客户端需要的接口,并且负责具体状态的切换
- State:抽象状态角色,为接口或者抽象类,负责对状态定义,并且封装环境角色以实现状态切换。
- ConcreteState:具体的状态角色,有两个职责:本状态的行为(本状态下要做的事情,以及本状态如何过渡到其他状态)管理和趋向状态处理;
适用场景:
- 行为随状态改变而改变,行为受状态约束的情况下
- 条件、分支判断语句的替代者:在程序中大量使用switch或者if判断语句导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,通过扩展子类实现条件的判断处理
UML:
下面就是状态模式的代码实现,这里以手机在有无话费状态下打电话为例子:
Context:本例中即为Class Phone
public class Phone {
private State hasPhoneCharge;
private State phoneArrears;
private State nowState;
//初始状态,该手机还剩下1块钱话费
private int phoneBill=1;
public State getHasPhoneCharge() {
return hasPhoneCharge;
}
public Phone setHasPhoneCharge(State hasPhoneCharge) {
this.hasPhoneCharge = hasPhoneCharge;
return this;
}
public State getPhoneArrears() {
return phoneArrears;
}
public Phone setPhoneArrears(State phoneArrears) {
this.phoneArrears = phoneArrears;
return this;
}
public State getNowState() {
return nowState;
}
public Phone setNowState(State nowState) {
this.nowState = nowState;
return this;
}
public int getPhoneBill() {
return phoneBill;
}
public Phone setPhoneBill(int phoneBill) {
this.phoneBill = phoneBill;
return this;
}
public void call(){
nowState.call();
}
public void payPhoneBill(int bill){
nowState.payPhoneBill(bill);
}
}
State:
public interface State {
void call();
void payPhoneBill(int bill);
}
ConcreteState:
public class PhoneArrears implements State{
Phone phone;
public PhoneArrears(Phone phone){
this.phone = phone;
}
@Override
public void call() {
System.out.println("您的手机已停机,无法拨打电话");
}
@Override
public void payPhoneBill(int bill) {
phone.setPhoneBill(phone.getPhoneBill()+bill);
System.out.println("成功充值"+bill+"元,目前话费余额"+phone.getPhoneBill());
if(phone.getPhoneBill()>0){
phone.setNowState(phone.getHasPhoneCharge());
}
}
}
public class HasPhoneCharge implements State{
Phone phone;
public HasPhoneCharge(Phone phone){
this.phone = phone;
}
@Override
public void call() {
System.out.println("通话成功,扣除一元话费");
phone.setPhoneBill(phone.getPhoneBill()-1);
if(phone.getPhoneBill()<=0){
System.out.println("您的手机因话费不足已停机,请充值");
phone.setNowState(phone.getPhoneArrears());
}
}
@Override
public void payPhoneBill(int bill) {
phone.setPhoneBill(phone.getPhoneBill()+bill);
System.out.println("成功充值"+bill+"元,目前话费余额"+phone.getPhoneBill());
}
}
Test:运行一下看下打印出的是什么!
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
//这里不是错误,想偷懒就弄成链式调用了
phone.setHasPhoneCharge(new HasPhoneCharge(phone))
.setPhoneArrears(new PhoneArrears(phone))
.setNowState(phone.getHasPhoneCharge());
phone.call();
phone.call();
phone.payPhoneBill(10);
phone.call();
}
}
总结:通过上面的代码我们可以看到,使用状态模式有效的避免被了过多的switch和if判断语句,降低了程序的复杂性,并且提高了可维护性。这样做程序的封装性也显得非常好,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。状态模式有一个缺点就是子类太多(这也是大多数设计模式的共同缺点),如果一个事物有很多个状态使用状态模式就会有很多子类,尽管如此,也好过于去看一大坨的if…else语句,至少在逻辑上可以一目了然。
参考:https://github.com/nivance/DPModel/tree/master/src/dp/com/company/state
https://segmentfault.com/a/1190000003818435
http://dreamrunner.org/blog/2014/05/04/%E6%B5%85%E8%B0%88%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F13/