阅读《Head First设计模式》来学习设计模式,以下代码都是书中的例子,通过不断的优化代码来达到学习的目的。《Head First设计模式》中的完整的代码的例子都可以在https://www.wickedlysmart.com/head-first-design-patterns/下载到。
看书是学习的一种方式,写博客是巩固学习设计模式的另一种方式,以此对学习模式的理解更加深刻。以下从业务需求开始。
需求
某公司设计了一款鸭子游戏。游戏中会有各种鸭子,各种行为,比如游泳,呱呱叫。下面根据需求来设计一下。
开始设计
按照我的正常的逻辑思维,首先要设计一个鸭子超类,他会有很多方法,属性。子类通过继承来得到行为。(哈哈哈,太幼稚了)
package headfirst.strategy.old;
public abstract class Duck {
public abstract void quack();
public abstract void swim();
}
package headfirst.strategy.old;
public class MallardDuck extends Duck{
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void swim() {
System.out.println("Swim");
}
}
package headfirst.strategy.old;
public class RedheadDuck extends Duck{
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void swim() {
System.out.println("Swim");
}
}
思考一下,上面的代码可能的问题:
- 代码看起来很笨重,每个子类都要重写父类的方法,实现功能。
- 如果超类新添加了一个新的方法,每个子类也要重写同样的方法。
- 如果父类的方法子类没有这个特性,难道要抛出一个异常或者不管了吗。
设计1
如果鸭子有会飞的方法,但是有的鸭子不会飞怎么办呢。设计如下:
由于有的鸭子没有fly()的功能,或者橡皮鸭子也没有quack()的功能,我们把这2个行为单独分离出来,写成接口。
把不变的属性和方法封装在类中。容易改变的行为单独创建一个接口类,鸭子子类分别继承鸭子类和行为的接口。
这就涉及到一个设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
设计1已经进行了分离,系统变得更有弹性了,但是通过继承而来的方法,每个子类也是需要重写方法来实现fly()的功能和quack()的功能,这个设计是否还可以继续改进呢?
最终设计
从现在开始鸭子的行为被放在单独的类中,这些类专门提供某些接口的实现的功能。
应用到了设计原则:
多用组合,少用继承。
代码如下:
package headfirst.strategy.recent;
import headfirst.strategy.recent.FlyBehavior;
import headfirst.strategy.recent.QuackBehavior;
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
}
package headfirst.strategy.recent;
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}
package headfirst.strategy.recent;
public class RubberDuck extends Duck {
//默认构造
public RubberDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
//有参构造,初始化就可以赋值行为
public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
this.flyBehavior = flyBehavior;
this.quackBehavior = quackBehavior;
}
}
package headfirst.strategy.recent;
public interface FlyBehavior {
public void fly();
}
package headfirst.strategy.recent;
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("I'm flying!!");
}
}
package headfirst.strategy.recent;
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("I can't fly");
}
}
package headfirst.strategy.recent;
public interface QuackBehavior {
public void quack();
}
package headfirst.strategy.recent;
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("Quack");
}
}
package headfirst.strategy.recent;
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("Squeak");
}
}
测试代码如下:
package headfirst.strategy.recent;
public class Test {
public static void main(String[] args) {
Duck mallardDuckOne = new MallardDuck();
mallardDuckOne.performFly();
mallardDuckOne.performQuack();
mallardDuckOne.setFlyBehavior(new FlyNoWay());
mallardDuckOne.performFly();
System.out.println("========================");
Duck rubberDuckOne = new RubberDuck(new FlyNoWay(),new Squeak());
rubberDuckOne.performFly();
rubberDuckOne.performQuack();
//改变飞行行为(1)
rubberDuckOne.setFlyBehavior(new FlyWithWings());
rubberDuckOne.performFly();
//再此改变飞行行为(2)
rubberDuckOne.setFlyBehavior(new FlyNoWay());
rubberDuckOne.performFly();
}
}
调用performFly()和performQuack()会打印语句,控制台打印输出如下:
以上代码应用了策略模式,定义如下:
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
我的可以行为想象成“一族算法”,把鸭子做的事看成是算法。方法(1)和(2)改变飞机的行为可以看成是改变算法,改变了行为动作。
从以上代码可以看出在使用策略模式过程中的优缺点:
- 代码提供了公共的策略抽象类,各个抽象类有多个抽象的具体实现类,各个行为分别封装起来。通过组合直接继承行为,代码复用性很高;
- 修改代码只要修改父类就可以了,子类不需要变动;
- 可以通过方法动态的改变行为。
缺点:
- 各个策略抽象类可能会很多,策略的实现类可能就会更多,这些类需要单独记忆,很麻烦;
- 代码量其实很多。
以上就是策略模式的简单学习,更好的理解还是需要在项目实战中多思考,多实践。