java策略模式

示例:

在说策略模式之前,先看一个小例子:
假如:有一场演讲比赛,有十个评委对参赛选手的成绩进行打分,但最终要通过评委的平均分来决定选手的名次。现在有两种求平均分的策略:
第一种:将十名裁判的分加起来求平均值。
第二种:去掉最高分和最低分后求平均值。
面对以上的需求,我们可能会写出如下代码:

public class Averge {

    /**
     * @param a  每个裁判所打的分数
     * @param strategy 使用的策略
     * @return
     */
    public double getScore(double a[],String strategy){

        double score =0,sum = 0;

        //使用第一种策略打分
        if("策略一".equals(strategy)){
            System.out.println("使用策略1计算分数!");
            for (int i = 0; i < a.length; i++) {
                sum = sum + a[i];
            }
            score = sum/a.length;

            return score;           //返回平均值

        //使用第二种策略打分
        }else{
            System.out.println("使用策略2计算分数!");
            Arrays.sort(a);
            for (int i = 1; i < a.length - 1; i++) {
                sum = sum + a[i];
            }
            score = sum/(a.length - 2);

            return score;
        }
    }
}

以上代码工作的很好,完全没有问题。但是,上面的代码存在一些问题,比如:将所有的策略都放在了一个方法里面,显得方法非常臃肿(这里代码少可能看不出来)。此时开始修改代码,将每一种策略进行提取出来封装为方法method1,method2,然后在if else分支进行调用方法即可。代码如下:

public class Averge {

    /**
     * @param a  每个裁判所打的分数
     * @param strategy 使用的策略
     * @return
     */
    public double getScore(double a[],String strategy){

        //使用第一种策略打分
        if("策略一".equals(strategy)){

            System.out.println("使用策略1计算分数!");
            return method1(a);
        //使用第二种策略打分
        }else{

            System.out.println("使用策略2计算分数!");
            return method2(a);
        }
    }

    public double method1(double a[]){

        double score =0,sum = 0;
        for (int i = 0; i < a.length; i++) {
            sum = sum + a[i];
        }
        score = sum/a.length;
        return score;
    }

    public double method2(double a[]){

        double score =0,sum = 0;
        Arrays.sort(a);
        for (int i = 1; i < a.length - 1; i++) {
            sum = sum + a[i];
        }
        score = sum/(a.length - 2);
        return score;
    }
}

经过如上的改造,我们的方法显得不是那么的臃肿,但是,如果说此时我们又要扩展一种计算平均分的策略呢(比如:增加一种只去掉最高分然后求平均分的打分策略)?
此时我们首先需要在该类中增加一个求平均值的方法method3,然后编写方法实现,然后再增加 if–else 语句块。这样显得十分的麻烦,而且这样的设计也违背了设计原则之一的----开闭原则。

开闭原则:

对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。

对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。

以上的代码设计,不易扩展并且不易维护。所以我们需要一些解决办法来将我们的代码符合开闭原则,并且易扩展和维护。所以我们开始使用策略模式来解决我们的问题。

定义:

策略模式定义了一系列的算法,把他们一个个封装起来,并且使他们可相互替换。该模式使得算法可独立于使用它的客户端而变化。

结构:

策略模式结构中包含了三种角色:1.策略 2.上下文 3.具体策略

  1. 策略:策略是一个接口,该接口定义若干算法标识,即定义了若干个抽象方法。
  2. 上下文(context):上下文是依赖接口的类(是面向策略二设计的类),即上下文中包含了策略变量。上下文中提供一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法。
  3. 具体策略:具体策略是实现策略接口的类。具体策略实现策略接口所定义的抽象方法。
    以上的打分示例中,method1和method2就相当于一个个具体的策略!

使用策略模式

使用策略模式实现评委打分方法:
1.策略接口:

public interface Strategy {
    public double getAverge(double a[]);
}

2.具体策略实现:
a). 策略A:直接求平均分

public class StrategyA implements Strategy {

   @Override
   public double getAverge(double[] a) {
       
       double score =0,sum = 0;
       for (int i = 0; i < a.length; i++) {
           sum = sum + a[i];
       }
       score = sum/a.length;
       return score;
   }
}

b). 策略B:去掉最高分和最低分后求平均分

public class StrategyB implements Strategy{

    @Override
    public double getAverge(double[] a) {

        double score =0,sum = 0;
        Arrays.sort(a);
        for (int i = 1; i < a.length - 1; i++) {
            sum = sum + a[i];
        }
        score = sum/(a.length - 2);
        return score;
    }
}

3.策略上下文

/**
 * 策略上下文
 */
public class StrategyContext {

    //策略变量的引用
    private Strategy strategy;

    public void setStrategy(Strategy strategy){
        this.strategy = strategy;
    }

    public double getAverge(double a[]){
        if(strategy != null){
            //调用引用策略的方法
            double averge = strategy.getAverge(a);

            return averge;
        }else {
            System.out.println("没有求平均值的策略!");
            return -1;
        }
    }
}

4.客户端

public class Client {

    public static void main(String[] args) {

        //模拟评委打分
        double a[] = {1,6,8,4,5,7,4,7,8,9};

        //1.创建具体打分策略
        StrategyA strategyA = new StrategyA();

        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext strategyContext = new StrategyContext(strategyA);

        //3.调用上下文对象的方法来完成对具体策略实现的回调
        double averge = strategyContext.getAverge(a);
        System.out.println("策略1:平均分:" + averge );

        //======================================================================

        //使用第二种求平均分的策略:
        StrategyB strategyB = new StrategyB();
        strategyContext.setStrategy(strategyB);

        //调用上下文对象的方法来完成对具体策略实现的回调
        averge = strategyContext.getAverge(a);
        System.out.println("策略2:平均分:" + averge );

    }
}

以上就是策略模式的简单例子实现。在以上示例中我们需要在客户端手动的设置选择哪个策略模式进行打分,其实我们也可以不用手动设置使用哪一个策略,直接在上下文中创建要优先使用的策略,若优先使用的策略执行失败,则进行另一个策略的使用,这样便不需要客户进行手动的设置要使用的策略模式了。示例代码如下:

public class StrategyContext {

    //策略变量的引用
    private Strategy strategy;

    public double getAverge(double a[]){
        strategy = new StrategyA();
        try{
            //调用引用策略的方法
            double averge = strategy.getAverge(a);
            return averge;
        }catch (Exception e){
            //策略A失败
            strategy = new StrategyB();
            double averge = strategy.getAverge(a);
            return averge;
        }
    }
}

深入理解策略模式:

**策略模式的作用:**就是把具体的算法实现从业务逻辑中剥离出来,成为一系列独立算法类,使得它们可以相互替换。

**策略模式的着重点:**不是如何来实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。

**策略和上下文的关系:**在策略模式中,一般情况下都是上下文持有策略的引用,以进行对具体策略的调用。但具体的策略对象也可以从上下文中获取所需数据,可以将上下文当做参数传入到具体策略中,具体策略通过回调上下文中的方法来获取其所需要的数据。

这里关于在策略中回调上下文中的方法示例,引用一个比较好的例子:
引用连接:https://www.cnblogs.com/lewis0077/p/5133812.html

在跨国公司中,一般都会在各个国家和地区设置分支机构,聘用当地人为员工,这样就有这样一个需要:每月发工资的时候,中国国籍的员工要发人民币,美国国籍的员工要发美元,英国国籍的要发英镑。

1 //支付策略接口

public interface PayStrategy {
     //在支付策略接口的支付方法中含有支付上下文作为参数,以便在具体的支付策略中回调上下文中的方法获取数据
   public void pay(PayContext ctx);
 }
//人民币支付策略
public class RMBPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        System.out.println("现在给:"+ctx.getUsername()+" 人民币支付 "+ctx.getMoney()+"元!");
    }
}
//美金支付策略
public class DollarPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        System.out.println("现在给:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !");
    }
}
//支付上下文,含有多个算法的公有数据
public class PayContext {
   //员工姓名
   private String username;
   //员工的工资
   private double money;
   //支付策略
   private PayStrategy payStrategy;

   public void pay(){
       //调用具体的支付策略来进行支付
       payStrategy.pay(this);
   }

   public PayContext(String username, double money, PayStrategy payStrategy) {
       this.username = username;
       this.money = money;
       this.payStrategy = payStrategy;
   }

   public String getUsername() {	
       return username;
   }

   public double getMoney() {
       return money;
   }
}
//外部客户端
public class Client {
    public static void main(String[] args) {
        //创建具体的支付策略
        PayStrategy rmbStrategy = new RMBPay();
        PayStrategy dollarStrategy = new DollarPay();
        //准备小王的支付上下文
        PayContext ctx = new PayContext("小王",30000,rmbStrategy);
        //向小王支付工资
        ctx.pay();

        //准备Jack的支付上下文
        ctx = new PayContext("jack",10000,dollarStrategy);
        //向Jack支付工资
        ctx.pay();
    }
}

控制台输出:

现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !

那现在我们要新增一个银行账户的支付策略,该怎么办呢?

显然我们应该新增一个支付找银行账户的策略实现,由于需要从上下文中获取数据,为了不修改已有的上下文,我们可以通过继承已有的上下文来扩展一个新的带有银行账户的上下文,然后再客户端中使用新的策略实现和带有银行账户的上下文,这样之前已有的实现完全不需要改动,遵守了开闭原则。

//银行账户支付
public class AccountPay implements PayStrategy {
    @Override
    public void pay(PayContext ctx) {
        PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx;
        System.out.println("现在给:"+ctxAccount.getUsername()+"的账户:"+ctxAccount.getAccount()+" 支付工资:"+ctxAccount.getMoney()+" 元!");
    }
}
//带银行账户的支付上下文
public class PayContextWithAccount extends PayContext {
    //银行账户
    private String account;
    public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) {
        super(username, money, payStrategy);
        this.account = account;
    }

    public String getAccount() {
        return account;
    }
}
//外部客户端
public class Client {
    public static void main(String[] args) {
        //创建具体的支付策略
        PayStrategy rmbStrategy = new RMBPay();
        PayStrategy dollarStrategy = new DollarPay();
        //准备小王的支付上下文
        PayContext ctx = new PayContext("小王",30000,rmbStrategy);
        //向小王支付工资
        ctx.pay();
        //准备Jack的支付上下文
        ctx = new PayContext("jack",10000,dollarStrategy);
        //向Jack支付工资
        ctx.pay();
        //创建支付到银行账户的支付策略
        PayStrategy accountStrategy = new AccountPay();
        //准备带有银行账户的上下文
        ctx = new PayContextWithAccount("小张",40000,accountStrategy,"1234567890");
        //向小张的账户支付
        ctx.pay();
    }
}

控制台输出:

现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
现在给:小张的账户:1234567890 支付工资:40000.0 元!

除了上面的方法,还有其他的实现方式吗?

当然有了,上面的实现方式是策略实现所需要的数据都是从上下文中获取,因此扩展了上下文;现在我们可以不扩展上下文,直接从策略实现内部来获取数据,看下面的实现:

//支付到银行账户的策略
public class AccountPay2 implements PayStrategy {
    //银行账户
    private String account;
    public AccountPay2(String account) {
        this.account = account;
    }
    @Override
    public void pay(PayContext ctx) {
        System.out.println("现在给:"+ctx.getUsername()+"的账户:"+getAccount()+" 支付工资:"+ctx.getMoney()+" 元!");
    }
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
}
//外部客户端
public class Client {
    public static void main(String[] args) {
        //创建具体的支付策略
        PayStrategy rmbStrategy = new RMBPay();
        PayStrategy dollarStrategy = new DollarPay();
        //准备小王的支付上下文
        PayContext ctx = new PayContext("小王",30000,rmbStrategy);
        //向小王支付工资
        ctx.pay();
        //准备Jack的支付上下文
        ctx = new PayContext("jack",10000,dollarStrategy);
        //向Jack支付工资
        ctx.pay();
        //创建支付到银行账户的支付策略
        PayStrategy accountStrategy = new AccountPay2("1234567890");
        //准备上下文
        ctx = new PayContext("小张",40000,accountStrategy);
        //向小张的账户支付
        ctx.pay();
    }
}

控制台输出:

现在给:小王 人民币支付 30000.0元!
现在给:jack 美金支付 10000.0dollar !
现在给:小张的账户:1234567890 支付工资:40000.0 元!

总结:

策略模式的优点:
  1. 上下文和具体策略是松耦合关系,因此,上下文只需要知道他要使用耨一个实现Strategy接口类的实例,但不需要知道具体是哪一个类。
  2. 策略模式满足“开-闭原则”,当增加新的具体策略时,不需要修改上下文类的代码,上下文就可以引用新的策略的实例。
适合使用策略模式的场景:
  1. 一个类定义了多种行为,并且这些行为在这个类的方法中以多个条件语句的形式出现,那么使用策略模式避免在类中使用大量的条件语句。
  2. 程序的主类(相当于上下文角色),不希望暴露复杂的,与算法相关的数据结构,那么可以使用策略模式封装算法,即将算法分别封装到具体策略中。
  3. 需要使用一个算法的不同变体。

引用一个大佬的总结:

策略模式的优点:
  1.策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。
  2.策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是if-else组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。
  3.扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。
策略模式的缺点:
  1.客户端必须了解所有的策略,清楚它们的不同:
     如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现。
  2.增加了对象的数量:
    由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多。
  3.只适合偏平的算法结构:
    由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套。
策略模式的本质:
  分离算法,选择实现。
  如果你仔细思考策略模式的结构和功能的话,就会发现:如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程,就能够享受到面向接口编程带来的好处,通过一个统一的策略接口来封装和分离各个具体的策略实现,无需关系具体的策略实现。
  貌似没有上下文什么事,但是如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单。
  策略模式体现了开闭原则:策略模式把一系列的可变算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。
  策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略实现都是兄弟关系,实现了同一个接口或者继承了同一个抽象类。这样只要使用策略的客户端保持面向抽象编程,就可以动态的切换不同的策略实现以进行替换。

已标记关键词 清除标记
1) 优秀的程序应该是这样的:阅读时,感觉很优雅;新增功能时,感觉很轻松;运行时,感觉很快速,这就需要设计模式支撑。 2) 设计模式包含了大量的编程思想,讲授和真正掌握并不容易,网上的设计模式课程不少,大多讲解的比较晦涩,没有真实的应用场景和框架源码支撑,学习后,只知其形,不知其神。就会造成这样结果: 知道各种设计模式,但是不知道怎么使用到真实项目。本课程针对上述问题,有针对性的进行了升级 (1) 授课方式采用 图解+框架源码分析的方式,让课程生动有趣好理解 (2) 系统全面的讲解了设计模式,包括 设计模式七大原则、UML类图-类的六大关系、23种设计模式及其分类,比如 单例模式的8种实现方式、工厂模式的3种实现方式、适配器模式的3种实现、代理模式的3种方式、深拷贝等 3) 如果你想写出规范、漂亮的程序,就花时间来学习下设计模式吧 课程内容和目标 本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式 1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23种设计模式包括:创建型模式:单例模式(8种实现)、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式:适配器模式(3种实现)、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式(3种实现)。行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式策略模式、职责链模式(责任链模式) 2) 学习目标:通过学习,学员能掌握主流设计模式,规范编程风格,提高优化程序结构和效率的能力。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页