【愚公系列】2021年12月 二十三种设计模式(二十一)-策略模式
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。
毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。
🚀一、策略模式(Stragety Pattern)
策略模式是一种行为型设计模式,它的核心思想是将一系列算法或行为封装成独立的策略类,使得这些策略可以相互替换,而不影响客户端代码的稳定性。这样做的好处是将行为和其所在的环境解耦,使得系统更加灵活、可维护,同时也支持算法的独立变化。
具体来说,策略模式包括以下要点:
封装算法:每个具体策略类封装了一个特定的算法或行为,这些算法可以在不同的策略类中实现。
客户端与策略解耦:客户端不需要知道或关心具体的策略实现,它只需要与策略接口或抽象类交互,从而将算法的选择与使用分离开来。
动态切换策略:客户端可以在运行时动态选择不同的策略,以满足不同的需求或情境,而不需要修改现有代码。
支持扩展:添加新的策略类或修改现有策略类都不会影响客户端的稳定性,这使得系统更具可扩展性。
策略模式是一种有助于将算法独立于其使用者的设计模式,它将行为封装在各个策略类中,使得系统更加灵活、可维护,并支持动态切换不同的算法。这种模式有助于优化代码结构,提高代码的可读性和可维护性。
🚀二、使用步骤
🔎1.角色
🦋1.1 抽象策略(Strategy)
策略模式(Strategy Pattern)中的抽象策略(Strategy)是这个模式的核心概念,它定义了一组算法或行为的抽象接口,以及所有具体策略类都必须实现的方法。抽象策略用于定义策略模式的统一接口,以确保所有具体策略类都具备相同的方法签名,从而使这些策略类可以互相替代。
抽象策略的作用包括:
定义共同接口:抽象策略为具体策略类定义了一组公共方法或行为接口,这些方法规定了策略类必须提供的功能。这有助于确保不同策略类具备一致的接口,从而实现了替代性。
实现多态性:通过抽象策略,客户端可以以统一的方式与不同的策略类交互。这允许客户端在运行时选择不同的具体策略,以满足不同的需求,而无需修改客户端代码。
解耦算法和客户端:抽象策略将算法的实现与客户端代码解耦,客户端只需要知道如何使用抽象策略,而不需要关心具体策略的细节。这有助于提高系统的可维护性和扩展性。
支持策略的独立变化:由于抽象策略定义了一致的接口,所以可以轻松地添加新的具体策略类或修改现有策略类的实现,而不会对客户端产生负面影响。
抽象策略在策略模式中充当了一个接口的角色,定义了策略类必须实现的方法,确保了策略类之间的一致性,并使得客户端代码与具体策略的实现相分离,从而实现了算法和客户端的解耦。这种设计有助于提高代码的可维护性、可扩展性和可读性,同时支持动态切换不同的策略。
🦋1.2 具体策略(Concrete Strategy)
策略模式(Strategy Pattern)中的具体策略(Concrete Strategy)是实现抽象策略(Strategy)接口或继承抽象策略类的具体类。每个具体策略类都代表了一种不同的算法或行为,它们实现了抽象策略中定义的方法,并为系统提供了多个可替代的算法选择。
具体策略的概念和作用包括:
实现具体算法:具体策略类实现了抽象策略中定义的算法或行为,具体描述了策略在特定情境下的实际操作。
提供替代选择:每个具体策略类都代表了一种不同的策略或算法,客户端可以在运行时选择合适的具体策略来执行特定的任务。
多态性:具体策略类通过实现相同的接口或继承相同的抽象类,使得它们可以以一致的方式与客户端交互。这种多态性使得客户端可以在不修改其代码的情况下切换不同的策略。
解耦算法和客户端:具体策略类将算法的实现与客户端代码解耦,客户端只需要知道如何选择和使用具体策略,而不需要关心策略的具体实现细节。
支持策略的动态切换:由于客户端可以在运行时选择不同的具体策略,策略模式支持策略的动态切换,从而使系统更加灵活。
可扩展性:策略模式允许随时添加新的具体策略类,以满足新的需求或场景,而不会对现有代码造成影响。
具体策略在策略模式中代表了具体的算法或行为实现,它们提供了多个可替代的策略选择,使得客户端可以根据需要动态选择不同的策略,从而实现了算法和客户端的解耦,提高了系统的可维护性、可扩展性,并支持动态切换行为。这种模式有助于使代码更具灵活性和可复用性。
🦋1.3 环境类(Context)
策略模式(Strategy Pattern)中的环境类(Context)是一个重要的角色,它担当着管理和使用具体策略(Concrete Strategy)的责任。环境类在策略模式中扮演着协调者的角色,负责与具体策略交互、委派任务以及控制策略的切换。以下是环境类的概念和作用:
封装策略:环境类包含一个策略对象,它将具体策略类的实例封装在内部。这样,客户端不需要直接与具体策略类交互,而是通过环境类来访问策略。
委派行为:环境类将客户端的请求委派给当前选定的具体策略对象来执行。这意味着客户端可以调用环境类的方法,而不必担心具体策略的选择和执行。
动态切换策略:环境类允许在运行时动态切换具体策略。这意味着客户端可以更改当前使用的策略,以满足不同的需求或情境,而不需要修改客户端代码。
传递上下文信息:环境类可以向具体策略传递上下文信息,使策略能够根据需要访问环境的状态或数据,从而执行相应的算法。
解耦客户端和具体策略:环境类的存在使客户端代码与具体策略的实现解耦。客户端只需要与环境类交互,而不需要了解策略的细节。
支持多种策略:环境类可以管理多个具体策略对象,根据需要在它们之间进行切换,从而允许系统支持多种不同的算法或行为。
环境类在策略模式中充当了一个管理和协调策略的角色。它封装了策略对象,委派任务给具体策略,支持动态切换策略,传递上下文信息,并使客户端与具体策略解耦。这种设计模式有助于提高代码的可维护性、可扩展性,并使系统更加灵活,能够根据需求选择不同的策略来执行特定任务。
🔎2.示例
命名空间StragetyPattern中包含策略基类Tax以及它的8个实现类,Context环境类持有策略基类。本示例通过一个优雅的方式来计算个人所得税。
public abstract class Tax {
protected decimal TaxRate = 0;
protected decimal QuickDeduction = 0;
public virtual decimal Calculate(decimal income) {
return income * TaxRate - QuickDeduction;
}
}
策略基类Tax,表示个人所得税,TaxRate为税率,QuickDeduction为速算扣除数,Calculate计算相应收入的个人所得税。
public class Level0 : Tax {
public Level0() {
TaxRate = 0.00m;
QuickDeduction = 0;
}
}
0级个人所得税阶梯,表示个人所得税的初始状态。
public class Level1 : Tax {
public Level1() {
TaxRate = 0.03m;
QuickDeduction = 0;
}
}
1级个人所得税阶梯。
public class Level2 : Tax {
public Level2() {
TaxRate = 0.10m;
QuickDeduction = 105;
}
}
2级个人所得税阶梯。
public class Level3 : Tax {
public Level3() {
TaxRate = 0.20m;
QuickDeduction = 555;
}
}
3级个人所得税阶梯。
public class Level4 : Tax {
public Level4() {
TaxRate = 0.25m;
QuickDeduction = 1005;
}
}
4级个人所得税阶梯。
public class Level5 : Tax {
public Level5() {
TaxRate = 0.30m;
QuickDeduction = 2755;
}
}
5级个人所得税阶梯。
public class Level6 : Tax {
public Level6() {
TaxRate = 0.35m;
QuickDeduction = 5505;
}
}
6级个人所得税阶梯。
public class Level7 : Tax {
public Level7() {
TaxRate = 0.45m;
QuickDeduction = 13505;
}
}
7级个人所得税阶梯。
public class Context {
private Tax _tax = null;
private const decimal EXEMPTION_VALUE = 3500m;
private List<decimal> _taxLevel = new List<decimal>{
0,
1500,
4500,
9000,
35000,
55000,
80000,
decimal.MaxValue
};
private List<Type> _levels = new List<Type>();
private void GetLevels() {
_levels = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(tp => tp.GetTypes()
.Where(t => t.BaseType == typeof(Tax)))
.ToList();
}
public Context() {
GetLevels();
}
public Context Calculate(decimal income) {
_tax = new Level0();
var result = income - EXEMPTION_VALUE;
for(int i = 1; i <= _taxLevel.Count - 1; i++) {
if(result > _taxLevel[i - 1] && result <= _taxLevel[i]) {
_tax = (Tax)Activator.CreateInstance(_levels[i]);
}
}
Console.WriteLine($"Income = {income}," + $"tax = {_tax.Calculate(result)}!");
return this;
}
}
环境类Context,首先需要维持对Tax的引用,EXEMPTION_VALUE表示免征额(本例使用3500元),之后通过反射和一些技巧选择相应的Tax实现类来计算相应阶梯的个人所得税。
public class Program {
private static Context _context = new Context();
public static void Main(string[] args) {
_context.Calculate(2500.00m)
.Calculate(4900.00m)
.Calculate(5500.00m)
.Calculate(7000.00m)
.Calculate(10000.00m)
.Calculate(16000.00m)
.Calculate(43000.00m)
.Calculate(70000.00m)
.Calculate(100000.00m)
.Calculate(4500.00m)
.Calculate(1986.00m);
Console.ReadKey();
}
}
以上是调用方的代码,Calculate经过特殊处理以支持方法链。以下是这个案例的输出结果:
Income = 2500.00,tax = 0.0000!
Income = 4900.00,tax = 42.0000!
Income = 5500.00,tax = 95.0000!
Income = 7000.00,tax = 245.0000!
Income = 10000.00,tax = 745.0000!
Income = 16000.00,tax = 2120.0000!
Income = 43000.00,tax = 9095.0000!
Income = 70000.00,tax = 17770.0000!
Income = 100000.00,tax = 29920.0000!
Income = 4500.00,tax = 30.0000!
Income = 1986.00,tax = 0.0000!
🚀总结
🔎1.优点
策略模式(Strategy Pattern)具有以下优点:
多重算法支持:策略模式允许在运行时选择不同的策略或算法来执行特定任务,从而提供了多种算法的支持。这使得系统更加灵活,能够根据需求选择合适的策略。
解耦算法和客户端:策略模式将具体策略的实现与客户端代码分离开来。客户端只需要知道如何选择和使用策略,而不需要关心策略的具体实现细节。这降低了客户端与策略的耦合度,增加了代码的可维护性和可读性。
可扩展性:由于策略模式支持添加新的具体策略类,系统可以轻松地扩展以支持新的算法或行为。这使得系统更具可扩展性,可以适应未来的变化和需求。
单一职责原则:策略模式遵循单一职责原则,每个具体策略类只负责实现一种算法或行为。这使得每个类的职责清晰明确,易于维护和测试。
代码复用:具体策略类可以在不同的上下文中重复使用,从而提高了代码的复用性。同样的策略可以在不同的环境中使用,而不需要重新实现。
测试容易:由于每个具体策略类都可以单独测试,策略模式使测试变得更加容易。每个策略的测试可以独立进行,有助于确保每种策略的正确性。
更好的可维护性:策略模式将算法与业务逻辑分开,使系统更容易理解和维护。当需要修改或优化某个策略时,只需修改具体策略类而不会影响其他部分的代码。
策略模式是一种有利于提高代码灵活性、可维护性和可扩展性的设计模式。它允许动态选择算法,将责任分散到具体策略类中,减少了代码的耦合度,使系统更易于维护和测试。这使得策略模式在需要处理多种算法或行为的情况下非常有用。
🔎2.缺点
策略模式(Strategy Pattern)虽然有很多优点,但也存在一些缺点,需要考虑:
类数量增加:策略模式会引入多个具体策略类,每个策略对应一个具体的算法或行为。如果策略较多,可能会导致类的数量增加,使代码变得更加复杂。
客户端必须了解策略:虽然策略模式降低了客户端与具体策略的耦合度,但客户端仍然需要了解可用的策略并选择合适的策略。这可能需要额外的逻辑来进行策略选择,增加了客户端的复杂性。
上下文对象的状态管理:如果上下文对象需要维护一些状态,以便在不同的策略之间共享,可能需要额外的管理工作。这可能会增加复杂性,并引入潜在的错误。
运行时性能开销:在运行时选择策略需要一定的开销,因为需要动态查找和创建具体策略对象。对于一些性能敏感的应用,这可能会引入一定的性能开销。
不适合简单情景:策略模式通常在需要处理多种算法或行为的情况下才有意义。对于简单的情景,引入策略模式可能会显得过于复杂,增加了不必要的复杂性。
维护成本:当系统中的策略较多或策略的变化频繁时,维护多个具体策略类可能会变得复杂,需要更多的时间和精力。
策略模式是一种有用的设计模式,但在某些情况下可能不是最佳选择。开发人员需要根据具体的应用场景和需求来权衡策略模式的优点和缺点,以确定是否使用它。对于简单的情景,可能存在更简单的解决方案,而对于复杂的情景,策略模式可以提供更好的灵活性和可维护性。
🔎3.使用场景
策略模式(Strategy Pattern)适用于以下场景:
多算法选择:当一个系统需要在不同情况下使用不同的算法或策略时,策略模式是一个合适的选择。例如,一个电商网站可以根据不同的促销活动选择不同的价格计算策略。
避免条件语句:策略模式可以帮助避免大量的条件语句。如果你的代码中存在很多
if-else
语句来选择不同的行为,可以考虑使用策略模式来替代这些条件逻辑。算法的自由切换:策略模式允许在运行时动态切换算法,这对于需要根据用户输入或其他条件来选择算法的应用非常有用。例如,一个游戏可以根据玩家选择的角色来切换不同的战斗策略。
单一职责原则:策略模式有助于遵循单一职责原则,每个具体策略类只负责实现一种算法或行为。
可扩展性:如果你的系统需要支持新的算法或策略的添加,策略模式提供了一种灵活的方式来扩展系统而无需修改现有代码。
测试和维护:策略模式使测试变得更加容易,因为每个具体策略可以独立测试。此外,维护也更加方便,因为不同策略的修改不会影响其他部分的代码。
框架和库设计:策略模式在设计框架和库时常常用到,因为它提供了一种可扩展的方式来让用户自定义行为。例如,一个图形绘制库可以使用策略模式来定义不同的绘制策略。
策略模式适用于需要在运行时选择不同算法或行为的情况,以及需要降低条件语句复杂度、遵循单一职责原则、提高可扩展性和可维护性的场景。当系统需要支持多种策略或算法时,策略模式是一个有力的设计模式。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)