设计模式是人们在面对同类型软件工程设计问题所总结出的一些经验。模式不是代码,而是某类问题的通用解决方案。
思考引入:
设计一个鸭子类:鸭子有“叫”,“游泳”,“外形”各种行为。鸭子都可以叫,都会游泳,但是外形不一样。定义一个鸭子的超类,里面有“叫”,“游泳”这两个方法和一个“外形”的抽象方法留给子类实现,不同子类继承这个超类,定义出不同外形的鸭子类。
// 鸭子的超类
public abstract class Duck {
public void quack(){
System.out.print("gaga")
}
public void swim(){
System.out.print("i am swimming")
}
public abstract display(){ }
}
现在有了新需求,给鸭子实现“飞”的功能,怎么办?
方式1:在超类里定义一个fly()方法,然后子类继承,就有了飞的功能。
// 鸭子的超类
public abstract class Duck {
public void quack(){
System.out.print("gaga")
}
public void swim(){
System.out.print("i am swimming")
}
public abstract display(){ }
public fly(){
System.out.print("i can fly")
}
}
这样行吗?好像是挺有道理的。那么问题来了,并不是所有鸭子都会飞,比如“玩具鸭”。这就需要“玩具鸭”这个子类自己去填坑修补这个“飞”的问题,一旦这样的子类很多,每个子类都要去填坑.
继承的问题:对类的局部改动,尤其是超类的改动,会影响其他部分。影响会有溢出效应。
方式2:在超类里定义一个fly()的抽象方法, 由子类自己去实现具体功能。或者直接定义一个fly的接口,子类实现该接口
// 鸭子的超类
public abstract class Duck {
public void quack(){
System.out.print("gaga")
}
public void swim(){
System.out.print("i am swimming")
}
public abstract display(){ }
public abstra fly(){ }
}
这样也可以实现该要求,但如果有很多子类都要实现fly功能,就需要分别实现该抽象放或者实现该接口。这种重复工作,代码的复用性很低。
这样,我们需要新的设计方式,对应项目的扩展性,降低复杂度。分析项目的变化与不变部分,提取变化部分,抽象成接口+实现。
1.定义行为接口
// 接口
public interface FlyBehavior{
void fly();
}
public interface QuackBehavior{
void quack();
}
2.定义出实现行为接口的行为(实现类),接口实现的各种行为类形成一个行为族。,如**“飞”,有的可以飞,有的不可以飞。有的鸭子“叫”**,有的叫声是“gaga”,有的叫声是“gege”.
// 叫声行为的行为族
// gaga叫的实现类
pubic class GagaQuack implements QuackBehavior{
void quack(){
System,out.print("gagaga...")
}
}
// gege叫的实现类
pubic class GegeQuack implements QuackBehavior{
void quack(){
System,out.print("gegege...")
}
}
// 飞行行为的行为族
// 可以飞行的实现类
pubic class CanFly implements FlyBehavior{
void fly (){
System,out.print("i can fly ...")
}
}
// 不能飞行的实现类
pubic class CanNotFly implements FlyBehavior{
void fly (){
System,out.print("i can't fly ...")
}
}
重新设计鸭子项目:
// 鸭子的超类
public abstract class Duck{
QuackBehavior quackBehavior;
FlyBehavior flyBehavior;
Public Duck(){}
Pulic void quack(){
quackBehavior.quack();
}
Pulic void fly(){
flyBehavior.fly();
}
// 提供这个方法,可以动态改变他的行为
public void setQuackBehavior (QuackBehavior qBehavior){
quackBehavior = qBehavior;
}
//swim方法和quack类似,此处就不重复写了。
}
定义一个可以飞,且gaga叫的绿头鸭子的“类”:
public class GreenHeadDuck extends Duck{
public GreenHeadDuck (){
QuackBehavior qBehavior = new GagaQuackBehavior();
FlyBehavior fBehavior = new CanFly();
}
@Override
public abstract display(){...}
}
定义一个gege叫,不会飞的玩具鸭的“类”:
public class ToyDuck extends Duck{
public ToyDuck (){
QuackBehavior qBehavior = new GegeQuackBehavior();
FlyBehavior fBehavior = new CanNotFly();
}
@Override
public abstract display(){...}
}
此时,通过new GreenHeadDuck()就可以创建一个可以飞,gaga叫绿头鸭的对象了。但是,我突然想让这个绿头鸭不gaga叫,而是gege叫,怎么办呢?再去重新定义一个类?这个时候就可以用鸭子超类里的setsetQuackBehavior方法了。通过
Duck gDuck = new GreenHeadDuck();
dDuck.quack();// 此时打印"gagaga..."
dDuck.setQuackBehavior(new GegeQuack());
gDuck.quack();//此时就打印"gegege..."
好处:这样,通过行为族的组合,用行为族里的对象来展现行为而不是把行为具体写在对象里。新增行为简单,行为类更好的复用,组合更方便。既有复用带来的复用的好处,没有挖坑。
这就是策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,子类里具体设定行为对象。原则就是分离变化部分,封装接口,基于接口编程各种功能。此模式让行为算法的变化独立于算法的使用者。
策略模式注意点:
1.分析项目中的变化与不变化部分。
2.多用组合少用继承;用行为类组合,而不是行为的继承,更有弹性。