android 设计模式 状态模式

本文通过一个糖果售卖机的例子,详细解析了如何使用状态模式重构代码,提高软件设计的灵活性和可扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

君子改过,小人饰非;改过终悟,饰非终迷;终悟福至,终迷祸归。

---邵雍


前几天看了设计模式之状态模式,写点代码巩固记忆。关于需求:糖果公司的糖果售卖机程序,功能用下图描述,按照下图开发功能。


在阅读文章之前我先构思了一下,结果继续读下去,发现和书中所写不谋而合,但这是令人沮丧的,因为书中总是先把“虽然实现功能但满是问题”的代码写在前面,然后进行分析,最后使用设计模式来重新构造代码。每次都是如此,很受打击。那先把满是问题的代码写出来吧。

====="糖果机" Machine====

<pre name="code" class="java">public class Machine {
    private final static String TAG = "Machine";
    //售罄
    public final static int SOLDOUT = 0;
    //没有硬币
    public final static int NO_QUARTER = 1;
    //没有硬币
    public final static int HAS_QUARTER = 2;
    //售出
    public final static int SOLD = 3;
    //当前状态
    public int state = SOLDOUT;
    //糖果数量
    public int count = 0;
    //message
    private String message = "";

    public Machine(int count) {
        this.count = count;
        if (count > 0) {
            state = NO_QUARTER;
        }
    }

    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            Log.d(TAG, "已经有硬币了,你不能再次投入硬币");
            message = "已经有硬币了,你不能再次投入硬币";
        } else if (state == NO_QUARTER) {
            Log.d(TAG, "你投入了硬币");
            message = "你投入了硬币";
            state = HAS_QUARTER;
        } else if (state == SOLDOUT) {
            Log.d(TAG, "抱歉,已售罄");
            message  = "抱歉,已售罄";
        } else if (state == SOLD) {
            Log.d(TAG, "请稍等");
            message  = "请稍等";
        }
    }

    public void ejectQuarter() {
        if (state == HAS_QUARTER) {
            Log.d(TAG, "即将退回硬币");
            message  = "即将退回硬币";
            state = NO_QUARTER;
        } else if (state == NO_QUARTER) {
            message  = "你没有投入硬币";
            Log.d(TAG, "你没有投入硬币");
        } else if (state == SOLDOUT) {
            Log.d(TAG, "你没有投入硬币");
            message  = "你没有投入硬币";
        } else if (state == SOLD) {
            Log.d(TAG, "抱歉,已经在出货");
            message  = "抱歉,已经在出货";
        }
    }

    public void turnCrank() {
        if (state == HAS_QUARTER) {
            Log.d(TAG, "你按下操作杆……");
            message  = "你按下操作杆……";
            state = SOLD;
            dispense();
        } else if (state == NO_QUARTER) {
            Log.d(TAG, "你没有投入硬币,按下操作杆无法得到糖果");
            message  = "你没有投入硬币,按下操作杆无法得到糖果";
        } else if (state == SOLDOUT) {
            Log.d(TAG, "你按下操作杆,但机器里没有糖果");
            message  = "你按下操作杆,但机器里没有糖果";
        } else if (state == SOLD) {
            message  = "抱歉,已经在出货,再次按下操作杆也无法给您第二枚糖果";
            Log.d(TAG, "抱歉,已经在出货,再次按下操作杆也无法给您第二枚糖果");

        }
    }

    public void dispense() {
        if (state == HAS_QUARTER) {
            Log.d(TAG, "没有糖果滑出……");
            message  = "没有糖果滑出……";
            state = SOLD;
        } else if (state == NO_QUARTER) {
            Log.d(TAG, "你需要投入硬币");
            message  = "你需要投入硬币";
        } else if (state == SOLDOUT) {
            Log.d(TAG, "没有糖果滑出……");
            message  = "没有糖果滑出……";
        } else if (state == SOLD) {
            Log.d(TAG, "糖果正在滑出……");
            message  = "糖果正在滑出……";
            count = count - 1;
            if (count < 1) {
                state = SOLDOUT;
                Log.d(TAG, "已售罄");
                message  = "已售罄";
            } else {
                state = NO_QUARTER;
            }

        }
    }
    public String getMessage(){
        return  message;
    }
}



======MainActivity====

public class MainActivityMachine extends ActionBarActivity implements View.OnClickListener {
    private Button insert;
    private Button eject;
    private Button press;
    private TextView message;
    private Machine machine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_machine);
        machine = new Machine(5);
        findView();
        setClickListener();

    }

    private void findView() {
        insert = (Button) findViewById(R.id.insert_quarter);
        eject = (Button) findViewById(R.id.eject_quarter);
        press = (Button) findViewById(R.id.press);
        message = (TextView) findViewById(R.id.message);
    }

    private void setClickListener() {
        insert.setOnClickListener(this);
        eject.setOnClickListener(this);
        press.setOnClickListener(this);

    }

    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
       switch (v.getId()){
           case R.id.insert_quarter:
               machine.insertQuarter();
               break;
           case R.id.eject_quarter:
               machine.ejectQuarter();
               break;
           case R.id.press:
               machine.turnCrank();
               break;
       }
        if(!TextUtils.isEmpty(machine.getMessage())){
            message.setText(machine.getMessage());
        }

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

=====效果图(录屏视频地址http://pan.baidu.com/s/1hqxQza8)====
























对于这个代码,使用起来是没有问题的,但如果新加如其他功能,比如书中所描述的“希望在用户按下操作杆的时候有10%的机会得到两枚糖果”,那么就需要在Machine的每个方法中加入新的判断逻辑。这样,这份代码再加入新的功能时可能导致bug出现,并且没有遵循“封装变化”的思想,同时并不符合面向对象。这个时候就可以引入“状态模式”来设计代码了。我们要做的是把每个状态写成单独的一个类,这个类只负责自己的动作,当某一个状态发生改变的时候,不会影响到其他代码。

首先,定义一个接口,这个接口里糖果机每个状态都有一个对应的方法。

然后,为机器的每个状态实现一个类,这些类负责具体的实现对应状态下机器的行为。

=======接口类======

public interface StateInterface {
    public void insertQuarter();


    public void ejectQuarter();


    public void turnCrank();


    public void dispense();

    public String getStateStr();


}
======“没有硬币”状态 NoQuarterState=====

public class NoQuarterState implements StateInterface{
    private  Machine machine;
    public  NoQuarterState (Machine machine){
        this.machine = machine;
    }
    @Override
    public void insertQuarter() {
        MainActivityMachine.message_str = "你投入了硬币";
        machine.setMachineState(machine.getHasQuarterState());

    }

    @Override
    public void ejectQuarter() {
        Log.d(MainActivityMachine.TAG, "NoQuarterState ejectQuarter() ");
        MainActivityMachine.message_str = "没有投入硬币,无法退回硬币";
    }

    @Override
    public void turnCrank() {
        Log.d(MainActivityMachine.TAG, "NoQuarterState turnCrank() ");
        MainActivityMachine.message_str = "没有投入硬币,按下操作杆不会得到糖果";
    }

    @Override
    public void dispense() {
        Log.d(MainActivityMachine.TAG, "NoQuarterState dispense() ");
        MainActivityMachine.message_str = "没有投入硬币,不会得到糖果";
    }
    @Override
    public String getStateStr() {
        Log.d(MainActivityMachine.TAG, "NoQuarterState getStateStr() ");
        return "NoQuarterState";
    }
}

=======“有硬币”状态 HasQuarterState=====

public class HasQuarterState implements StateInterface {
   Machine machine;
    public HasQuarterState(Machine machine) {
        this.machine  = machine;
    }

    @Override
    public void insertQuarter() {
        Log.d(MainActivityMachine.TAG, "HasQuarterState getStateStr() ");
        MainActivityMachine.message_str = "已经有硬币了,你不能再次投入硬币";
    }

    @Override
    public void ejectQuarter() {
        Log.d(MainActivityMachine.TAG, "HasQuarterState ejectQuarter() ");
        MainActivityMachine.message_str = "正在退回硬币";
        machine.setMachineState(machine.getNoQuarterState());

    }

    @Override
    public void turnCrank() {
        Log.d(MainActivityMachine.TAG, "HasQuarterState turnCrank() ");
        MainActivityMachine.message_str = "正在滑出糖果";
        machine.setMachineState(machine.getSoldState());

    }

    @Override
    public void dispense() {
        Log.d(MainActivityMachine.TAG, "HasQuarterState dispense() ");
        MainActivityMachine.message_str = "没有糖果滑出,请按下操作杆";
    }

    @Override
    public String getStateStr() {
        Log.d(MainActivityMachine.TAG, "HasQuarterState getStateStr() ");
        return "HasQuarterState";
    }
}

======“正在售出”状态  SoldState=====

public class SoldState implements  StateInterface {
    Machine machine;

    public SoldState(Machine machine) {
        this.machine = machine;
    }

    @Override
    public void insertQuarter() {
        MainActivityMachine.message_str = "正在出货,请稍后投入硬币";
        Log.d(MainActivityMachine.TAG, "SoldState insertQuarter() ");
    }

    @Override
    public void ejectQuarter() {
        MainActivityMachine.message_str = "正在出货,不能退回硬币";
        Log.d(MainActivityMachine.TAG, "SoldState ejectQuarter() ");
    }

    @Override
    public void turnCrank() {
        MainActivityMachine.message_str = "正在出货,重复按下操作杆不会获得更多糖果";
        Log.d(MainActivityMachine.TAG, "SoldState turnCrank() ");
    }

    @Override
    public void dispense() {
        Log.d(MainActivityMachine.TAG, "SoldState dispense() ");
        MainActivityMachine.message_str = "正在滑出糖果";
        machine.releaseBall();
        if(machine.getCount()>0){
            machine.setMachineState(machine.getNoQuarterState());
        }else{
            machine.setMachineState(machine.getSoldOutState());
        }
    }

    @Override
    public String getStateStr() {
        Log.d(MainActivityMachine.TAG, "SoldState getStateStr() ");
        return "SoldState";
    }
}

======“售罄”状态 SoldOutState=====

public class SoldOutState implements  StateInterface {
    Machine machine;

    public SoldOutState(Machine machine) {
        this.machine = machine;
    }

    @Override
    public void insertQuarter() {
        MainActivityMachine.message_str = "抱歉,已售罄";
        Log.d(MainActivityMachine.TAG, "SoldOutState insertQuarter() ");
    }

    @Override
    public void ejectQuarter() {
        Log.d(MainActivityMachine.TAG, "SoldOutState ejectQuarter() ");
        MainActivityMachine.message_str = "机器里没有硬币";
    }

    @Override
    public void turnCrank() {
        Log.d(MainActivityMachine.TAG, "SoldOutState turnCrank() ");
        MainActivityMachine.message_str = "售罄,不会有糖果滑出";
    }

    @Override
    public void dispense() {
        Log.d(MainActivityMachine.TAG, "SoldOutState dispense() ");
        MainActivityMachine.message_str = "售罄,不会有糖果滑出";
    }

    @Override
    public String getStateStr() {
        Log.d(MainActivityMachine.TAG, "SoldOutState getStateStr() ");
        return "SoldOutState";
    }
}

=======糖果机 Machine=======

public class Machine {
    private final static String TAG = "Machine";
    //售罄
    public final static int SOLDOUT = 0;
    //没有硬币
    public final static int NO_QUARTER = 1;
    //没有硬币
    public final static int HAS_QUARTER = 2;
    //售出
    public final static int SOLD = 3;
    //当前状态
    public int state = SOLDOUT;
    //糖果数量
    public int count = 0;
    //message
    private String message = "";
    //机器状态
    public StateInterface machineState;

    //各个状态对象
    private StateInterface hasQuarterState;
    private StateInterface noQuarterState;
    private StateInterface soldOutState;
    private StateInterface soldState;

    public Machine(int count) {
        hasQuarterState = new HasQuarterState(this);
        noQuarterState = new NoQuarterState(this);
        soldOutState =new SoldOutState(this);
        soldState = new SoldState(this);

        this.count = count;
        if (count > 0) {
            machineState = noQuarterState;
        }
    }

    public StateInterface getMachineState() {
        return machineState;
    }

    public void setMachineState(StateInterface machineState) {
        this.machineState = machineState;
    }

    public StateInterface getHasQuarterState() {
        return hasQuarterState;
    }

    public StateInterface getNoQuarterState() {
        return noQuarterState;
    }

    public StateInterface getSoldOutState() {
        return soldOutState;
    }

    public StateInterface getSoldState() {
        return soldState;
    }

    public void releaseBall(){
        if(count>0){
            count--;
        }
    }

    public int getCount(){
        return count;
    }
    public void insertQuarter() {
        machineState.insertQuarter();
//        if (state == HAS_QUARTER) {
//            Log.d(TAG, "已经有硬币了,你不能再次投入硬币");
//            message = "已经有硬币了,你不能再次投入硬币";
//        } else if (state == NO_QUARTER) {
//            Log.d(TAG, "你投入了硬币");
//            message = "你投入了硬币";
//            state = HAS_QUARTER;
//        } else if (state == SOLDOUT) {
//            Log.d(TAG, "抱歉,已售罄");
//            message = "抱歉,已售罄";
//        } else if (state == SOLD) {
//            Log.d(TAG, "请稍等");
//            message = "请稍等";
//        }
    }

    public void ejectQuarter() {
        machineState.ejectQuarter();
//        if (state == HAS_QUARTER) {
//            Log.d(TAG, "即将退回硬币");
//            message = "即将退回硬币";
//            state = NO_QUARTER;
//        } else if (state == NO_QUARTER) {
//            message = "你没有投入硬币";
//            Log.d(TAG, "你没有投入硬币");
//        } else if (state == SOLDOUT) {
//            Log.d(TAG, "你没有投入硬币");
//            message = "你没有投入硬币";
//        } else if (state == SOLD) {
//            Log.d(TAG, "抱歉,已经在出货");
//            message = "抱歉,已经在出货";
//        }
    }

    public void turnCrank() {
        machineState.turnCrank();
        machineState.dispense();
//        if (state == HAS_QUARTER) {
//            Log.d(TAG, "你按下操作杆……");
//            message = "你按下操作杆……";
//            state = SOLD;
//            dispense();
//        } else if (state == NO_QUARTER) {
//            Log.d(TAG, "你没有投入硬币,按下操作杆无法得到糖果");
//            message = "你没有投入硬币,按下操作杆无法得到糖果";
//        } else if (state == SOLDOUT) {
//            Log.d(TAG, "你按下操作杆,但机器里没有糖果");
//            message = "你按下操作杆,但机器里没有糖果";
//        } else if (state == SOLD) {
//            message = "抱歉,已经在出货,再次按下操作杆也无法给您第二枚糖果";
//            Log.d(TAG, "抱歉,已经在出货,再次按下操作杆也无法给您第二枚糖果");
//
//        }
    }

//    public void dispense() {
//        if (state == HAS_QUARTER) {
//            Log.d(TAG, "没有糖果滑出……");
//            message = "没有糖果滑出……";
//            state = SOLD;
//        } else if (state == NO_QUARTER) {
//            Log.d(TAG, "你需要投入硬币");
//            message = "你需要投入硬币";
//        } else if (state == SOLDOUT) {
//            Log.d(TAG, "没有糖果滑出……");
//            message = "没有糖果滑出……";
//        } else if (state == SOLD) {
//            Log.d(TAG, "糖果正在滑出……");
//            message = "糖果正在滑出……";
//            count = count - 1;
//            if (count < 1) {
//                state = SOLDOUT;
//                Log.d(TAG, "已售罄");
//                message = "已售罄";
//            } else {
//                state = NO_QUARTER;
//            }
//
//        }
//    }

//    public String getMessage() {
//        return message;
//    }
    public String getStateStr(){
        return machineState.getStateStr();
    }
}

======Mainactivity======

public class MainActivityMachine extends ActionBarActivity implements View.OnClickListener {
    public static final String TAG  ="MainActivityMachine";
    private Button insert;
    private Button eject;
    private Button press;
    private TextView message;
    private TextView ballCount;
    private TextView stateMessage;
    private Machine machine;
    public static String message_str = "";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_machine);
        machine = new Machine(5);
        findView();
        setClickListener();

    }

    private void findView() {
        insert = (Button) findViewById(R.id.insert_quarter);
        eject = (Button) findViewById(R.id.eject_quarter);
        press = (Button) findViewById(R.id.press);
        message = (TextView) findViewById(R.id.message);
        ballCount = (TextView) findViewById(R.id.ball_count);
        stateMessage = (TextView) findViewById(R.id.state_message);
    }

    private void setClickListener() {
        insert.setOnClickListener(this);
        eject.setOnClickListener(this);
        press.setOnClickListener(this);

    }

    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
       switch (v.getId()){
           case R.id.insert_quarter:
               machine.insertQuarter();
               break;
           case R.id.eject_quarter:
               machine.ejectQuarter();
               break;
           case R.id.press:
               machine.turnCrank();
               break;
       }
            message.setText("提示信息:"+message_str);
            ballCount.setText("剩余数目:"+String.valueOf(machine.getCount()));
            stateMessage.setText("当前状态:"+String.valueOf(machine.getStateStr()));


    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

}

======效果图(视频链接http://pan.baidu.com/s/1c08hTSG)=====

















在有硬币状态下点击“按下操作杆”按钮,textview显示的“当前状态”由“HasQuarter”变为“NoQuarter”,但是中间会有一个“Sold”状态,log信息如下:

04-06 17:53:03.194    2600-2600/com.buxiaohui.newimage D/MainActivityMachine﹕ HasQuarterState getStateStr()
04-06 17:53:04.017    2600-2600/com.buxiaohui.newimage D/MainActivityMachine﹕ HasQuarterState turnCrank()
04-06 17:53:04.017    2600-2600/com.buxiaohui.newimage D/MainActivityMachine﹕ SoldState dispense()
04-06 17:53:04.019    2600-2600/com.buxiaohui.newimage D/MainActivityMachine﹕ NoQuarterState getStateStr()
至此,当需要加入“幸运模式”(有机会获得两枚糖果)时,就可以再写一个新的状态的类,与SoldState类似,只是dispense方法中需要判断剩余糖果数量,执行一次或两次releaseBall方法,而且需要在HasQuarter的turnCrank方法里根据随机数生成判断逻辑,并决定是进入幸运模式还是进入之前代码里写的模式(SoldState)。这就是状态模式的好处。

不过说实话,状态模式我似乎还是不知道以后怎么应用到自己的代码里……只好照葫芦画瓢,不过以后遇到文章开始写的那种代码的时候至少知道往状态模式这边靠了……


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值