要想写出可维护、可复用、可扩展的应用程序,就必须掌握一些设计模式的思想.设计模式重在设计,是一种思想层面的东西,需要细细的去领悟。今天学习了一下设计模式的第一个模式——策略模式,现在把它整理出来。
策略模式:定义了算法族,分别封装起来,让他们之间可以互相转换,此模式让算法的变化独立于使用算法的用户。初次去看这么一个定义,估计很多人跟我一样,直接晕掉或者崩溃掉。完全不理解,没关系,我们由一个例子来逐步分析策略模式。
这是一个关于鸭子类的游戏,在这个程序中,会有各种各样的鸭子在水里游泳,呱呱叫等。我们来看代码实现.
<span style="font-family:Courier New;font-size:14px;">/*
* 鸭子的抽象类 我们定义了一个鸭子超类,他拥有鸭子共同特征的方法,比如呱呱叫,并且拥有一个抽象的方法,因为鸭子有各种各样不同的外观
*/
public abstract class Duck {
//鸭子呱呱叫
public void quack() {
System.out.println("呱呱...");
}
//游泳行为
public void swim() {
System.out.println("我可以在水里游泳");
}
//鸭子外观的抽象方法
public abstract void display();
}</span>
<span style="font-family:Courier New;font-size:14px;">/*
* 外观是绿头的鸭子 继承自超类 Duck那么它也就拥有了超类中的行为,实现超类的抽象方法,以展示自己不同于超类的特征
*/
class MallardDuck extends Duck{
@Override
public void display() {
System.out.println("我是绿头鸭哦");
}
}</span>
<span style="font-family:Courier New;font-size:14px;">/*
* 外观是红头的鸭子
*/
class ReadheadDuck extends Duck{
@Override
public void display() {
System.out.println("我是红头鸭哦");
}
}</span>
<span style="font-family:Courier New;font-size:14px;">public class DuckTest {
public static void main(String[] args) {
MallardDuck md = new MallardDuck();
md.display();
md.quack();
md.swim();
}
}</span>
这个程序已经写好了,不过好像还没有用到设计模式相关的东西,是的,让我们一步一步的引入策略模式。
现在我们有一个需求,让会飞的鸭子来超越其他的竞争者。如果让我们去实现这样的功能,可能会觉得很简单啊,我们在超类Duck中,写一个飞行类的方法,这样他的子类不就都拥有了飞行行为。恩,确实是这样,但是有一个问题,并不是所有的鸭子都具有飞行行为,比如在这个游戏中会有一些木有鸭子,他们是不会飞甚至不会叫的。那么刚才的做法就会使得不会飞的鸭子也有了飞行的行为,显然是不合适的。
那么我们是不是可以利用接口去达到刚才的需求呢。我们把飞行的行为以及呱呱叫的行为从超类中抽取出来,放到Flyable和Quackable接口中,这样就可以让拥有这些行为的类去实现这些接口.我们来看代码:
<span style="font-family:Courier New;font-size:14px;">/*
* 鸭子的抽象类
*/
public abstract class Duck {
//鸭子外观的抽象方法
public abstract void display();
public void swim() {
System.out.println("游啊游..");
}
}
/*
* 外观是绿头的鸭子
*/
class MallardDuck extends Duck implements Quackable,Flyable{
@Override
public void display() {
System.out.println("我是绿头鸭哦");
}
@Override
public void fly() {
System.out.println("我正在飞哦..");
}
@Override
public void quack() {
System.out.println("呱呱叫..");
}
}
/*
* 木头鸭 不会飞也不会叫 外观是木头样的颜色 假定因为可以在水上漂 就相当于会游泳
*/
class WoodDuck extends Duck{
@Override
public void display() {
System.out.println("我的外观颜色是木般的颜色");
}
}
/*
* 飞行的接口
*/
interface Flyable {
void fly();
}
/*
* 呱呱叫行为的接口
*/
interface Quackable {
void quack();
}
public class DuckTest {
public static void main(String[] args) {
MallardDuck md = new MallardDuck();
md.display();
md.swim();
md.quack();
md.fly();
WoodDuck wd = new WoodDuck();
wd.swim();
wd.display();
}</span>
}
不错,这样是解决了刚才的问题,可以有个问题,那么就是代码的复用性,假设我们有100个鸭子,都有同样的飞行行为,那么这同样的代码我们要在每个子类中写一遍,相当于100遍,即便粘贴复制,也要好久吧.如果要修改的话就更麻烦了。所以从代码复用的角度来看,似乎又不太可行。设计中有一个原则就是:找出应用中可能需要变化的地方,将它们独立出来,与那些不需要变化的代码分离。
从上面的程序分析可知,需要变化的地方是飞行和呱呱叫的行为,因为不同的鸭子具有不同的飞行行为或者是否会呱呱叫。所以根据设计原则,我们应该将他们抽取出来,建立一组新的类来代表每个行为。这里可能有人会有疑问,行为怎么可以成为一个类呢?因为飞行也会有属性,比如飞行的速度,飞行的高度等。或者我们将这个行为类想象成一个技能学习中心,父类中的行为是从这里学习得来的,子类也是。但如果我们要想在鸭子子类中可以动态的去改变飞行的行为,该怎么做?有这样的一个设计原则:针对接口编程,而不是针对实现编程。所以我们用FlyBehavior和QuackBehavior接口来代表每个行为,然后行为的每个实现类都去实现其中的接口。这种做法跟以往不同,我们以往做法是行为来自Duck超类的具体实现,或是继承某个接口并由子去实现,这都是依赖于“实现”,这种做法不灵活。现在我们来看代码实现:
<span style="font-family:Courier New;font-size:14px;">/*
* 鸭子的抽象类
*/
public abstract class Duck {
//行为接口的变量
QuackBehavior quackBehavior;
FlyBehavior flyBehavior;
//鸭子外观的抽象方法
public abstract void display();
public void performFly() {
//委托给行为类
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("游啊游..");
}
}
/*
* 飞行的接口
*/
interface FlyBehavior{
public void fly();
}
//飞行的具体实现类
class FlyWithWing implements FlyBehavior {
public void fly() {
System.out.println("我会飞哦");
};
}
class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("我不会飞啊");
}
}
/*
* 呱呱叫行为的接口
*/
interface QuackBehavior {
public void quack();
}
class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("我不会叫啊");
}
}
/*
* 外观是绿头的鸭子
*/
class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWing();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("我是绿头鸭哦");
}
}
/*
* 木头鸭 不会飞也不会叫 外观是木头样的颜色 假定因为可以在水上漂 就相当于会游泳
*/
class WoodDuck extends Duck{
public WoodDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new MuteQuack();
}
@Override
public void display() {
System.out.println("我的外观颜色是木般的颜色");
}
}
public class DuckTest {
public static void main(String[] args) {
//向上转型
Duck md = new MallardDuck();
md.display();
md.performFly();
md.performQuack();
Duck wd = new WoodDuck();
wd.display();
wd.performFly();
wd.performQuack();
}
}</span>
虽然代码量并没有减少,但是可扩展性可维护性是增加的。现在我们虽然在main方法里调用的是同一个方法,但是具体的结果却不同。我们还可以在Duck类里设定一个改变飞行行为或其他行为的方法,这样子类就可以动态的去设定行为了。
设计模式是一种思想,既然是思想上的东西,就需要我们不断的思考,领悟,在实践当中不断的加以运用与验证。虽然写了这篇博客,但并不代表我已经彻底掌握,后面还需要不断的理解、思考并将这些思想运用到实践当中去。
ps:如要转载,请注明出处。另推荐Head First 设计模式这本书,对于初学者来说是一本不错的书,博客中的例子也是引用书里面的。