君子改过,小人饰非;改过终悟,饰非终迷;终悟福至,终迷祸归。
---邵雍
前几天看了设计模式之状态模式,写点代码巩固记忆。关于需求:糖果公司的糖果售卖机程序,功能用下图描述,按照下图开发功能。
在阅读文章之前我先构思了一下,结果继续读下去,发现和书中所写不谋而合,但这是令人沮丧的,因为书中总是先把“虽然实现功能但满是问题”的代码写在前面,然后进行分析,最后使用设计模式来重新构造代码。每次都是如此,很受打击。那先把满是问题的代码写出来吧。
====="糖果机" 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)。这就是状态模式的好处。
不过说实话,状态模式我似乎还是不知道以后怎么应用到自己的代码里……只好照葫芦画瓢,不过以后遇到文章开始写的那种代码的时候至少知道往状态模式这边靠了……