设计模式之策略模式(面向接口编程)

面向实现编程的弊端

举一个简单的动物界的例子,我们需要写一段代码,包含以下要素:鸭子类,鸭子的若干子类,实现一些方法。
惯用的编程思路
1、写一个鸭子类Duck,写几个方法,比如鸭子飞行的方法fly(),鸭子叫的方法quack()。
2、写鸭子的子类,比如绿头鸭MallardDuck,继承Duck类。
3、在子类中重写以上两个方法。
这种编程思路的弊端
1、代码冗杂
在代码量少且代码稳定不变更的情况下似乎不会有什么问题,在实际运行程序时,也是完全不会出错的。但是,假设我们把鸭子的数量增加,增加到一万种鸭子类,我们是不是要重写一万次方法呢?显然这是很费事费力的。另外,假设我们要调用一万次fly()方法,是不是要写一万次调用的程序呢?即:对象名.fly() 。这也会导致代码的冗杂。
2、变更性弱
假设当你下定决心去写那一万种鸭子类,并且快要写完的时候,老板和你说,这个fly()方法不应该这样写,需要改一下,你是不是就要崩溃了,代码几乎是死的,没法变更!
简单分析:这种类型的代码属于硬代码,不仅写的时候代码量繁杂,而且写完后,几乎是没法动的,自然不能适应现在软件需要不断更新版本的要求,需要我们转变编程思路。

面向接口编程的引入

我们一个个地解决这些问题。
1、解决代码冗杂的问题:减少代码量的最好方式就是相同代码的复用,假设一万种鸭子中,有一部分鸭子的fly()方法(行为)是一致的,那么我们可以试着去减少它重复的书写。fly()既需要满足被多处复用的要求,又需要满足可以被特殊地实现的要求,于是我们可以把这种飞行的行为单独地拿出来,使它脱离鸭子类,写一个接口flyBehavior,让fly()方法成为它的成员方法,fly()方法在这里是一种抽象方法,实例化必须改写内容。然后,我们可以写若干子类继承flyBehavior接口,实现行为方法fly(),然后在需要用的时候实例化调用就可以了。似乎这还没有解决一万次调用方法的代码冗杂问题,一万个对象,一万个对象名.fly()肯定是不现实的,我们希望的duck.fly()。解决的方法是在创建对象的时候让子类对象向上转型为父类,通过父类的引用去调用被子类重写后的方法,那么这个问题也解决了。
2、解决变更性弱的问题:这个问题其实已经被上述方法解决,我们修改fly()方法的内容,只要在相应的行为类中修改就可以了,操作起来很方便。其他行为如quack()行为也可以作为接口。除此之外,我们还可以做一些别的事情,比如实现鸭子飞行行为的动态更改,只要在鸭子类中写一个set方法,修改flyBehavior的引用,使得flyBehavior调用不同的fly()方法,实现在程序运行中多态修改飞行参数以及飞行姿态。
小结:以上就体现了面向接口编程的特点:
1、反复被使用的行为被抽象为接口,利用接口的多实现使得行为多实现。
2、行为的反复重写变为了对已经实现的行为的多次调用。
3、父类对象调用子类方法体现了自动转型的思想。
4、实现了多态。

面向接口编程实践

1、Duck类

public abstract class Duck {
	 FlyBehavior flyBehavior;
	 QuackBehavior quackBehavior;	 
	 /**
	  * 鸭子的外观
	  */
	 public abstract void display();	 
	 /**
	  * 鸭子的身份
	  */
	 public void isDuck()
	 {
		 System.out.println("我是鸭子");
	 }	 
	 /**
	  * 使用飞行的算法
	  */
	 public void flyPerfrom()
	 {
		 flyBehavior.fly();
	 }	
	 /**
	  * 使用叫的算法
	  */
	 public void quackPerform()
	 {
		 quackBehavior.quack();
	 }
		 /**
	  * 设置飞行的算法
	  * @param flyBehavior
	  */
	 public void setFlyBehavior(FlyBehavior flyBehavior)
	 {
		 this.flyBehavior=flyBehavior;
	 }	
	 /**
	  * 设置叫的算法
	  * @param quackBehavior
	  */
	 public void setQuackPerform(QuackBehavior quackBehavior)
	 {
		 this.quackBehavior=quackBehavior;
	 }	 
}

2、绿头鸭子类

public class MallardDuck extends Duck{
	/**
	 * 鸭子外观方法
	 * 重写的抽象方法
	 */
	@Override
	public void display() {		
		System.out.println("一只绿头鸭");		
	}
	/**
	 * 构造方法
	 * 实现动物行为的接口
	 */
	public MallardDuck()
	{
		flyBehavior=new FlyWithWing();
		quackBehavior=new Quack();
	}	
}

3、飞行行为接口

public interface FlyBehavior {
	public abstract void fly();	
}

4、鸭叫行为接口

public interface QuackBehavior {
    public abstract void quack();
}

5、两种飞行行为的实现类

public class FlyWithWing implements FlyBehavior{
	@Override
	public void fly() {		
		System.out.println("用翅膀飞行");		
	}
}
public class FlyNoWing implements FlyBehavior{
	@Override
	public void fly() {		
		System.out.println("没有翅膀,飞不起来");		
	}			
}

6、一种鸭叫行为的实现类

public class Quack implements QuackBehavior{
	@Override
	public void quack() {	
		System.out.println("绿头鸭的叫声");		
	}	
}

7、测试类

	public static void main(String[] args)
	{
		Duck duck = new MallardDuck();
		duck.flyPerfrom();//执行飞行的算法
		duck.quackPerform(); //执行鸭叫的算法
		duck.display();  //外观展示方法
		duck.isDuck();  //鸭子的类别判断方法		
		duck.setFlyBehavior(new FlyNoWing());//设飞行算法
		duck.flyPerfrom(); //执行飞行算法
	}

8、运行结果
用翅膀飞行
绿头鸭的叫声
一只绿头鸭
我是鸭子
没有翅膀,飞不起来
9、分析
我对三种不同的方法有不同的处理:对于行为方法,让其变为接口;对于鸭子的通用方法,比如上述鸭子身份的判断,在鸭子类中直接具体化,子类继承拿来直接用;对于每个鸭子的不同属性,比如外观display(),作为抽象方法,每个子类都需要实现。行为方法的接口化,实现了面向接口编程,另外,在鸭子类中,先申明了接口的对象,然后在perform方法写接口对象对抽象方法的调用,在实例化鸭子对象前,这些引用并不指向任何具体内容,实例化后,接口对象指向了具体被实现的抽象方法,便可以调用。案例中,我们实现了在程序运行中对鸭子飞行行为的修改,这种思想可以类比于我们在玩一个射击类游戏中切换武器,不同的武器就可以定义在一个算法族中,共同实现了武器接口。

策略模式的几项原则

以上我们学习的,其实就是设计模式中的策略模式,我们已经学会了使用设计模式,接下来,我给大家总结一下策略模式涉及到的几项OO原则吧:
1、找出应用中那些需要经常变化的部分,把它们独立出来“封装”,避免和那些不需要变化的代码混杂。
2、针对接口编程而不是针对实现编程。
3、多用组合,少用继承。
策略模式定义了算法族(我们把行为改称为算法,算法族即那些被封装的行为的统称),分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

你可能感兴趣的:(设计模式)