设计模式之策略模式
这是本人第一篇技术博客,写的不好的地方请多多指正,大家一起进步.在看了Head First之后,决定给自己写一个读书笔记,用来总结归纳自己学到了什么,并把它用自己的语言总结出来,一方面方便自己回顾,另一方面,自己总结一下自己领悟到的东西.
第一阶段
首先从鸭子这个抽象类开始,给鸭子赋予了三种方法:quack(),swim()和抽象方法display().所有的鸭子都得继承这个抽象类,这样做并没有任何问题.代码如下
package com.yczuoxin.pattern;
public abstract class Duck {
public void quack(){
System.out.println("呱呱叫");
}
public void swim(){
System.out.println("会游泳");
}
public abstract void display();
}
要创建一个类型的鸭子,只需要继承这个Duck类就可以了,例如现在创建一个绿头鸭:
package com.yczuoxin.pattern;
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("绿头");
}
}
此时我们创建的绿头鸭已经可以呱呱叫,游泳以及拥有了绿头的独特属性:
package com.yczuoxin.pattern;
public class Test {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.quack();
mallardDuck.swim();
mallardDuck.display();
}
}
呱呱叫
会游泳
绿头
第二阶段
此时需要增加需求,让部分鸭子会飞,此时如果简单地在Duck类中增加一个fly()方法的话就会导致所有的鸭子都会飞了:
package com.yczuoxin.pattern;
public abstract class Duck {
public void quack(){
System.out.println("呱呱叫");
}
public void swim(){
System.out.println("会游泳");
}
public void fly(){
System.out.println("在天上飞");
}
public abstract void display();
}
此时如果不对继承Duck的类(例如RubberDuck,与MallardDuck一致)做任何的操作,那么就会出现:
package com.yczuoxin.pattern;
public class Test {
public static void main(String[] args) {
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.display();
rubberDuck.fly();
}
}
呱呱叫
会游泳
我是一个橡皮鸭
在天上飞
橡皮鸭都可以在天上飞了,这明显不符合常理.但此时有一种方法可以缓解这个问题,就是重写fly()方法,并且不作任何操作:
package com.yczuoxin.pattern;
public class RubberDuck extends Duck {
@Override
public void display() {
System.out.println("我是一个橡皮鸭");
}
@Override
public void fly() {
// 不作任何操作
}
}
此时输出的结果发现,橡皮鸭在天上飞的方法没有做任何操作,但是按照逻辑,鸭子不应该都拥有fly()的行为.所以不应该让Duck拥有fly()方法,删掉该方法,于是用实现接口的方式,让那些需要有飞的能力的鸭子实现flyable接口:
package com.yczuoxin.pattern;
public interface Flyable {
void fly();
}
此时绿头鸭实现Flyable()接口拥有了飞的能力
package com.yczuoxin.pattern;
public class MallardDuck extends Duck implements Flyable {
@Override
public void display() {
System.out.println("绿头");
}
@Override
public void fly() {
System.out.println("在天上飞");
}
}
而橡皮鸭不实现Flyable()接口,也就不会有fly()方法,即没有飞的能力了.
小结继承的一些缺点:
1.代码在多个子类中重复.
2.运行时的行为不易被改变.
3.很难知道超类(父类)的全部方法.
4.改变会牵一发而动全身,造成其他子类不想要的改变.
第三阶段
虽然我们使用接口解决了所有鸭子都会飞的缺陷,但是代码无法复用又成了接口造成的问题,比如鸭子有什么样的飞行动作,相同的动作每次都要写一遍,并且导致你无论何时需要修改某个行为,你必须的往下追踪并在每一个定义此行为的类中修改它,一不小心就可能造成新的错误.此处引出了第一个设计原则——找出可能需要改变的部分,把他们独立出来,与不变的代码区分.此时把鸭子的飞属性单独建立一个行为类,索性,我们把多态也融入进来,使得鸭子的飞行方法可以动态的改变.为了实现这个功能,引入了第二个设计原则——面向接口编程.比如我们设定一个飞行为的接口FlyBehavior,然后对于不同的飞行动作创建实现类去实现FlyBehavior接口.
package com.yczuoxin.pattern;
public interface FlyBehavior {
void fly();
}
然后一个飞的实现类和一个不会飞的实现类:
package com.yczuoxin.pattern;
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("在天上飞");
}
}
package com.yczuoxin.pattern;
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("我不会飞");
}
}
package com.yczuoxin.pattern;
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
// 不作任何操作
System.out.println("在天上飞");
}}
此时修改Duck类使得飞成为鸭子的一个属性,并将
package com.yczuoxin.pattern;
public abstract class Duck {
FlyBehavior flyBehavior;
public void quack(){
System.out.println("呱呱叫");
}
public void swim(){
System.out.println("会游泳");
}
public void performFly(){
flyBehavior.fly();
}
public abstract void display();
}
在实例化特定的具体行为在实现了FlyBehavior的类中:
package com.yczuoxin.pattern;
public class MallardDuck extends Duck{
public MallardDuck(){
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("绿头");
}
}
package com.yczuoxin.pattern;
public class RubberDuck extends Duck {
public RubberDuck(){
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("我是一个橡皮鸭");
}
}
此时在看看测试类的结果
package com.yczuoxin.pattern;
public class Test {
public static void main(String[] args) {
Duck rubberDuck = new RubberDuck();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.display();
rubberDuck.performFly();
}
}
呱呱叫
会游泳
我是一个橡皮鸭
我不会飞
package com.yczuoxin.pattern;
public class Test {
public static void main(String[] args) {
Duck mallardDuck = new MallardDuck();
mallardDuck.quack();
mallardDuck.swim();
mallardDuck.display();
mallardDuck.performFly();
}
}
呱呱叫
会游泳
绿头
在天上飞
由此一来,我们利用继承和实现接口的方式及解决了继承带来的包袱,也解决了实现接口导致的代码无法复用.并且将飞行的动作交给了别的类去处理.然后利用了多态使得飞行的行为在运行时可以动态的去改变.
第四阶段
此时我们其实并没有实际上的在运行时动态,而是在实例的构造函数中来设定子类的行为,此时我们应该用更好的方法实现真正意义上的动态,就是使用setter方法.
package com.yczuoxin.pattern;
public abstract class Duck {
FlyBehavior flyBehavior;
public void quack(){
System.out.println("呱呱叫");
}
public void swim(){
System.out.println("会游泳");
}
public void performFly(){
flyBehavior.fly();
}
public abstract void display();
public FlyBehavior getFlyBehavior() {
return flyBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
而此时,我们子类不再需要构造函数去设定行为的实现类.只是在实例化的时候记得set进实例就好了,此处就放一个
package com.yczuoxin.pattern;
public class Test {
public static void main(String[] args) {
Duck rubberDuck = new RubberDuck();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.display();
rubberDuck.performFly();
rubberDuck.setFlyBehavior(new FlyWithWings());
rubberDuck.performFly();
}
}
呱呱叫
会游泳
我是一个橡皮鸭
我不会飞
在天上飞
此处可以看出,我们实现了动态的使不会飞橡皮鸭即刻拥有了飞翔的能力,这也就是是说明实现了动态给实例赋予了不同的行为.这便是策略模式.我总结的UML图如下: