设计模式(行为型)-策略模式
目录
定义
类图
角色
角色详解
Strategy(抽象策略类)
Context(环境类 / 上下文类)
ConcreteStrategy(具体策略类)
优缺点
优点
缺点
使用场景
类行为差异场景
动态算法选择场景
避免复杂条件语句场景
保密性与安全性场景
定义
策略模式作为一种行为型设计模式,核心在于将对象的行为与对象本身分离开来。它把一系列相关的行为定义为一个行为接口,然后通过不同的具体类来实现这些行为。这种设计方式使得行为可以独立于使用它们的对象进行变化,极大地增强了系统的灵活性和可扩展性。
简单来说,策略模式就像是为你的软件系统准备了一个 “行为工具箱”,里面装满了各种不同的行为工具。当你的对象需要执行某种行为时,它可以从这个工具箱中挑选合适的工具,而无需在自身内部固化特定的行为逻辑。例如,在一个电商系统中,对于商品的促销活动,有打折、满减、赠品等多种策略。通过策略模式,我们可以将这些促销策略分别定义为不同的类,每个类实现促销行为的接口。这样,在不同的促销场景下,只需选择相应的策略类,商品就能以不同的促销方式进行销售,而无需频繁修改商品类的代码。
从本质上讲,策略模式的最大特点就是行为的变化性和可替代性。它允许在运行时动态地选择和切换行为,就如同你在玩游戏时,可以根据不同的游戏场景和对手特点,灵活地选择不同的游戏策略一样。每个具体的行为实现就像是一个独立的策略,它们之间可以相互替换,以适应不同的业务需求和变化。在编程世界中,这种模式使得算法能够独立于使用它的用户(即对象)而变化,为软件系统注入了强大的生命力。
类图
角色
策略模式的类图清晰地展示了其核心组成部分以及它们之间的关系,主要涉及三个关键角色:
-
Strategy(抽象策略类):这是一个接口或抽象类,它定义了一系列算法标识,也就是若干个抽象方法。这些抽象方法代表了不同策略所共有的行为特征,具体的策略实现类必须实现这些方法。例如,在一个图形绘制系统中,抽象策略类可能定义了一个 “drawShape” 的抽象方法,用于绘制不同形状的图形。而具体的圆形绘制策略类、矩形绘制策略类等都需要实现这个 “drawShape” 方法,以提供各自独特的绘制逻辑。抽象策略类的存在,就像是为所有具体策略类制定了一个统一的规范,确保它们在行为上具有一致性和可替代性。
-
Context(环境类 / 上下文类):环境类依赖于抽象策略类,它包含一个用策略接口声明的变量。这个变量就像是一个 “插座”,可以插入不同的具体策略类 “插头”。环境类提供一个方法,这个方法持有一个策略类的引用,并最终供客户端调用。在调用时,该方法会委托策略变量调用具体策略所实现的策略接口中的方法。例如,在一个旅行规划系统中,环境类可以是 “TravelPlanner”,它有一个属性是 “TravelStrategy” 类型,用于存储不同的旅行策略(如飞机旅行策略、火车旅行策略等)。“TravelPlanner” 类还提供一个 “planTravel” 方法,当客户端调用这个方法时,它会根据当前设置的 “TravelStrategy” 对象,调用相应策略的旅行规划方法,如飞机旅行策略的 “planByAir” 方法或火车旅行策略的 “planByTrain” 方法。环境类在策略模式中起到了桥梁的作用,它将客户端的请求与具体的策略实现连接起来,使得客户端无需关心具体策略的细节,只需与环境类进行交互即可。
-
ConcreteStrategy(具体策略类):具体策略类是实现抽象策略接口的类,它们针对不同的具体情况,实现了策略接口所定义的抽象方法,给出了算法标识的具体实现。每个具体策略类就像是一把独特的 “钥匙”,对应着解决特定问题的特定算法或行为。例如,在一个排序算法的应用场景中,可能有 “BubbleSortStrategy”(冒泡排序策略类)和 “QuickSortStrategy”(快速排序策略类)等具体策略类。“BubbleSortStrategy” 类实现了抽象策略接口中定义的 “sort” 方法,使用冒泡排序算法对数据进行排序;“QuickSortStrategy” 类同样实现了 “sort” 方法,但采用的是快速排序算法。通过这种方式,不同的具体策略类可以根据实际需求,灵活地替换使用,以达到最佳的排序效果。
角色详解
Strategy(抽象策略类)
-
行为抽象:抽象策略类的首要职责是对一系列相关行为进行抽象。它通过定义抽象方法,将不同具体策略类的共同行为特征提取出来,形成一个统一的行为接口。这个接口就像是一个蓝图,为具体策略类的实现提供了指导和规范。例如,在一个支付系统中,抽象策略类 “PaymentStrategy” 可以定义一个 “processPayment” 的抽象方法,用于处理支付行为。无论是信用卡支付策略、支付宝支付策略还是微信支付策略,都需要实现这个 “processPayment” 方法,以完成各自特定的支付流程。通过这种行为抽象,不同的支付策略在对外表现上具有了一致性,都遵循 “PaymentStrategy” 接口所定义的行为规范,使得它们可以在相同的环境中被灵活使用。
-
代码复用:当多个具体策略类中存在一些共同的代码逻辑时,抽象策略类可以将这部分公共代码提取出来,放在自身的实现中。这样,具体策略类只需继承抽象策略类,并专注于实现各自特有的行为逻辑,避免了代码的重复编写。例如,在一个图像处理系统中,抽象策略类 “ImageProcessingStrategy” 可能定义了一些通用的图像预处理方法,如调整图像大小、转换图像格式等。具体的图像模糊处理策略类、图像锐化处理策略类等都继承自 “ImageProcessingStrategy”,它们可以复用抽象策略类中的预处理方法,同时实现自己独特的图像特效处理逻辑。这种代码复用机制不仅提高了开发效率,还增强了代码的可维护性,因为当公共代码需要修改时,只需在抽象策略类中进行一次修改,所有继承它的具体策略类都会自动应用这些修改。
-
扩展性支持:抽象策略类为系统的扩展性提供了有力支持。当有新的行为需求出现时,只需创建一个新的具体策略类,实现抽象策略类定义的接口即可。无需对现有的代码进行大规模修改,就能轻松地将新的策略集成到系统中。例如,在一个游戏角色的技能系统中,如果需要添加一种新的技能策略,如 “FreezeSkillStrategy”(冰冻技能策略),只需要创建一个实现 “SkillStrategy” 抽象策略接口的 “FreezeSkillStrategy” 类,并实现接口中的 “executeSkill” 方法,定义冰冻技能的具体执行逻辑。然后,在游戏运行时,就可以根据需要动态地选择使用这个新的技能策略,而不会影响到其他已有的技能策略和游戏系统的整体架构。
Context(环境类 / 上下文类)
-
策略引用管理:环境类负责持有一个抽象策略类的引用,这个引用就像是一个 “容器”,可以容纳不同的具体策略对象。通过这种引用管理方式,环境类可以在运行时动态地切换所使用的策略。例如,在一个物流配送系统中,环境类 “DeliveryContext” 有一个 “DeliveryStrategy” 类型的属性,用于存储不同的配送策略(如快递配送策略、同城配送策略等)。在系统初始化时,可以根据用户的选择或系统的配置,将相应的具体配送策略对象赋值给这个属性。当需要执行配送任务时,“DeliveryContext” 就可以通过这个引用调用具体配送策略的方法,实现不同方式的配送服务。这种策略引用管理机制使得系统能够根据实际情况灵活地选择和应用不同的策略,提高了系统的适应性和灵活性。
-
客户端交互接口:环境类为客户端提供了一个统一的交互接口,客户端通过调用环境类的方法来触发相应的策略行为,而无需了解具体策略的实现细节。这样,客户端与具体策略类之间实现了松耦合,降低了客户端代码与策略实现代码之间的依赖关系。例如,在一个音乐播放系统中,环境类 “MusicPlayerContext” 提供了一个 “playMusic” 方法,客户端只需调用这个方法,而无需关心音乐播放是采用在线播放策略还是本地播放策略。“MusicPlayerContext” 会根据当前设置的 “MusicPlayStrategy” 对象,调用相应策略的播放方法,实现音乐的播放功能。这种设计方式使得客户端代码更加简洁、易读,同时也方便了系统的维护和扩展,因为当需要更换音乐播放策略时,只需在 “MusicPlayerContext” 中修改所引用的策略对象,而无需修改客户端代码。
-
策略执行协调:环境类在策略执行过程中起到了协调的作用。它不仅负责将客户端的请求传递给具体策略类,还可以在策略执行前后进行一些额外的处理,如日志记录、权限验证等。例如,在一个订单处理系统中,环境类 “OrderProcessingContext” 在调用具体的订单处理策略(如普通订单处理策略、加急订单处理策略)的 “processOrder” 方法之前,可以先记录订单处理的开始时间和相关信息;在策略执行结束后,可以记录订单处理的结果和结束时间,并根据处理结果进行相应的后续操作,如发送订单处理成功或失败的通知。通过这种策略执行协调机制,环境类可以对整个策略执行过程进行有效的管理和控制,确保系统的运行符合业务需求和规范。
ConcreteStrategy(具体策略类)
-
算法实现:具体策略类的核心任务是实现抽象策略类定义的抽象方法,提供具体的算法实现。每个具体策略类针对特定的业务场景或问题,采用不同的算法或逻辑来完成相应的行为。例如,在一个数据加密系统中,“AESEncryptionStrategy” 类实现了抽象策略类 “EncryptionStrategy” 定义的 “encryptData” 方法,使用 AES 加密算法对数据进行加密;而 “RSAEncryptionStrategy” 类同样实现了 “encryptData” 方法,但采用的是 RSA 加密算法。这些具体策略类通过实现各自独特的加密算法,为系统提供了多种数据加密选择,用户可以根据实际需求和安全要求选择合适的加密策略。
-
行为定制:具体策略类可以根据自身的特点和需求,对行为进行定制化处理。它们可以在实现抽象方法的基础上,添加额外的逻辑和功能,以满足特定场景下的业务需求。例如,在一个电商平台的优惠券使用策略中,“PercentageDiscountCouponStrategy” 类实现了抽象策略类 “CouponStrategy” 定义的 “applyCoupon” 方法,用于计算使用百分比折扣优惠券后的商品价格。在这个方法中,除了基本的价格计算逻辑外,还可以添加一些定制化的逻辑,如判断优惠券是否过期、是否满足使用条件等。通过这种行为定制机制,具体策略类能够更加灵活地适应不同的业务场景和变化,为系统提供更加丰富和个性化的功能。
-
可替换性:具体策略类之间具有可替换性,这是策略模式的重要特性之一。由于它们都实现了相同的抽象策略接口,所以在运行时可以根据需要轻松地将一个具体策略类替换为另一个具体策略类,而不会影响到系统的其他部分。例如,在一个图形渲染系统中,起初使用 “OpenGLRenderingStrategy” 类来实现图形渲染功能,但随着业务发展和技术升级,需要切换到性能更好的 “VulkanRenderingStrategy” 类。由于这两个类都实现了 “RenderingStrategy” 抽象策略接口,所以只需在环境类中修改所引用的策略对象,将 “OpenGLRenderingStrategy” 替换为 “VulkanRenderingStrategy”,系统就可以无缝地切换到新的渲染策略,而无需对其他模块的代码进行大规模修改。这种可替换性使得系统能够快速适应不同的需求和变化,提高了系统的可维护性和可扩展性。
优缺点
优点
-
完美支持开闭原则:开闭原则是软件设计中的重要原则之一,它要求软件系统对扩展开放,对修改关闭。策略模式为开闭原则提供了近乎完美的支持。当有新的行为或算法需求出现时,我们只需创建一个新的具体策略类,实现抽象策略接口,然后在环境类中进行简单配置,即可将新的策略集成到系统中,而无需修改现有系统的核心代码。例如,在一个在线教育平台的课程推荐系统中,起初使用基于用户历史浏览记录的推荐策略,但随着业务发展,需要增加基于用户兴趣标签的推荐策略。通过策略模式,我们可以创建一个新的 “InterestBasedRecommendationStrategy” 类,实现 “RecommendationStrategy” 抽象策略接口,然后在课程推荐环境类中添加对这个新策略的支持。这样,系统在不修改现有推荐策略代码的基础上,成功实现了新推荐策略的扩展,有效降低了系统的维护成本和风险。
-
管理相关算法族:策略模式提供了一种有效的方式来管理一组相关的算法。通过将不同的算法封装到各自的具体策略类中,并通过抽象策略类进行统一管理,我们可以清晰地组织和维护这些算法。例如,在一个数据处理系统中,有多种数据排序算法(冒泡排序、快速排序、归并排序等)和数据搜索算法(顺序搜索、二分搜索等)。通过策略模式,我们可以将这些排序算法和搜索算法分别封装到不同的具体策略类中,如 “BubbleSortStrategy”“QuickSortStrategy”“SequentialSearchStrategy”“BinarySearchStrategy” 等。这些具体策略类都继承自相应的抽象策略类(如 “SortingStrategy”“SearchingStrategy”),形成了一个算法族的层次结构。这种组织方式使得算法的管理更加方便,易于理解和维护,同时也方便了算法的扩展和替换。
-
替代继承关系:在传统的面向对象编程中,当一个类需要有多种不同的行为时,可能会通过继承来实现,即创建多个子类,每个子类实现不同的行为。然而,这种方式会导致类的数量急剧增加,代码的复杂度和维护成本也随之上升,并且在运行时难以动态地改变对象的行为。策略模式提供了一种更灵活的替代方案,它通过将行为封装到独立的策略类中,使得对象可以在运行时动态地选择和切换行为,而无需通过继承来实现。例如,在一个游戏角色类中,如果使用继承来实现不同的攻击行为,可能需要创建 “MeleeAttackCharacter”(近战攻击角色类)、“RangedAttackCharacter”(远程攻击角色类)等多个子类。而通过策略模式,我们可以创建 “MeleeAttackStrategy” 和 “RangedAttackStrategy” 等具体策略类,游戏角色类只需持有一个 “AttackStrategy” 抽象策略类的引用,在运行时根据实际情况选择不同的攻击策略,从而实现不同的攻击行为。这样,不仅减少了类的数量,降低了代码复杂度,还提高了系统的灵活性和可扩展性。
-
避免多重条件转移语句:在没有使用策略模式的情况下,当一个对象需要根据不同的条件执行不同的行为时,往往会使用多重条件转移语句(如 if-else 或 switch 语句)。然而,随着条件和行为的增加,这些条件转移语句会变得冗长、复杂,难以阅读和维护。策略模式通过将不同的行为封装到具体策略类中,使得条件判断和行为执行的逻辑分离,从而避免了使用多重条件转移语句。例如,在一个订单处理系统中,如果不使用策略模式,可能会在订单处理方法中使用大量的 if-else 语句来判断订单类型(普通订单、加急订单、团购订单等),并根据不同的订单类型执行不同的处理逻辑。而使用策略模式后,我们可以创建 “NormalOrderStrategy”“UrgentOrderStrategy”“GroupBuyOrderStrategy” 等具体策略类,订单处理环境类只需根据订单类型选择相应的策略类来执行订单处理逻辑,无需在代码中编写复杂的条件判断语句。这样,代码结构更加清晰,可读性和可维护性大大提高。
缺点
-
客户端知晓策略类:在策略模式中,客户端必须了解所有可用的策略类,并自行决定使用哪一个策略类。这就要求客户端对系统中的各种策略有一定的了解,以便能够根据实际需求做出正确的选择。然而,在一些复杂的系统中,策略类的数量可能较多,而且策略的实现细节也可能较为复杂,这增加了客户端使用的难度。例如,在一个金融投资系统中,有多种投资策略(如价值投资策略、成长投资策略、量化投资策略等),每个策略都有其独特的算法和风险特点。客户端在选择投资策略时,需要对这些策略有深入的了解,才能做出合适的决策。如果客户端对策略不熟悉,可能会选择错误的策略,导致投资失败。为了解决这个问题,可以在系统中提供一些辅助工具或文档,帮助客户端更好地了解和选择策略,或者在环境类中提供一些默认的策略选择逻辑,降低客户端的使用难度。
-
策略类数量众多:由于策略模式将每个具体的行为都封装成一个独立的策略类,当系统中的行为种类较多时,会导致策略类的数量急剧增加。这不仅增加了系统的复杂性和维护成本,还可能会使代码的结构变得混乱。例如,在一个电商平台的促销活动系统中,可能有打折、满减、赠品、限时抢购、团购等多种促销策略,每种促销策略都需要一个具体的策略类来实现。随着业务的发展和促销活动的不断创新,可能还会有更多的促销策略需要添加,这将导致策略类的数量不断增加
使用场景
类行为差异场景
当系统中存在众多类,其差异仅体现在行为方面时,策略模式的优势便得以凸显。以游戏开发为例,游戏中有战士、法师、刺客等多种角色类。战士擅长近身物理攻击,法师专注远程法术输出,刺客则精于潜行暗杀。这些角色类的核心区别就在于战斗行为的不同。运用策略模式,我们可以将攻击行为抽象出来,创建如近战攻击策略类、远程法术攻击策略类、潜行攻击策略类等。游戏角色类通过持有攻击策略接口的引用,在运行时动态选择相应策略。比如战士角色在初始化时可选择近战攻击策略,在战斗中能根据局势灵活切换,若遇到远程敌人,可临时切换为远程攻击策略(假设游戏设计允许),从而极大地增强了角色行为的灵活性与可扩展性。
再比如电商平台中的不同商品类,普通商品、限时折扣商品、团购商品,它们的销售行为有所不同。普通商品正常售卖,限时折扣商品在特定时间段以折扣价销售,团购商品需达到一定购买人数才生效。通过策略模式,分别创建普通销售策略类、限时折扣销售策略类、团购销售策略类,商品类通过关联相应策略,能轻松实现不同销售行为,并且在促销活动调整时,方便地切换销售策略。
动态算法选择场景
对于需要动态在几种算法中抉择的系统,策略模式堪称绝佳之选。在数据处理领域,以排序算法为例,常见的有冒泡排序、快速排序、归并排序等。不同场景对排序算法的性能要求各异,小规模数据可能适合冒泡排序,因其实现简单;而大规模数据则更适合快速排序或归并排序以提升效率。通过策略模式,我们将每种排序算法封装成一个具体策略类,如冒泡排序策略类、快速排序策略类、归并排序策略类,它们都实现统一的排序策略接口。在数据处理模块中,根据数据规模、数据特点等因素,动态选择合适的排序策略。例如,当处理 100 条以内的用户信息排序时,可选用冒泡排序策略;处理成千上万条交易数据排序时,则切换为快速排序策略,确保系统在不同情况下都能高效运行。
在图形渲染系统中,也存在类似情况。渲染 2D 图形和 3D 图形需要不同的渲染算法,2D 图形渲染可能采用简单的光栅化算法,3D 图形渲染则需更复杂的光线追踪算法等。通过策略模式,创建 2D 渲染策略类和 3D 渲染策略类,渲染引擎类根据要渲染的图形类型,动态调用相应的渲染策略,实现高效且灵活的图形渲染。
避免复杂条件语句场景
若一个对象存在大量行为,若不采用合适模式,多重条件选择语句将不可避免,导致代码冗长且难以维护。以电商系统的订单处理模块为例,订单有普通订单、加急订单、团购订单、退货订单等多种类型,每种订单的处理流程和规则大相径庭。传统做法可能是在订单处理方法中使用大量 if - else 或 switch 语句判断订单类型并执行相应处理逻辑,随着订单类型增多,代码会变得异常复杂。运用策略模式,我们创建普通订单处理策略类、加急订单处理策略类、团购订单处理策略类、退货订单处理策略类等,订单处理类通过持有订单处理策略接口的引用,根据订单实际类型调用对应的策略类方法进行处理。如此一来,代码结构清晰,新增订单类型时只需添加对应的策略类,无需大幅修改原有代码,维护成本大幅降低。
在物流配送系统中,配送订单也有多种类型,如同城配送订单、跨城配送订单、国际配送订单,每种订单的配送流程、费用计算、运输方式都不同。如果不使用策略模式,配送处理代码将充满复杂的条件判断。采用策略模式后,分别构建同城配送策略类、跨城配送策略类、国际配送策略类,配送系统根据订单类型调用相应策略,让代码简洁明了且易于扩展。
保密性与安全性场景
在保密性与安全性要求较高的场景下,策略模式同样发挥着重要作用。在金融加密领域,数据加密算法复杂且涉及敏感信息。例如有 AES 加密算法、RSA 加密算法等。通过策略模式,将这些加密算法分别封装在 AES 加密策略类、RSA 加密策略类中,客户端只需与加密策略接口交互,无需知晓复杂的算法细节与相关数据结构。在数据传输或存储前,根据安全级别等要求选择合适的加密策略。如普通用户数据可选用 AES 加密策略,涉及大额资金交易的数据则采用安全性更高的 RSA 加密策略,既保证了算法的保密性与安全性,又提升了系统对不同安全需求的适应性。
在企业级系统中,访问权限控制也可应用策略模式。不同级别的用户(如普通员工、部门经理、系统管理员)对系统资源有不同的访问权限。将权限验证算法封装在相应的策略类中,如普通员工权限策略类、部门经理权限策略类、系统管理员权限策略类。系统在用户登录访问资源时,根据用户角色调用对应的权限策略,确保敏感信息的安全访问,同时隐藏了复杂的权限验证逻辑。