设计模式第一弹,发现在继续学习Java相关知识前,很有必要学习下设计模式,其中几个比较重要的设计模式其实在之前的使用中已经出现过很多次,但是由于没有学过设计模式,也就知其然不知其所以然的拿来用了,所以接下来准备好好整理学习设计模式,博客整理的内容均来自《Head First 设计模式》。下面开始整理第一个设计模式–策略模式
1.概念
策略模式:针对一组算法,将每一个算法分别封装起来,让它们之前可以互相替换,此模式让算法的变化独立于使用算法的客户。
2.设计原则
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。即:封装变化
把会变化的部分取出并“封装”起来,以便以后可以轻易地改动或扩充此部分,而不影响到不需要变化的其他部分,总结起来就是:分开变化和不会变化的部分。针对接口编程,而不是针对实现编程
将需要变化的行为放在分开的类中,此类专门提供某种行为接口的实现。多用组合,少用继承
区分“有一个”(组合)关系和“是一个”(继承)关系,使用 组合建立系统具有很大的弹性,不仅可以将算法族封装成类,更可以“在运行时动态的改变行为”。
3.使用举例
借用《Head First 设计模式》书上的实例:
3.1 假设鸭子类(Duck),有许多不同的子类:
绿头鸭(MallardDuck)
红头鸭(RedHeadDuck)
橡皮鸭(RubberDuck)
诱饵鸭(DecoyDuck)
模型鸭(ModelDuck)
子类继承父类的行为:呱呱叫(quack())和游泳(swim())
嗯,一切看起来都很正常,通过继承,子类得到父类的行为,这样子类也会呱呱叫和游泳;
3.2 现在想让某些鸭子具有新的行为:飞行(fly())
首先想到的是在需要具有飞行行为的子类中添加新的方法(fly())不就解决了吗!嗯,这是在子类比较少的情况下可以使用的“笨办法”,一旦子类很多,而且也许好几个子类具有的飞行行为是一样的呢?这样不就产生了大量重复代码吗?这是我们需要避免的!
其次想到的是在父类(Duck)中添加行为(fly()),其子类通过继承就拥有飞行行为了。看似问题解决了,但是想想这样子所有的子类就都拥有了飞行的行为,包括橡皮鸭(不应该具有飞行的行为),所以这种方法行不通,因为通过继承,一旦改变父类中的某些行为,所有子类都将受影响,导致某些不应该变化的子类也发生了变化,这种改变将是牵一发而动全身的;
通过继承来提供Duck的行为的缺点:- 导致代码在多个子类中重复
- 运行时的行为不容易改变
- 很难知道所有鸭子的全部行为
接下来想到的方法是通过接口实现,通过将变化的行为(呱呱叫,游泳,飞行)封装到单独的接口中,某个子类想要拥有哪种行为,实现那种接口,重写其方法就行了,看似能解决暂时的问题,然而一旦行为增加(比如每隔一周增加一种新的行为)或者子类增加,这样将造成严重的混乱局面,所以,这种方法也不合适。
通过把会变的行为提取出来设计成一个接口的缺点:- 导致重复代码变多
- 导致代码无法进行复用
3.3 接下来就是策略模式展现魅力的时候了
通过以上思路,了解到添加新的行为并不是那么容易,这个过程是变化的,根据设计原则:封装变化,我们知道变化的部分是鸭子类的行为,而不变的是鸭子类的继承关系,把握住了变化与不变的内容,于是进行如下设计:建立对应行为的接口,比如创建飞行行为的接口(FlyBehavior),其中有个方法fly(),然后所有的飞行行为类都实现它,实现其中的fly()方法,结构如下:
其他行为以此类推,这里就不重复了。
接下来整合鸭子的行为,做法是这样的:
- 首先,在Duck类中定义对应行为接口的实例,例如上面的飞行行为:
protected FlyBehavior flyBehavior;
- 然后,实现调用接口行为的方法,依然使用飞行行为举例:
public void performFly() {
flyBehavior.fly();
}
- 接下来是在子类中指定其应具有的行为
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior =new FlyNoWay();//这里指定的飞行行为是“不会飞”
}
}
嗯,以上,基本结构出来了,接下来进行测试,创建测试类:
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performFly();//调用了MallardDuck类的飞行方法,看上面,MallardDuck类指定的飞行行为是“不会飞”
}
以上,简单总结了策略模式的使用,使用策略模式的优点如下:
- 采用良好的oo软件设计原则
- 分开变化和不会变化的部分
- 针对接口编程
4. 实例代码
实例代码根据《Head First 设计模式》书上的代码进行编写测试,如下:
4.1 首先,封装变化,即封装行为类:
package DuckInterface;
public interface FlyBehavior {
public void fly();
}
package DuckFlyBehavior;
import DuckInterface.FlyBehavior;
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("-->I can fly!!!");
}
}
package DuckFlyBehavior;
import DuckInterface.FlyBehavior;
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("-->I cannot fly...");
}
}
注:另外一个行为:呱呱叫(quack)结构与飞行行为结构类似,这里就不贴出代码了;
4.2 接下来是定义鸭子父类:
package DuckAbstract;
import DuckInterface.FlyBehavior;
import DuckInterface.QuackBehavior;
public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public Duck() {
}
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
// 所有鸭子都具有游泳的行为,所以游泳放在父类中,所有子类都能继承这个方法
public void swim() {
System.out.println("-->all ducks can swim");
}
public abstract void display();
}
可以看到,在鸭子父类中定义了封装好的行为,并调用接口的方法,具体调用哪个方法并不知道,需要根据具体的子类进行调用,下一步就是具体定义不同子类拥有的具体行为;
4.3 创建子类,继承父类,定义好不同子类拥有的具体行为,这里以绿头鸭(MallardDuck)示范:
package DuckInstence;
import DuckAbstract.Duck;
import DuckFlyBehavior.FlyNoWay;
import DuckQuackBehavior.MuteQuack;
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new MuteQuack();// 绿头鸭具有MuteQuack的行为,就是“不会叫”
flyBehavior = new FlyNoWay();// 绿头鸭具有FlyNoQWay的行为,就是“不会飞”
}
@Override
public void display() {
System.out.println("-->this is MallardDuck");
}
}
4.4 最后一步,进行测试:
package DuckInstence;
import DuckAbstract.Duck;
public class TestDuck {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performFly();
mallard.performQuack();
}
}
在主方法中创建MallardDuck类的对象,调用其行为,结果显示如下:
以上,通过使用策略模式,很好的解决了不同子类具有不同行为,但是又互不影响的问题,达到了良好的OO设计必须具备可复用、可扩充、可维护 的特性