Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

【淘宝】浅析设计模式2 —— 策略模式

2023-06-11 1986点热度 0人点赞 0条评论

策略模式是一种应用广泛的行为型模式,本文将着眼于策略模式进行学习分享。

本文为此系列第二篇。

第一篇:浅析设计模式1——工厂模式

概述

我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度,而设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。

浅析设计模式2 —— 策略模式

大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。其中,行为型模式可用于描述程序中多个类和多个对象如何协作完成复杂的任务,涉及不同对象间的职责分配、算法的抽象化。策略模式是一种应用广泛的行为型模式,本文将着眼于策略模式进行学习分享,如有表述不当的地方恭请大佬们指教哦~

 

浅析设计模式2 —— 策略模式

基本概念

策略模式的核心思想是对算法进行封装,委派给不同对象来管理。这样,我们就可以定义一系列算法,将每个算法封装到具有公共接口的一系列具体策略类中,从而使它们可以灵活替换,并让算法可以在不影响到客户端的情况下发生变化。同时,策略模式仅仅封装算法(包括添加、删除),但其并不决定在何时使用何种算法,算法的选择由客户端来决定。

浅析设计模式2 —— 策略模式

比如,我们旅游时可以选择的出行策略有很多种:自行车、汽车、火车、飞机,每种出行策略都有各自的使用方法,只要能到目的地,我们可以随意更换各种策略。再比如我们去逛商场,商场会有很多促销活动:满减、返利等,这些促销方式本质上都是一些算法,而算法本身也是一种策略,随时都可能互相替换的,针对同一件商品,今天满500减50、明天满300返100购物券,这些策略之间同样可以互换。

 

那么,我们应该如何使用策略模式呢?下面将从结构和使用步骤两个层面,对策略模式进行概念性介绍。

▐结构

策略模式包含三种类,分别是抽象策略类、具体策略类、环境类,它们各自负责完成特定任务,并且相互之间存在紧密的联系。

 

角色 关系 作用
抽象策略 Strategy 所有具体策略类的父类 定义一个公共接口,定义若干个算法标识和抽象方法
具体策略 Concrete Strategy 抽象策略的接口实现类 实现抽象策略定义的抽象方法,描述具体的算法实现
环境 Context
维护一个抽象策略类的引用实例
委托策略变量,调用具体策略所实现的抽象策略接口中的方法

浅析设计模式2 —— 策略模式

▐使用

有了上述的基本概念,我们将策略模式的使用步骤概括为:

  1. step1:创建抽象策略类,为具体策略定义好一个公共接口;
  2. step2:创建具体策略类,其通过接口来实现抽象策略类,同时封装了具体的算法;
  3. step3:创建环境类,持有一个抽象策略类的引用,提供给客户端调用。
使用示例

淘宝用户都知道,除了双11购物狂欢节,平台每年都会打造很多其他的促销活动。试想一下,如果每种大促活动都使用一种促销模式,未免太过枯燥,于用户、商家、平台而言都不友好。因此,为了提升用户购买体验、突出商家营销特点,需要面向不同大促活动使用不同的策略进行促销。这里以促销策略为例,简单分析策略模式如何使用:

 

▐代码实现

//step1:定义抽象策略角色(Strategy):所有促销活动的共同接口
public interface Strategy {  
    void show();
}

//step2:定义具体策略角色(Concrete Strategy):不同类型的具体促销策略
//618大促活动 A
public class ConcreteStrategyA implements Strategy{
    @Override
    public void show() {
        System.out.println("618大促");
    }
}

//99大促活动 B
public class ConcreteStrategyB implements Strategy{
    @Override
    public void show() {
        System.out.println("99大促");
    }
}

//双11大促活动 C
public class ConcreteStrategyC implements Strategy{
    @Override
    public void show() {
        System.out.println("双11大促");
    }
}

//step3:定义环境角色(Context):把促销活动推送给用户,这里可理解为淘宝平台
public class Context_TaoPlatform{
    //持有抽象策略的引用
    private Strategy myStrategy;
    //生成构造方法,让平台根据传入的策略参数选择策略
    public TaoPlatform(Strategy strategyType) {
        this.myStrategy = strategyType;
    }
    //向用户展示促销活动
    public void taoPlatformShow(String time) {
        System.out.println(time + "的促销策略是:");
        myStrategy.show();
    }
}

//step4:客户端调用
public class StrategyPattern{
  public static void main(String[] args){
        Context_TaoPlatform context;
    
        String time1 = "9月";
        Strategy strategyB = new ConcreteStrategyB();
        context = new Context_TaoPlatform(strategyB);
        context.taoPlatformShow(time1);
    
        String time2 = "11月";
        Strategy strategyC = new ConcreteStrategyC();
        context = new Context_TaoPlatform(strategyC);
        context.taoPlatformShow(time2);
    
        String time3 = "6月";
        Strategy strategyA = new ConcreteStrategyA();
        context = new Context_TaoPlatform(strategyA);
        context.taoPlatformShow(time3);
  }   
}
▐结果输出
9月的促销策略是:
99大促
11月的促销策略是:
双11大促
6月的促销策略是:
618大促

▐UML图

浅析设计模式2 —— 策略模式

▐与简单工厂模式的区别

从上面的代码示例及类图可以看出来,策略模式和上一篇文章中介绍的简单工厂模式很像,两者主要区别在于 Context 类和工厂类。为了方便对比,我们把这两个类的代码单独拎出来看看:

public class Context_TaoPlatform{
    //持有抽象策略的引用
    private Strategy myStrategy;
    //生成构造方法,让平台根据传入的策略参数选择促销活动
    public TaoPlatform(Strategy strategyType) {
        this.myStrategy = strategyType;
    }
    //向用户展示促销活动
    public void taoPlatformShow(String time) {
        System.out.println(time + "的促销策略是:");
        myStrategy.show();
    }
}
public class Factory{
    public static Shirt exhibit(String ShirtName){
        switch(ShirtName){
            case "女款衬衫":
                return new WomenShirt();
            case "男款衬衫":
                return new MenShirt();
            default:
                return null;
        }
    }
}
首先看一下接收参数:工厂类 Factory 中的 exhibit() 方法接收字符串,返回一个 Shirt 对象;环境类 Context_TaoPlatform 初始化时需要接收一个 Strategy 对象。也就是说:工厂类中是根据接收的条件创建一个相应的对象,而 Context 类接收的是一个对象,可以调用方法去执行此对象的方法。
举个例子:笔有很多种,假设有一个工厂专门负责生产不同用途的笔。
  1. 工厂模式:根据用户给出的目的来生产不同用途的笔,如:要写毛笔字就生产毛笔、要写钢笔字就生产钢笔。即根据用户给出的某种属性,生产能做出相应行为的一种对象返回给用户,重点在于创建何种对象。
  2. 策略模式:用工厂生产的笔去出做对应的行为,如:用毛笔写毛笔字、用钢笔写钢笔字。即根据用户给出的某种对象,执行相应的方法,重点在于选择何种行为。
JDK源码赏析

这里以 Comparator 比较器为例,通过分析其源码实现来深入理策略模式。

 

在 JDK 中,我们调用数组工具类 Arrays 的一个排序方法 sort() 时,可以使用默认的排序规则(升序),也可以自定义一种排序的规则,即自定义实现升序或降序的排序。源码如下:

public class Arrays{
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            //若没有传入Comparator接口的实现类对象,调用默认的升序排序方法
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                //jdk5及之前的传统归并排序,新版本中LegacyMergeSort.userRequested默认false
                legacyMergeSort(a, c);
            else
                //改进后的归并排序
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
}

此时我们需要传入两个参数:一个是待排序的数组,另一个则是 Comparator 接口的实现类对象。其中,Comparator 接口是一种函数式接口,该接口中定义了一个抽象方法 int compare(T o1, T o2),用于定义具体的排序规则。这里,Comparator 接口就是策略模式中的抽象策略接口,它定义了一个排序算法,而具体策略(具体的排序算法)将由用户来定义,那么Arrays 就是一个环境类,sort() 方法可以传入一个策略 c ,让 Arrays 根据这个策略进行排序任务。

 
public class demo {
    public static void main(String[] args) {

        Integer[] data = {2, 0, 22, 14, 1, 3, 4};
        // 实现降序排序
        Arrays.sort(data, new Comparator<Integer>() {
             // 排序策略 降序
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        System.out.println(Arrays.toString(data)); 
    }
}

在上面这个 demo 中,我们在调用 Arrays.sort() 方法时,第二个参数传递的是 Comparator 接口的子实现类对象。由此可见,Comparator 充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色,环境角色类 Arrays 应该持有抽象策略的引用来调用。那么,Arrays.sort() 方法究竟有没有使用 Comparator 子实现类中的 compare() 方法?下面再看看 TimSort.sort() 方法,源码如下:

class TimSort<T> {
    static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  

        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }
        ...
    }   
        
    private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        if (c.compare(a[runHi++], a[lo]) < 0) { 
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }
        return runHi - lo;
    }
}

 

上面的代码最后会执行到 countRunAndMakeAscending() 方法中,在执行判断语句时调用了 compare() 方法。那么如果只用了 compare() 方法,在调用 Arrays.sort() 方法时只要传具体 compare() 重写方法的类对象。

 

优缺点及适用场景

▐优点

  1. 具体策略类之间可自由切换,由于具体策略类都实现同一个抽象策略接口,所以它们之间可以自由切换。
  2. 支持“开闭原则”,扩展增加一个新策略时只需添加一个具体策略类即可,基本不需要改变原有的代码。
  3. 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

 

▐ 缺点

  • 客户端必须知道所有的具体策略类,并理解不同具体策略的区别、自行决定使用哪一个策略类。
  • 策略模式将产生很多具体策略类,在一定程度上增加了系统中类的个数(可通过使用享元模式在一定程度上减少对象数量)。

 

▐ 适用场景
  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到具体策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句,就能避免使用难以维护的多重条件选择语句,并体现面向对象涉及的概念。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节,提高算法的保密性与安全性。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

 

总结

策略模式是一个比较容易理解和使用的设计模式,它仅封装算法,方便新算法插入系统中、老算法从系统中退休。本文在分析策略模式的缺点时提到,策略模式并不决定在何时使用何种算法,算法选择由客户端来决定,虽然这在一定程度上提高了系统的灵活性,但客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,增加了客户端的使用难度。

 

上一篇文章提到,策略模式和工厂模式有一定相似之处,在于它们的模式结构,因此有时候会让人混淆不清。实际上,这两者之间存在较多差异:工厂模式是创建型模式,作用是创建对象,它关注对象如何创建,主要解决的是资源的统一分发,将对象的创建完全独立出来,让对象的创建和具体的使用客户无关;策略模式是行为型模式,作用是让一个对象在许多行为中选择一种行为,它关注行为如何封装,通过定义策略族来实现策略的灵活切换与扩展,并让策略的变化独立于使用策略的客户。

 

另外,很多场景下策略模式和工厂模式可以结合使用,共同发挥优势起到相辅相成的作用。比如,策略模式的缺点之一是用户必须清楚所有的具体策略算法,这样具体策略难免暴露出去,并且要由上层模块初始化,这与迪米特法则相悖(最少知识原则),而上层模块和底层模之间的解耦,可以让工厂模式来完成。两者结合之后,对于上层模块而言不需要知道每种具体策略,只要通过 Context 就可以实现策略模式。(至于如何结合策略模式和工厂模式,大家可以上网搜索哦,已经有很多大佬给出了具体的案例和代码示例,这里就不再赘述了)

 

下篇预告

至此,我们已经学习了两种类型的设计模式,即创建型模式(工厂模式)和行为型模式(策略模式),看过文章的朋友们一定知道,每篇文章开头都会对 23 种经典的设计模式进行罗列,其中划分出三大类设计模型:创建型模式、结构型模式和行为型模式。不知道你们是否和我一样好奇结构型模式有什么特点和适用场景?实际生产中最常用的结构型模式是什么?下一篇文章我们一起认识和了解结构型模式,敬请期待!

 

作者|刘文慧(鎏越)

编辑|橙子君

 

本文仅供学习!所有权归属原作者。侵删!文章来源: 大淘宝技术

更多文章:

  1. 设计模式在外卖营销业务中的实践
  2. 浅析设计模式1 —— 工厂模式
  3. 手把手教你实战TDD
  4. 殷浩详解DDD系列 第一讲 - Domain Primitive
  5. 殷浩详解DDD 第四讲:领域层设计规范
  6. 责任链模式在复杂数据处理场景中的实战
  7. JVM 内存分析工具 MAT 的深度讲解与实践——进阶篇(长文)
  8. 浅析设计模式4——模板方法模式
  9. 从代码到设计的性能优化指南
  10. 全链路压测之影子库及ShardingSphere实现影子库源码剖析
标签: 淘宝 Java 策略模式 设计模式
最后更新:2023-06-11

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 概述
  • 基本概念
  • 总结
最新 热点 推荐
最新 热点 推荐
干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务? 视频笔记:什么是AI 智能体? 视频笔记:什么是Flink? 如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具
Elasticsearch 使用误区之六——富文本内容写入前不清洗基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计干货 | 论Elasticsearch数据建模的重要性你可以不用RxJava,但必须得领悟它的思想!如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具视频笔记:什么是Flink?视频笔记:什么是AI 智能体?
3.编程语言的演化(译) Cache——对于缓存你应该知道的都在这张图里 一文看懂”ParNew+CMS”组合垃圾回收器 4款亲测好用的开发画图工具 视频笔记:什么是微服务,为什么是微服务? 笔记 | Java对象探秘 可插拔组件设计机制—SPI 万字长文全面了解学习Netty!

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (2) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (18) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2

x