需求故事
- 作为黄鸭子,我希望可以display “Yellow Duck”,可以fly "Yellow Duck Fly"
- 作为灰鸭子,我希望可以display “Gray Duck”,可以fly “Gray Duck Fly”
- 作为橡皮鸭子, 我希望可以display “Rubber Duck”,可以fly ”Rubber Duck can not Fly“
Story1
Story1 Test Case
public class DuckTest {
@org.junit.Test
public void testDuckFly(){
//Story 1
YellowDuck yellowDuck = new YellowDuck();
assertEquals("Yellow Duck", yellowDuck.display());
assertEquals("Yellow Duck Fly", yellowDuck.fly());
}
}
Story1 Implementation
Story2
Story2 Test case
public class DuckTest {
@org.junit.Test
public void testDuckFly(){
//Story 1
Duck yellowDuck = new YellowDuck();
assertEquals("Yellow Duck", yellowDuck.display());
assertEquals("Yellow Duck Fly", yellowDuck.fly());
//Story 2
Duck grayDuck = new GrayDuck();
assertEquals("Gray Duck", grayDuck.display());
assertEquals("Gray Duck Fly", grayDuck.fly());
}
}
增加Story2的Test case时,发现YellowDuck和GrayDuck由于有相同的行为,所以可以抽象成Duck,这样test case也都是真对duck的。
Story2 Implementation
在实现这两个Story的TestCase时,发现三个行为可以在Duck中实现
public class Duck {
protected String duckType = "";
protected FlyBehavior flyBehavior;
private static String duckStr = "Duck";
public String display() {
return new StringBuilder(duckType).append(" ").append(duckStr).toString();
}
public String fly() {
...
}
}
public class YellowDuck extends Duck {
public YellowDuck(){
super.duckType = "Yellow";
}
}
public class GrayDuck extends Duck {
public GrayDuck(){
super.duckType = "Gray";
}
}
第二步 增加Rubber Duck到Test Case中
增加的TestCase在没有修改Duck的情况下是肯定失败的
public class DuckTest {
@org.junit.Test
public void testDuckFly(){
//Story 1
Duck yellowDuck = new YellowDuck();
assertEquals("Yellow Duck", yellowDuck.display());
assertEquals("Yellow Duck Fly", yellowDuck.fly());
//Story 2
Duck grayDuck = new GrayDuck();
assertEquals("Gray Duck", grayDuck.display());
assertEquals("Gray Duck Fly", grayDuck.fly());
//Story 3
Duck rubberDuck = new RubberDuck();
assertEquals("Rubber Duck", rubberDuck.display());
assertEquals("Rubber Duck can not Fly", rubberDuck.fly());
}
}
这时候其实让上述testcase实现的最简单的方法是在Duck的Fly方法之中增加if语句,if RubberDuck就走另外一套Fly方法。但是这种简单方法是很脆弱的,因为如果有一个RocketDuck可以用Rocket飞,那么就有要修改Duck的代码了。
所以我们发现fly这个行为是一个可能变化的行为,那么我们需要对变换抽象,把行为抽象成接口FlyBehavior,然后能飞和不能飞是这个接口的两个实现。Duck本身只负责调用接口FlyBehavior就可以了,具体的Duck子类才需要知道究竟FlyBehavior的实现是什么。
public interface FlyBehavior {
public String fly(String duckType, String duckStr);
}
public class FlyWithWings implements FlyBehavior {
@Override
public String fly(String duckType, String duckStr) {
return new StringBuilder(duckType).append(" ").append(duckStr).append(" Fly").toString();
}
}
public class FlyNoWay implements FlyBehavior {
@Override
public String fly(String duckType, String duckStr) {
return new StringBuilder(duckType).append(" ").append(duckStr).append(" can not Fly").toString();
}
}
public class Duck {
protected String duckType = "";
protected FlyBehavior flyBehavior;
private static String duckStr = "Duck";
public String display() {
return new StringBuilder(duckType).append(" ").append(duckStr).toString();
}
public String fly() {
return flyBehavior.fly(duckType,duckStr);
}
}
public class YellowDuck extends Duck {
public YellowDuck(){
super.duckType = "Yellow";
super.flyBehavior = new FlyWithWings();
}
}
public class RubberDuck extends Duck {
public RubberDuck(){
super.duckType = "Rubber";
super.flyBehavior = new FlyNoWay();
}
}
和坚思辨
Strategy模式的本质其实就是变化行为的封装,把一个行为从继承变成了组合关系。这样做的好处就是在行为发生变化的时候不需要去改动行为对应的父类。
当发现下面的情景时,就应该考虑是不是可以用Strategy模式来解决问题了。
- 给一个父类增加了一个新子类,结果发现父类需要修改方法来判断子类的类型,这代表着父类的行为随着子类而变化。