策略模式
策略模式是一种行为设计模式,它定义了一系列算法,将每个算法封装到一个类中,并使它们可以相互替换。策略模式让算法独立于使用它的客户端。
现实中的例子
假设我们经营一家电影院,目前是销售淡季,我们推出了各种折扣,学生票八折,儿童票九折,VIP会员半价。我们要设计一个系统,根据不同的折扣算法计算电影票的价格。
不假思索的你可能写下了如下代码:
1 | enum UserType { |
我们来看看这段代码有什么问题:
- 违反了开闭原则。如果我们要添加新的折扣类型,比如老年人票,就需要修改
MovieTicket
类,违反了开闭原则。 - 违反了单一职责原则。
MovieTicket
类不仅负责计算价格,还负责判断折扣类型,违反了单一职责原则。 - 耦合性太高,不易维护。如果折扣算法发生变化,需要修改
MovieTicket
类,不易维护。 - 其他类如果想要使用折扣算法的话,只能复制粘贴一份,导致代码重复。
下面我们通过策略模式来重构这段代码,策略模式的精髓就是将算法和使用算法的类解耦,使得算法可以独立于使用它的类进行变化。
完整代码
1 | interface Discount { |
代码解析:
- 首先定义一个接口
Discount
,它有一个getDiscount
方法,返回折扣值。所有具体的折扣类都要实现这个接口。 - 然后定义三个具体的折扣类:
StudentDiscount
、ChildrenDiscount
和VIPDiscount
。在每个折扣类中,我们实现了getDiscount
方法,返回不同的折扣值。 - 最后定义一个
MovieTicket
类,它有一个ticketPrice
属性,表示电影票的价格,还有一个discount
属性,表示折扣。我们定义了一个setDiscount
方法,用来设置折扣。getPrice
方法用来计算折扣后的价格。
这种实现使得折扣算法和使用折扣的类解耦,使得折扣算法可以独立于使用它的类进行变化。好处如下:
- 便于扩展,可以很容易地添加新的折扣类。只要这个类实现了
Discount
接口,就可以被MovieTicket
类使用。 - 由于折扣算法和使用折扣的类解耦,所以折扣算法可以独立于使用它的类进行变化。如果折扣算法发生变化,只需要修改折扣类即可,不需要修改使用折扣的类, 比如儿童票从9折改成8折,只需要修改
ChildrenDiscount
类使其返回0.8即可。 - 如果有其他类想使用折扣算法的话,也可以直接使用。
以上代码其实使用了依赖注入的方式,MovieTicket
类依赖于Discount
接口,我们通过Setter的方式将具体的折扣类注入到MovieTicket
类中。
注意这里discount
属性是可选的,因为我们在getPrice
方法中判断了discount
是否存在,如果不存在就抛出异常。这样做是为了防止忘记设置折扣而导致的错误。如果不设置为可选的话会报错:Property 'discount' has no initializer and is not definitely assigned in the constructor.
由于TypeScript配置文件默认开启了strictPropertyInitialization
选项导致的。
策略模式UML图
Context
:消费策略的类相当于上例中的MovieTicket
类,内涵一个抽象策略类,相当于上例中的Discount
接口。Strategy
:抽象策略类,相当于上例中的Discount
接口。ConcreteStrategy
:具体策略类,相当于上例中的StudentDiscount
、ChildrenDiscount
和VIPDiscount
类。execute
:执行策略的方法,相当于上例中的getDiscount
方法。
策略模式的应用场景
- Sorting Algorithms: If you have a collection of data that can be sorted in several ways, you can use the Strategy pattern to switch between sorting algorithms (like quicksort, merge sort, heap sort) at runtime.
- Payment Methods: In an e-commerce application, you can use the Strategy pattern to switch between different payment methods (like credit card, PayPal, Bitcoin) at runtime.
- Compression Algorithms: If you have a file that can be compressed using different algorithms (like zip, rar, 7z), you can use the Strategy pattern to choose the compression algorithm at runtime.
- Travel Planning: If you have a travel planning application, you can use the Strategy pattern to switch between different travel strategies (like by car, by train, by plane) at runtime.