OOAD之策略模式(1)

在引入策略模式之前。先用一个小项目解释为什么要有策略模式。这是一个模拟鸭子的小游戏。最开始需求为,所有的鸭子都会叫以及都会游泳且叫声游泳姿势都一样。因此我们用原始OO的思维去编写代码时很容易做到

public abstract class Duck{
  public void yuck(){
        System.out.println("嘎嘎叫");
    }

   public void swim(){
        Systrm.out.println("i am swim");
    }              

        public abstract void display();
}

也就是封装一个超类。然后设置两个子类通用的方法,叫和游泳。此时再编写程序需要的各种不同的鸭子子类。他们都能够叫和游泳。此时编写一个测试类。

public class Test{

   public static void main(String[] args){
       Duck gDuck = new GreenDuck();
       Duck rDuck  = new RedDuck();
       
       gDuck.display();
       gDuck.yuck();
       gDuck.swim();

       rDuck.display();
       rDuck.yuck();
       rDuck.swim();
    
    }  
}
public class GreenHeadDuck extends Duck{

   @override
       public void display(){
        System.out.println("绿头鸭");
    }
}    
public class RedHeadDuck extends Duck{
    
    @override
        public void display(){
        System.out.println("红头鸭");
    }
}    

测试类的结果为:绿头鸭   嘎嘎叫  i am swim    红头鸭  嘎嘎叫  i am swim。这个完全没有问题。好的,现在游戏上线,市场火热,新需求来了。鸭子要会飞。那我们继续用OO的思维去想,

那么可以给超类Duck中设置一个通用的fly方法。让所有的鸭子都会飞。让我们把Duck的代码改一下。改成下面的

public abstract class Duck{
  public void yuck(){
        System.out.println("嘎嘎叫");
    }

   public void swim(){
        Systrm.out.println("i am swim");
    }              

        public abstract void display();

     public void fly(){
      System.out.println("i am flying");
    }
}

好的,现在的确实现了鸭子会飞的功能,但是这也引起了一些问题。因为事实上不是所有的鸭子都会飞。你在超类中定义了方法。影响的是全部的子类。容易导致溢出的问题,也就是。有些子类不需要。但是你给它强制加了。好,那如果现在有些子类不会飞。我们继续用OO的思维去解决。那么可以在不会飞的子类中用重写去覆盖掉父类给他的方法。我们加入绿头鸭不会飞,我们修改以下绿头鸭的代码如下

public class GreenHeadDuck extends Duck{

   @override
       public void display(){
        System.out.println("绿头鸭");
    }

    @override
       public void fly(){
       System.out.println("i can't fly")
    }
}    

好,可以通过在子类中覆盖来实现有些鸭子会费飞有些不会飞这个需求。但是在我们这个例子中鸭子种类少,这么玩没有关系。但如果由2000种鸭子呢。难道你要一个一个的去覆盖子类中的方法。

很明显就能看出单纯的面向对象,也就是OO的思想在设计这个需求的时候并不完善。那也有些人说,那我不在超类Duck中定义fly的方法。我在子类中去定义,谁会飞我就给谁定义。这其实和上面一样。如果子类基数大。那么你写的重复代码也会很多。那么OO中的代码复用就完全没有体现出来了。

因此,我们分析以下上面OO设计的问题所在。为什么会说这个设计不好。原因是在于它的行为和客户类没有分离。也就是“干什么” 和 “怎么干”没有分离开来。例如上面的例子。客户类中的fly是干什么,但是怎么干,fly里面怎么定义代码,他们没有分离。全部堆在客户类中,耦合度太高。导致,你的“怎么干“放在客户类中定义,无论你放在超类还是子类。都容易出问题,因此就导出了策略模式这个概念

策略模式是干嘛的,是去耦合的。它所做的就是把”干什么“和”怎么干“分离开来。把”怎么干“封装到一个策略接口或者抽象类中。然后编写一系列的怎么干算法封装起来。客户类中只需要调用这个算法就ok了

策略模式的两种标准点的定义:

图视:

    环境对象(Context):该类中实现了对抽象策略中定义的接口或者抽象类的引用。
      抽象策略对象(Strategy):它可由接口或抽象类来实现。
      具体策略对象(ConcreteStrategy):它封装了实现同不功能的不同算法。

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化。

使这些算法在客户端调用它们的时候能够互不影响地变化。


策略模式:分别封装行为接口,实现算法族,超类中放行为接口对象。在子类中的构造方法中去实例化行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能族,让行为算法的变化独立于算法的使用者。

使用策略模式的步骤:

1.分析变化的行为,拿出来进行封装成一个策略接口或者抽象类。然后定义该行为的多种实现算法。形成算法族

2.在客户超类中放策略接口。

3.在子类的构造方法中去具体实例化具体的策略算法

 


下面让我们看看用策略模式修改模拟鸭子的这个项目。

1.封装变化的部分。当前鸭子的变化行为为fly。有些会飞,有些不会飞。有些会飞的好,有些飞的不好。因此封装fly这个行为为一个策略接口。FlyObject

public interface FlyObject{
   void fly();
}

接下来,编写项目中实现fly这个行为的多种算法。例如不会飞 nofly,飞的好,good fly,飞得不好 badfly。这三个行为族

public class NoFly implements FlyObject{
    
   @override
    public void fly(){
       System.out.println("不会飞")
    }
}
public class GoodFly implements FlyObject{

    @override
       public void fly(){
        System.out.println("Good fly");
    }
}            
public class BadFly implements FlyObject{

   @override
     public void fly(){
       System.out.println("bad fly");
    }
}




之后在客户类的超类也就是Duck中放置策略接口,也就是FlyObject。

package com.lyh.strategy.duck;

import com.lyh.strategy.flybehavior.FlyBehavior;
import com.lyh.strategy.yuckbehavior.YuckBehavior;

public abstract class Duck {

    FlyBehavior flybehavior;
    YuckBehavior yuckbehavior;
    public Duck() {
        
    };
    
    public void fly() {
        flybehavior.fly();
    }
    
    public void yuck() {
        yuckbehavior.yuck();
    }
    
    public abstract void display();
    
    public void setFlyBehavior(FlyBehavior fb) {
        this.flybehavior = fb;
    }
    
    public void setYuckBehavior(YuckBehavior yb) {
        this.yuckbehavior = yb;
    }
    
    //这个方法子类通用,不会变化
    public void swim() {
        System.out.println("i am swim");
    }
}


在客户类的子类中去实例化具体的策略接口算法。

package com.lyh.strategy.duck;

import com.lyh.strategy.flybehavior.GoodFlyBehavior;
import com.lyh.strategy.yuckbehavior.GaGaYuckBehavior;

public class GreenHeadDuck extends Duck {

    public GreenHeadDuck() {
        flybehavior = new GoodFlyBehavior();
        yuckbehavior = new GaGaYuckBehavior();
    }
    @Override
    public void display() {
        System.out.println("绿头鸭");
    }

}

package com.lyh.strategy.duck;

import com.lyh.strategy.flybehavior.NoFlyBehavior;
import com.lyh.strategy.yuckbehavior.NoYuckBehavior;

public class RedHeadDuck extends Duck {

    
    public RedHeadDuck() {
        flybehavior = new NoFlyBehavior();
        yuckbehavior = new NoYuckBehavior();
    }
    @Override
    public void display() {
        System.out.println("红头鸭");
    }

    
}
因此,通过这种方式,能够实现客户类中的行为和客户类相分离。把变化的行为独立出来。抽象成接口。这样如果有新的行为需求,可以直接在行为族中加入。如果有新的客户类对象。也可以让他们
具体选择的去实现行为族。达到降低耦合度的目的。


但是,策略模式也有缺点。也就是客户类必须了解策略行为族中的每一个算法的实现以及原理。只有全部知晓。客户类才能去区别调用他们而不会出错。这时策略模式必须要做到的一点。

我们什么时候使用策略模式:这个没有具体的需求。一般而言。如果你定义的抽象父类中的一些方法是可变的。子类中不一定需要这些方法的时候。建议用策略模式将变化的行为封装起来。这其实也是一种方法的封装。你将行为封装,那么其他的类也可以去使用。

原文地址:https://www.cnblogs.com/exceptionblog/p/8372941.html