策略模式-定义一个算法族

编程之家收集整理的这篇文章主要介绍了策略模式-定义一个算法族编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

公号:码农充电站pro
主页:https://codeshellme.github.io

本篇来介绍策略模式(Strategy Design Pattern)。

假设我们要为动物进行建模,比如狗,猪,兔子等,每种动物的能力是不同的。

1,使用继承

首先你可能想到用继承的方式来实现,所以我们编写了下面这个 Animal 类:

abstract class Animal {
    public void run() {
        System.out.println("I can run.");
    }

    public void drinkWater() {
        System.out.println("I can drink water.");
    }

    protected abstract String type();
}

Animal一个抽象类,其中包括了动物的能力,每种能力用一个方法表示:

  • run:奔跑能力。
  • drinkWater:喝水能力。
  • type:返回动物的种类,比如“狗”,“兔子”。这是一个抽象方法,子类要去实现。

然后我们编写 DogPigRabbit

class Dog extends Animal {
    public String type() {
        return "Dog";
    }
}

class Pig extends Animal {
    public String type() {
        return "Pig";
    }
}

class Rabbit extends Animal {
    public String type() {
        return "Rabbit";
    }
}

上面的三种动物都继承了 Animal 中的 rundrinkWater,并且都实现了自己的 type 方法

现在我们想给 PigRabbit 加入吃草的能力,最直接的办法是分别在这两个类中加入 eatGrass 方法,如下:

class Pig extends Animal {
    public void eatGrass() {
        System.out.println("I can eat grass.");
    }

    public String type() {
        return "Pig";
    }
}

class Rabbit extends Animal {
    public void eatGrass() {
        System.out.println("I can eat grass.");
    }

    public String type() {
        return "Rabbit";
    }
}

上面代码能够达到目的,但是不够好,因为PigRabbit 中的 eatGrass 一模一样,是重复代码代码没能复用。

为了解决代码复用,我们可以将 eatGrass 方法放到 Animal 中,利用继承的特性,PigRabbit 中就不需要编写 eatGrass 方法,而直接从 Animal 中继承就行。

但是,这样还是有问题,因为如果将 eatGrass 放在 Animal 中,Dog 中也会有 eatGrass ,而我们并不想让 Dog 拥有吃草的能力。

也许你会说,我们可以在 Dog 中将 eatGrass 覆盖重写,让 eatGrass 不具有实际的能力,就像这样:

class Dog extends Animal {
    public void eatGrass() {
        // 什么都不写,就没有了吃草的能力
    }

    public String type() {
        return "Rabbit";
    }
}

这样做虽然能到达目的,但是并不优雅。如果 Animal 的子类特别多的话,就会有很多子类都得这样覆盖 eatGrass 方法

所以,将 eatGrass 放在 Animal 中也不是一个好的方案。

2,使用接口

那是否可以将 eatGrass 方法提取出来,作为一个接口?

就像这样:

interface EatGrassable {
    void eatGrass();
}

然后,让需要有吃草能力的动物都去实现该接口,就像这样:

class Rabbit extends Animal implements EatGrassable {
    public void eatGrass() {
        System.out.println("I can eat grass.");
    }

    public String type() {
        return "Rabbit";
    }
}

这样做可以达到目的,但是,缺点是每个需要吃草能力的动物之间就会有重复的代码,依然没有达到代码复用的目的。

所以,这种方式还是不能很好的解决问题。

3,使用行为类

我们可以将吃草的能力看作一种“行为”,然后使用“行为类”来实现。那么需要有吃草能力的动物,就将吃草类的对象,作为自己的属性

这些行为类就像一个个的组件,哪些类需要某种功能的组件,就直接拿来用。

下面我们编写“吃草类”:

interface EatGrassable {
    void eatGrass();
}

class EatGreenGrass implements EatGrassable {
    // 吃绿草
    public void eatGrass() {
        System.out.println("I can eat green grass.");
    }
}

class EatDogtailGrass implements EatGrassable {
    // 吃狗尾草
    public void eatGrass() {
        System.out.println("I can eat dogtail grass.");
    }
}

class EatNoGrass implements EatGrassable {
    // 不是真的吃草
    public void eatGrass() {
        System.out.println("I can not eat grass.");
    }
}

首先创建了一个 EatGrassable 接口,但是不用动物类来实现该接口,而是我们创建了一些行为类 EatGreenGrassEatDogtailGrassEatNoGrass,这些行为类实现了 EatGrassable接口。

这样,需要吃草的动物,不但能够吃草,而且可以吃不同种类的草。

那么,该如何使用 EatGrassable 接口呢?需要将 EatGrassable 作为 Animal属性,如下:

abstract class Animal {

    // EatGrassable 对象作为 Animal 的属性
    protected EatGrassable eg;

    public Animal() {
        eg = null;
    }

    public void run() {
        System.out.println("I can run.");
    }

    public void drinkWater() {
        System.out.println("I can drink water.");
    }

    public void eatGrass() {
        if (eg != null) {
            eg.eatGrass();
        }
    }

    protected abstract String type();
}

可以看到,Animal增加eg 属性eatGrass 方法

其它动物类在构造函数中,要初始化 eg 属性

class Dog extends Animal {
    public Dog() {
        // Dog 不能吃草
        eg = new EatNoGrass();    
    }
    
    public String type() {
        return "Dog";
    }
}

class Pig extends Animal {
    public Pig() {
        eg = new EatGreenGrass();
    }
    
    public String type() {
        return "Pig";
    }
}

class Rabbit extends Animal {
    public Rabbit() {
        eg = new EatDogtailGrass();
    }
    
    public String type() {
        return "Rabbit";
    }
}

代码测试:

public class Strategy {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal pig = new Pig();
        Animal rabbit = new Rabbit();

        dog.eatGrass();    // I can not eat grass.
        pig.eatGrass();    // I can eat green grass.
        rabbit.eatGrass(); // I can eat dogtail grass.
    }
}

4,策略模式

实际上,上面的实现方式使用的就是策略模式。重点在于 EatGrassable 接口与三个行为类 EatGreenGrassEatDogtailGrassEatNoGrass。在策略模式中,这些行为类被称为算法族,所谓的“策略”,可以理解为“算法”,这些算法可以互相替换。

策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户

我将完整的代码放在了这里,供大家参考,类图如下:

在这里插入图片描述

5,继承与组合

在一开始的设计中,我们使用的是继承(Is-a) 的方式,但是效果并不是很好。

最终的方案使用了策略模式,它是一种组合(Has-a) 关系,即 AnimalEatGrassable间的关系。

这也是一种设计原则:多用组合,少用继承,组合关系比继承关系有更好的弹性。

6,动态设定行为

策略模式不仅重在创建一组算法(行为类),能够动态的让这些算法互相替换,也是策略模式典型应用。

所谓的“动态”是指,在程序的运行期间,根据配置,用户输入等方式,动态的设置算法。

只需要在 Animal 中加入 setter 方法即可,如下:

abstract class Animal {
    // 省略了其它代码
    
    public void setEatGrassable(EatGrassable eg) {
        this.eg = eg;
    }
}

使用 setter 方法

Animal pig = new Pig();
pig.eatGrass();	// I can eat green grass.

pig.setEatGrassable(new EatDogtailGrass()); // 设置新的算法
pig.eatGrass();	// I can eat dogtail grass.

本来 pig 吃的是绿草,我们通过 setter 方法绿草 换成了 狗尾草,可以看到,算法的切换非常方便。

7,总结

策略模式定义了一系列算法族,这些算法族也可以叫作行为类。策略模式使用了组合而非继承来构建类之间的关系,组合关系比继承关系更加有弹性,使用组合也比较容易动态的改变类的行为。

(本节完。)


推荐阅读:

设计模式之高质量代码

单例模式-让一个类只有一个实例

工厂模式-将对象的创建封装起来


欢迎关注作者公众号,获取更多技术干货。

码农充电站pro

总结

以上是编程之家为你收集整理的策略模式-定义一个算法族全部内容,希望文章能够帮你解决策略模式-定义一个算法族所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您喜欢寻找一群志同道合、互帮互助的学习伙伴,可以点击下方链接加入:
编程之家官方1群
编程之家官方2群
编程之家官方3群
编程之家官方4群

相关文章

猜你在找的设计模式相关文章

什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
微信公众号搜索 “ 程序精选 ” ,选择关注!
微信公众号搜 "程序精选"关注