乐趣区

关于设计模式:设计模式学习笔记二十四策略模式

1 概述

1.1 引言

在外出游览时,很多时候的出行形式都不止一条,通常依据理论状况,比方目的地,估算,游览工夫等确定最适宜的出行形式。在软件开发中,也经常会遇到相似的状况,实现某一个性能有多种路径,每一条路径对应一个算法,这时能够应用一种叫做策略模式的设计模式来进行设计。在策略模式中,能够定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法。

这里 每一个封装的算法能够被称之为一种策略,为了保障这些策略在应用时具备一致性,个别会提供一个形象的策略类作为规定的定义,每种具体算法对应于一个具体策略类。

策略模式的次要目标是将算法的定义与应用离开,也就是将算法的行为和环境离开,将算法的定义放在专门的策略类中,每一个策略类封装了一种实现算法,应用算法的环境类针对形象策略类进行编程,合乎 DIP(依赖倒转准则)。呈现新算法时只须要定义一个新的具体策略类即可。

1.2 定义

策略模式:定义一系列算法类,将每一个算法封装起来,并让他们能够互相替换。

策略模式也叫政策模式,是一种对象行为型模式。

1.3 结构图

1.4 角色

  • Context(环境类):应用算法的角色,解决了某个问题时能够采纳的多种策略,在环境类维持一个形象策略类的援用实例,用于定义所采纳的策略
  • Strategy(形象策略类):为反对的算法申明了形象办法,是所有策略类的父类,能够是抽象类或具体类,也能够是接口,环境类通过形象策略类中申明的办法在运行时调用具体策略类中实现的算法
  • ConcreteStrategy(具体策略类):实现了形象策略类中申明的算法,在运行时,具体策略类将笼罩在环境类中定义的形象策略类对象,应用一种具体的算法实现某个业务解决

2 典型实现

2.1 步骤

  • 定义形象策略类:个别实现为接口,申明形象算法
  • 定义具体策略类:实现形象策略类,实现其中的具体算法
  • 定义环境类:维持一个对形象策略类的援用,通过 setter 或构造函数注入具体策略类,调用时通过该形象援用调用相应算法

2.2 形象策略类

interface AbstarctStrategy
{void algorithm();
}

这里定义为一个接口,只有一个形象算法办法。

2.3 具体策略类

class ConcreteStrategy1 implements AbstarctStrategy
{
    @Override
    public void algorithm()
    {System.out.println("具体策略 1");
    }
}

class ConcreteStrategy2 implements AbstarctStrategy
{
    @Override
    public void algorithm()
    {System.out.println("具体策略 2");
    }
}

定义两个具体策略类,别离示意不同的算法。

2.4 环境类

class Context
{
    private AbstarctStrategy strategy;
    public void setStrategy(AbstarctStrategy strategy)
    {this.strategy = strategy;}

    public void algorithm()
    {strategy.algorithm();
    }
}

通过 setter 注入具体策略类,在调用环境类的办法时通过形象策略类调用其中的具体策略类的算法。

2.5 客户端

public static void main(String[] args)
{Context context = new Context();
    context.setStrategy(new ConcreteStrategy1());
    context.algorithm();
    context.setStrategy(new ConcreteStrategy2());
    context.algorithm();}

3 实例

设计一个电影票打折零碎,有三种不同的打折形式:学生能够享受 8 折优惠,10 周岁以下儿童能够享受减免 10 元优惠,VIP 能够享受半价优惠,应用策略模式进行设计。

设计如下:

  • 环境类:MovieTicket
  • 形象策略类:Discount
  • 具体策略类:StudentDiscount+VIPDiscount+ChildrenDiscount

首先是形象策略类:

interface Discount
{double calculate(double price);
}

蕴含一个计算折扣的办法,接着是具体策略类:

class StudenDiscount implements Discount
{
    @Override
    public double calculate(double price)
    {System.out.println("学生票");
        return price * 0.8;
    }
}

class ChildrenDiscount implements Discount
{
    @Override
    public double calculate(double price)
    {System.out.println("儿童票");
        return price - 10.0;
    }
}

class VIPDiscount implements Discount
{
    @Override
    public double calculate(double price)
    {System.out.println("VIP 票");
        return price * 0.5;
    }
}

三个不同的具体策略类示意三种不同的计算折扣形式,依据须要返回对应的折扣价格。

最初是环境类:

class MovieTicket
{
    private Discount discount;
    private double originalPrice;

    public void setPrice(double price)
    {this.originalPrice = price;}

    public void setDiscount(Discount discount)
    {this.discount = discount;}

    public double getDicountPrice()
    {return discount.calculate(originalPrice);
    }
}

环境类通过 setPrice 设定电影票价格后,在通过 setDiscount 注入具体策略类,最初应用 getDiscountPrice 获取折扣后的价格。

测试:

public static void main(String[] args)
{MovieTicket movieTicket = new MovieTicket();
    movieTicket.setPrice(100.0);
    movieTicket.setDiscount(new StudenDiscount());
    System.out.println(movieTicket.getDicountPrice());
    movieTicket.setDiscount(new VIPDiscount());
    System.out.println(movieTicket.getDicountPrice());
    movieTicket.setDiscount(new ChildrenDiscount());
    System.out.println(movieTicket.getDicountPrice());
}

客户端须要明确晓得这三种折扣,也就是打折形式由客户端指定,输入如下:

4 JDK 中的策略模式

策略模式实用性强,扩展性好,是应用频率较高的设计模式,上面来看看 JDK 中的典型利用。

Java SE 容器布局管理器就是策略模式的一个经典利用案例,根本构造如下:

JavaSE 中用户须要对容器对象 Container 进行布局,在程序运行期间由客户端动静决定一个 Container 对象如何布局,Java 提供了几种不同的布局形式:BorderLayoutFlowLayoutGridLayoutGridBagLayoutCardLayout。在上图构造中:

  • Container充当了环境角色Context
  • LayoutManager充当了形象策略角色
  • LayoutManager的各个子类充当了具体策略类

Container针对 LayoutManager 进行编程,毋庸关怀具体布局是什么,这样的设计合乎里氏替换准则。

5 与状态模式的不同

状态模式与策略模式很像,上面是两者的结构图:


两者的结构图很类似,然而实际上也有很多的不同:

  • 用意不同:策略模式让客户端指定更换具体策略算法,而状态模式是状态在满足肯定条件主动切换,用户无奈手动设置状态
  • 负责范畴不同:状态模式负责不同状态下对象行为的解决,而策略模式负责具体算法或策略的解决,对于算法来说都有一个明确的指标,都是在做一件事件,比方下面的电影票打折例子,无论抉择何种策略,都是为了打折,然而状态模式在不同的状态下做的事件可能不同
  • 封装内容不同:状态模式封装了对象的状态,而策略模式封装了具体的算法或策略
  • 重用性不同:状态是跟对象密切相关的,不能重用,而策略模式的具体策略能够分离出来重用
  • Context的应用不同:状态模式中每个状态持有 Context 援用,实现状态切换,然而每个策略不持有 Context 援用,策略只是被 Context 应用
  • 客户端须要思考的状况不同:对于状态模式来说,状态模式依赖于其外部状态的变动时外部的行为发生变化,状态是零碎本身固有的,由零碎自身管制,状态对客户端不通明,客户端不须要思考零碎的状态,也不能间接指定或扭转零碎的状态切换。然而对于策略模式来说,客户端须要晓得所有的策略类,明确各种策略的利弊,对其进行衡量并抉择策略,也就是策略须要对客户端通明,须要由客户端思考应用何种策略

6 次要长处

  • 完满反对 OCP:策略模式提供了对开闭准则的完满反对,用户能够在不批改原有零碎的根底上抉择算法或者行为,也能够灵便提供新的算法或行为
  • 易于治理和复用算法:策略模式提供了治理相干的算法族的方法,策略类的等级构造定义了一个算法或行为族,失当应用继承能够把公共代码移到形象策略类中以简化代码,同时因为算法独自封装在具体策略类中,能够不便复用这些算法
  • 替换继承:策略模式提供了一种替换继承关系的办法,不应用策略模式的话环境类可能有子类,造成算法的应用和定义混在一起,而且应用继承的话无奈实现算法或行为在运行时动静切换
  • 防止多重 else if:多重选择语句不易保护,因为将抉择算法的逻辑以及算法本事实现逻辑混在一起,硬编码在一个微小的if/else if 中,应用策略模式能够防止这种构造

7 次要毛病

  • 策略类须要对客户端通明:客户端必须晓得所有的策略类,并自行决定哪一个策略类,也就是客户端须要了解这些算法的区别以便抉择适当的算法
  • 策略类数量多:策略模式会造成零碎产生很多具体策略类,任何细小的变动都会导致系统减少一个新的具体策略类
  • 客户端无奈应用多个策略类:客户端每次只能应用一个策略类,不反对应用一个策略类实现局部性能后再应用另一个策略类来实现剩下的性能

8 实用场景

  • 一个零碎须要动静在几种算法中抉择一种,这些算法类均有对立的接口
  • 一个对象有很多行为,应用策略模式能够将这些行为转移到相应具体策略类中
  • 不心愿客户端晓得简单的,与算法相干的数据结构,在具体策略类中对其进行封装

9 总结

如果感觉文章难看,欢送点赞。

同时欢送关注微信公众号:氷泠之路。

退出移动版