• 策略模式


    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:返回动物的种类,比如“狗”,“兔子”。这是一个抽象方法,子类要去实现。

    然后我们编写 DogPig 和 Rabbit

    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 中的 run 和 drinkWater,并且都实现了自己的 type 方法。

    现在我们想给 Pig 和 Rabbit 加入吃草的能力,最直接的办法是分别在这两个类中加入 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";
        }
    }
    

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

    为了解决代码复用,我们可以将 eatGrass 方法放到 Animal 中,利用继承的特性,Pig 和 Rabbit 中就不需要编写 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 接口,但是不用动物类来实现该接口,而是我们创建了一些行为类 EatGreenGrass ,EatDogtailGrass 和 EatNoGrass,这些行为类实现了 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 接口与三个行为类 EatGreenGrassEatDogtailGrass 和 EatNoGrass。在策略模式中,这些行为类被称为算法族,所谓的“策略”,可以理解为“算法”,这些算法可以互相替换。

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

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

    在这里插入图片描述

    5,继承与组合

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

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

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

    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,总结

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

  • 相关阅读:
    JSONObject处理java.util.Date
    JSON lib 里JsonConfig详解
    Android编程获取手机的IMEI
    Toast用法
    JMM内存管理
    Users is not mapped(Hibernate实体类采用注解)
    指针小结(不定期更新)
    这个博客几乎不用了,转到csdn
    2013暑期在家(1)
    用户空间与内核空间,进程上下文与中断上下文[总结]
  • 原文地址:https://www.cnblogs.com/ma13461749958/p/14247432.html
Copyright © 2020-2023  润新知