likes
comments
collection
share

策略模式 - 白话详解以及和工厂模式的区别

作者站长头像
站长
· 阅读数 5

前言

第一眼看到策略模式(Strategy Pattern)这四个大字的时候

我可太喜欢它的名字了

策略?对我这个喜欢看三国的人来说,这两个字简直在闪闪发光

又说它主要解决在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护的问题

这么优雅的吗?!

我要学这个!

奔现

看了看它的通用实现

先创建一个抽象策略接口(妙啊👏)

public interface IStrategy {  
    void doSomething();  
}

然后创建实现策略接口的具体策略(理应如此)

public class StrategyA implements IStrategy {  
    @Override  
    public void doSomething() {  
        System.out.println("策略A");  
    }  
}

public class StrategyB implements IStrategy {  
    @Override  
    public void doSomething() {  
        System.out.println("策略B");  
    }  
}

再创建封装类(这如此简单的 class 要干啥用,真是高深啊)

public class Context {  
    private IStrategy strategy;  
    public Context(IStrategy strategy) {  
        this.strategy = strategy;  
    }  
    public void executeStrategy() {  
        strategy.doSomething();  
    }
}

最后调用(额)

public class StrategyPatternDemo {  
    public static void main(String[] args) {  
        Context context = new Context(new StrategyA());  
        context.executeStrategy();  
        
        context = new Context(new StrategyB());  
        context.executeStrategy();  
    }  
}

啊这

这不就是OOP里的继承 + 多态

而且,实际开发怎么用啊?

  1. 那么多实现了 IStrategy 的对象,开发的时候不能写死具体用哪几个,得动态选吧
  2. 动态选,那 Context.class 得和工厂模式配合用
  3. 用工厂模式,那还是需要参数和 if...else 逻辑来判断创建哪个策略类

啊这

真的鸡肋,吗

“因为喜欢,所以都对”,我尝试思索出策略模式的优点

(看到了吧JYM,名字起的好真的很重要啊)

贴合开发的优点:利于代码复用

举个例子:实现一个简单计算器,让它能对 a、b 两个数做加减

我们先定义一个接口

public interface IStrategy {  
    public int doOperation(int a, int b);  
}

再定义加减法 class 并实现 IStrategy

public class Add implements IStrategy {  
    @Override  
    public int doOperation(int a, int b) {  
        return a + b;  
    }  
}

public class Subtract implements IStrategy {  
    @Override  
    public int doOperation(int a, int b) {  
        return a - b;  
    }  
}

然后程序运行时要根据用户输入,动态的选择“加”或“减”

假如不用策略模式,那么最直接的写法就是用一堆 if...else + 参数来选择

public class SimpleCalculator {  
    public static void main(String[] args) {  
        String mockUserInput = "Add";  
        
        IStrategy strategy = null;  
        if ("Add".equals(mockUserInput)) {  
            strategy = new Add();  
        } else if ("Subtract".equals(mockUserInput)) {  
            strategy = new Subtract();  
        }  
        
        System.out.println(strategy.doOperation(3, 2));  
    }  
}

运行输出3 + 2 = 5

挺好

有一天又来个需求,要实现个电脑,这电脑里也有个简单算数的功能

这不是巧了吗

有现成的简单计算器的代码

这时不同人就有不同的做法了,最常见的2种:

  1. 把 SimpleCalculator 里的 if...else 直接复制过去(屎山的来源之一)
  2. 把 SimpleCalculator 里的 if...else 抽出一个方法,然后在 SimpleComputer 里调用
public class SimpleCalculator {  
    public static void main(String[] args) {  
        String mockUserInput = "Add";  
        IStrategy strategy = getStrategy(mockUserInput);  
        
        System.out.println(strategy.doOperation(3, 2));  
    }  
    
    // 抽出一个方法  
    public static IStrategy getStrategy(String mockUserInput) {  
        IStrategy strategy = null;  
        if ("Add".equals(mockUserInput)) {  
            strategy = new Add();  
        } else if ("Subtract".equals(mockUserInput)) {  
            strategy = new Subtract();  
        }  
        return strategy;  
    }  
}

public class SimpleComputer {  
    public static void main(String[] args) {  
        // 调用  
        IStrategy strategy = SimpleCalculator.getStrategy("Subtract");  
        System.out.println(strategy.doOperation(3, 2));  
    }  
}

抽出一个方法然后调用的做法比直接复制好太多

但是吧

SimpleCalculator 和 SimpleComputer 本身没有一毛钱关系,这样调用不别扭么?

别扭就对了

用专业术语说,这种写法违反了设计模式的原则:

  1. 违反单一职责原则:从角色上讲,那堆 if...else 是算法本身,而 SimpleCalculator 是算法的使用者,它们不应该混在一起
  2. 违反迪米特法则:SimpleComputer 只是想用下算法,和 SimpleCalculator 没有任何其它交集,最好不要这么调用

想象一下

为了实现这个电脑的简单计算功能,却把整个计算器都焊到了电脑上,能用是能用,但这不太合适吧?

那如果新建个 class 封装并对外提供这堆 if...else 算法逻辑呢(想象成把计算逻辑烧到单片机里)

public class ChipContext {  
    private IStrategy strategy;  
    
    public ChipContext(String mockUserInput) {  
        if ("Add".equals(mockUserInput)) {  
            this.strategy = new Add();  
        } else if ("Subtract".equals(mockUserInput)) {  
            this.strategy = new Subtract();  
        }  
    }  
}

光封装起来没用,主要目的还是供人使用

那就再加个doOperation()

/*
 * 只是为了举例,实际开发别这么写
 * 实际开发时,个人认为一般情况下 ChipContext、Add、Subtract 使用单例模式足以
 */
public class ChipContext {  
    private IStrategy strategy;  
    
    public ChipContext(String mockUserInput) {  
        IStrategy strategy = null;  
        if ("Add".equals(mockUserInput)) {  
            this.strategy = new Add();  
        } else if ("Subtract".equals(mockUserInput)) {  
            this.strategy = new Subtract();  
        }  
    }  
    
    public int doOperation(int a, int b) {  
	return this.strategy.doOperation(a, b);  
    }
}

眼熟不?

这就是配合了工厂模式的策略模式

而且 SimpleCalculator、SimpleComputer 可以这么用了

public class SimpleCalculator {  
    public static void main(String[] args) {  
        String mockUserInput = "Add";  
        ChipContext context = new ChipContext(mockUserInput);  
        System.out.println(context.doOperation(3, 2));  
    }  
}

public class SimpleComputer {  
    public static void main(String[] args) {  
        ChipContext context = new ChipContext("Subtract");  
        System.out.println(context.doOperation(3, 2));  
    }  
}

优雅不

与工厂模式的区别

回忆下上面的例子

在上例中,我们的关注点是实现加减功能,而不是计算器和电脑

对吧?

这就是策略模式和工厂模式的区别

工厂模式关注的是对象的创建:好比想要一台电脑、想要一台计算器,工厂给你生产出来

策略模式关注的是行为的封装:好比要开发一台电脑或者计算器,你想实现加减法。是 a+b 还是 b+a,由你决定;是 a×10÷10+b 还是 (a+b),也由你决定。对外暴露的就是加减功能,用户能知道有这俩功能就行

总结

设计模式只是把 if...else 提到更高的地方了,最终还是逃不过逻辑上的 if...else

策略模式当然也是设计模式的一种(虽然可以用枚举类或者Map消除明面上的 if...else,但内部实现上还是),它本质上就是OOP里的继承 + 多态,且常与工厂模式配合使用,目前觉得利于代码复用这一优点就很好,至于其它优缺点,拗口的答案就不在此赘述了

我觉得新人在开发时用不用设计模式不重要,重要的是要有一颗想写好代码的心。在实现功能的基础上,能做到代码的整洁、重构、复用就完全OK。日积月累,总会灵光一现,会开始用设计模式来比照代码,此所谓厚积薄发


感谢阅读~不喜勿喷。欢迎讨论、指正

转载自:https://juejin.cn/post/7217993597617946684
评论
请登录