java设计模式之 策略模式

策略模式

定义

定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

似乎不太好理解

这里我们通过一个例子来讲解 策略模式

如果有这样一个需求

你的公司要求你设计一款鸭子游戏,游戏中鸭子会呈现各种形态,有的会游泳,有的会叫,有的会飞

怎么设计 ?

你可能这样

//使用标准的 oo 设计
//1.设计一个鸭子超类 Duck 类,并让各种鸭子继承这个超类
class Duck{
    quack();
    swim();
    display();
    //鸭子的其他方法
}

//2.继承
class MallardDuck{
    display(){
        //绿头鸭
    }
}

class RedheadDuck{
    display(){
        //红头鸭
    }
}

那么恭喜你 , 你的 java 已经入门了

新需求

游戏需要会飞的鸭子

这时你会怎么做呢?

解决方案

在鸭子超类上添加抽象方法 fly();,每个子类继承

可怕的事情发生了 , 测试的时候发现有一只橡皮鸭子在屏幕上飞来飞去 , 可是并非所有的鸭子都会飞

当涉及 维护 时,为了复用[reuse] 而使用继承,并不是一个可行的解决方案

当然你用继承也能实现 , 比如

//把超类的fly()方法覆盖掉
class RubberDuck{
    quack(){//吱吱叫};
    display(//橡皮鸭);
    fly(){
        //覆盖,什么都不做
    }
}
新的问题

新鸭子的需求提上日程 , 诱饵鸭 , 这是一种不会飞 , 也不会叫的鸭子

当然 , 问题仍然可以解决

class DecoyDuck{
    quack(){
        //覆盖,什么事都不做
    }
    display(){
        //诱饵鸭
    }
    fly(){
        //覆盖,什么都不做
    }
}

看到这里 , 想必你已经知道了这种设计的缺点 ,

  • 你的扩展伴随着大量的冗余代码
  • 你的扩展会影响其他功能
实现 implement

当发现继承不能很好的解决问题的时候 , 你会想到利用接口吗 ?

或许可以这样 ?

  1. fly()取出来 , 放进一个Flyable接口中;
  2. 同理,也可设计Quackable接口,因为并不是所有的鸭子都会飞,也不是所有的鸭子都会叫
  3. 只有拥有对应能力的鸭子才去实现对应的接口

仔细想想 , 这其实是一个超极笨的注意 , 如果 , 每个Duck的子类都要稍微修改下飞行的行为 , 怎么办!!!

我们知道

  • 并非「所有」的子类都具有飞行和呱呱叫的行为,所以继承 并不是适当的解决方式
  • Flyable与Quackable可以解决「一部分」 的问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用,
  • 这只能 算是从一个恶梦跳进另一个恶梦。甚至,在会飞的鸭子中,飞行的动作 可能还有多种变化…
重新设计
把问题归零
  • 现在我们知道使用继承有一些缺失,因为改变鸭子的行为会 影响所有种类的鸭子,而这并不恰当。
  • Flyable与Quackable  接口一开始似乎还挺不错,解决了问题(只有会飞的鸭子才 继承Flyable),
  • 但是Java的接口不具有实现代码,所以继承接口无法达到代码的复用。这意味着:无论何时你需要修改 某个行为,你必须得往下追踪并修改每一个定义此行为的类,一不小心,可能造成新的错误。

幸运地,有一个设计原则,正适用于此状况

分开会变的和不会变的部分

如何开始?

1.就我们目前所知,除了fly()和quack()的问题之外,Duck类还算一切正常,所以,我们不打算对Duck类做 太多处理。

2.为了要分开「变化和不会变化的部分」,我们准备建立两组类(完全远离Duck类), 一个是「fly」相关的,一个是「quack」相关的,每一组类将实现各自的动作

3.比方说,我 们可能有一个类实现「呱呱叫」,另一个类实现「吱吱叫」,另一个类实现「安静」。

4.我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。
为了要把这两个行为从Duck类中分开,我们将把它们自Duck类中 取出,建立一组新类代表每个行为

动态的改变鸭子的行为

我们希望一切能有弹性

毕竟,正是因为一开始的鸭子行为没有弹性,才让我们走上现在这条路。

我们还想能够「指定」行为到鸭子的实例,比方说,想要产生绿头鸭实例,并指定特定 「类型」的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,就可以在「运行时」动态地「改变」绿头鸭的飞行行为

为了达到这个目标,引入一个很重要的概念

面向接口编程 , 而不是面向实现编程

面向接口编程 , 而不是面向实现编程

面向接口编程 , 而不是面向实现编程

// 1.利用接口代表行为  比方说,FlyBehavior 与 QuackBehavior,而行为的每个实现都必须实现这些接口之一。

// 2. 所以这次鸭子类不会负责实现Flying与Quacking接口,反而是 由其他类专门实现FlyBehavior与QuackBehavior,这就称为 「行为」类。由行为类实现行为接口,而不是由Duck类实现行为 接口。

跟前面不一样的是 : 前面的两种做法都是依赖实现 , 我们被实现捆绑的死死的,没办法灵活的扩展代码,除非写更多的重复代码

在新的设计中 , 鸭子的子类将使用接口来代表行为,而实现不会绑定在鸭子的子类中,他们会在实现FlyBehaviorQuackBehavior的特定的实现类中

在各种设计模式中或多或少的会有 多态的参与

多态

看一下下面这段代码

Abstract class Animal{
    makeSound();
}
class Dog extends Animal{
    makeSound(){
        bark();
    }
    bark(){
        // 汪汪
    }
}
class Cat extends Animal{
    makeSound(){
        meow();
    }
    meow(){
        // 喵喵
    }
}

前面所说的面向接口编程 , 并不是狭义的接口 interface,可以理解为面向超类型编程
比如对于实际调用我们可以这样

//面向实现编程
Dog d = new Dog(); 
d.bark();

//面向接口编程
Animal animal = new Dog(); 
animal.makeSound();

这样做的好处是什么呢

  • 子类型实例化的动作不再需要在代码中硬编码,而是在运行时指定
  • 我们不需要知道子类具体的实现,我们只关心如何正确的调用就 ok 了

回到鸭子的问题

实现鸭子的行为
//我们会有两个接口
interface FlyBehavior{
    fly();
}
interface QuackBehavior{
    quack();
}
// 行为具体的实现
class FlyWithWings implements FlyBehavior{
    fly(){
        //实现鸭子飞
    }
}
class FlyNoWay implements FlyBehavior{
    fly(){
        //飞不了
    }
}
class Quack implements QuackBehavior{
    quack(){
        //鸭子呱呱叫
    }
}
class Squeak implements QuackBehavior{
    quack(){
        //鸭子吱吱叫
    }
}
class MuteQuack implements QuackBehavior{
    quack(){
        //不会叫
    }
}

这样的设计, 可以让飞行和叫的动作被其他对象复用,因为这些行为和鸭子没关系了

我们甚至可以扩展一些行为,不会影响到现有的类,也不会影响到已经使用到飞行行为的鸭子

这样一来,有了继承复用的好处,同时避免了继承的问题

所以如果还有的新需求呢

新的需求

需要加上一个火箭飞行的动作到这个游戏中, 怎么做,基本不用多说

聚合优于实现

接下来对这个案例进行整合

  • 在鸭子中加入两个接口类型的实例变量,该变量会在运行时指定对应正确的行为类型
  • 以前的没用的方法移除掉
  • 我们用performFly()和performQuack()取代Duck类中的fly()与quack()
class Duck{
// 接口类型变量,在运行时持有特定行为的引用
    FlyBehavior  flyBehavior ;
    QuackBehavior  quackBehavior
    performQuack();
    swim();
    display();
    performFly(); 
}

再比如我们实现quack

public class Duck { 
//每 只 鸭 子 都 会 引 用 实 现 QuackBehavior接口的对象
    QuackBehavior quackBehavior;   
    // 还有更多   
    public void performQuack() {
    
    //不亲自处理呱呱叫行为,而是委托 给quackBehavior对象。
        quackBehavior.quack();   
    } 
}
重构一开始的需求

Duck 基类

package org.apdoer.strategy.model;

import org.apdoer.strategy.behavior.FlyBehavior;
import org.apdoer.strategy.behavior.QuackBehavior;

/**
 * @author apdoer
 * @version 1.0
 * @date 2019/10/8 15:52
 */
public abstract class Duck {
    /**
     * 为行为接口类型声明两个 引用变量,所有鸭子子类 (在同一个  package)都 继承它们。
     */
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    public Duck() {

    }
    public abstract void display();
    public void performFly() {
        //委托给行为类
        flyBehavior.fly();
    }
    public void performQuack() {
        //委托给行为类
        quackBehavior.quack();
    }
    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}

MallardDuck

package org.apdoer.strategy.model;

import org.apdoer.strategy.behavior.impl.FlyWithWings;
import org.apdoer.strategy.behavior.impl.Quack;

public class MallardDuck extends Duck {
   public MallardDuck() {
      quackBehavior = new Quack();
      flyBehavior = new FlyWithWings();
   }
   @Override
   public void display() {
      System.out.println("I’m a real Mallard duck");
   }
}

两个行为接口

package org.apdoer.strategy.behavior;

public interface FlyBehavior {
     void fly();
}


package org.apdoer.strategy.behavior;

public interface QuackBehavior {
     void quack();
}

对应的实现类

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I can’t fly");
    }
}

public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I’m flying!!");
    }
}

public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("<< Silence >>");
    }
}

public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Quack");
    }
}

public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Squeak”");
    }
}

测试类

/**
 * @author apdoer
 * @version 1.0
 * @date 2019/10/8 16:01
 */
public class Test {
    public static void main(String[] args) {

        Duck mallard = new MallardDuck();

        mallard.performQuack();

        mallard.performFly();
    }
}

在这里插入图片描述
可以看到,能够实现我们想要的结果

动态的设定行为

可以看到,刚才的例子中定义了一堆动态功能却没有使用到

现在我们来解决这个

在Duck 类中加入 setter方法

public void setFlyBehavior(FlyBehavior fb) {    
    flyBehavior = fb; 
} 

public void setQuackBehavior(QuackBehavior qb) {    
    quackBehavior = qb; 
}

创建一个 ModelDuck

package org.apdoer.strategy.model;

import org.apdoer.strategy.behavior.impl.FlyNoWay;
import org.apdoer.strategy.behavior.impl.Quack;

public class ModelDuck extends Duck {       
    public ModelDuck() {       
        flyBehavior = new FlyNoWay(); 
        quackBehavior = new Quack();
    }   
    @Override
    public void display() {       
        System.out.println("I’m a model duck");    
    } 
}

创建一个新的行为类型

package org.apdoer.strategy.behavior.impl;

import org.apdoer.strategy.behavior.FlyBehavior;

public class FlyRocketPowered implements FlyBehavior {    
    @Override
    public void fly() {       
        System.out.println("I’m flying with a rocket!");
    } 
}

在测试类中添加如下测试代码

public class Test {
    public static void main(String[] args) {
        Duck model = new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}

在这里插入图片描述
可以看到,动态改变改变行为,不是问题

封装行为的大局观

我们重构后的设计

  • 鸭子继承Duck, 飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口
  • 我们描述事情的方式也稍有改变。不再把鸭子的行为说成「一组行为」,我们开始把行为想成是「一族算法 ]
  • has-a 和 is-a 的区别

有一个 可能比 是一个更好

行为不是通过是xxx来继承 , 而是通过有xxx来使用

多用聚合 , 少用继承

如果你看到了这里 , 那么恭喜你,你大概对策略模式有了基本的了解

定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

关于设计模式,相关代码已经上传到github 点击跳转

欢迎大家加入qq群859759121,大量免费vip学习资源,一起成长,一起进步

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值