目录
一、引言
某公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
1)学生票凭学生证可享受票价的8折优惠。
2)儿童可享受票价的6折优惠。
3)VIP用户可享受票价的5折优惠。
4)其他用户按票价正常收费。
本文的重点不在于代码的实现,而在于代码的设计,所以上述例子就尽可能的简单,方便代码的编写。接下来我们将上面的例子转化为代码。
二、优化前的代码
class MovieTicket {
public:
void studentDiscount() { _price *= 0.8; } //学生
void childDiscount() { _price *= 0.6; } //儿童
void normalPrice() { _price *= 1; } //正常
void vipDiscount() { _price *= 0.5; } //VIP
double getPrice() { return _price; }
private:
double _price;
};
核心代码不到10行,便可以将影院售票系统的功能全部实现了,客户端根据用户类型自行选择打折方式(也就是要调用哪个函数)即可。这个代码有问题吗?没有,但是不优雅。
1)如果需要新增打折方式或者修改现有的打折方式的话,需要直接修改现有类的代码。这就违反了面向对象设计的五大原则之一的开闭原则——对扩展开放,对修改关闭。
2)耦合度太高。算法直接嵌入到使用它的类中,导致业务逻辑与具体实现紧密耦合。我们追求的是高内聚,低耦合。
3)复用性差。相同的算法无法在不同上下文中复用,必须重新实现或复制代码。例如,支付处理中不同支付方式(信用卡、支付宝)的实现,若硬编码在订单类中会导致代码重复。
4)在一些情况下,我们并不希望对外暴露底层的算法实现。业务类你只需要通过调用提供的方法来处理好业务逻辑即可,不需要知道底层的算法实现。
上述这些问题,策略模式都能优雅的解决。
三、策略模式
策略模式的定义如下:
策略模式:定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户而变化。
Strategy Pattern:Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
这个定义挺抽象的,我们看看它的模式结构图。
策略模式包含3个角色。
1)Context(上下文类):用来处理业务逻辑的类。
2)Strategy(抽象策略类):为所要提供的算法声明抽象方法。
3)ConcreteStrategy(具体策略类):继承抽象策略类,然后重写抽象策略类中的方法。
综上,可以看到,策略模式就是要对算法进行封装,业务类只需要调用相应的算法就好了,算法的具体实现则交给抽象策略类的子类来完成。
典型代码:
首先定义一个抽象策略类,其中包含一个纯虚函数。
class AbstractStrategy {
public:
virtual void algorithm() = 0;//声明抽象方法
};
其次,定义具体策略类,继承自抽象策略类,然后每个具体策略类重写虚方法。
class ConcreteStrategy1 : public AbstractStrategy {
public:
void algorithm() override {
//算法1...
}
};
class ConcreteStrategy2 : public AbstractStrategy {
public:
void algorithm() override {
//算法2...
}
};
然后,定义一个上下文类来处理业务逻辑。
class Context {
public:
void setStrategy(AbstractStrategy* strategy) {
_strategy = strategy;
}
void doSomething() {
_strategy->algorithm();
}
private:
AbstractStrategy* _strategy;
};
最后,编写客户端的调用逻辑。
Context context;
AbstractStrategy* strategy = new ConcreteStrategy1();
context.setStrategy(strategy);
context.doSomething();
基于策略模式实现的代码就这样串联起来了。
四、优化后的代码
了解了策略模式后,我们来对前面影院售票系统的代码做优化。
class AbstractDiscount {
public:
virtual double calculate(double price) = 0; //声明抽象方法
};
class StudentDiscount : public AbstractDiscount {
public:
double calculate(double price) override {
//学生折扣
return price * 0.8;
}
};
class ChildDiscount : public AbstractDiscount {
public:
double calculate(double price) override {
//儿童折扣
return price * 0.6;
}
};
class NormalPrice : public AbstractDiscount {
public:
double calculate(double price) override {
//正常价格
return price;
}
};
class VipDiscount : public AbstractDiscount {
public:
double calculate(double price) override {
//VIP折扣
return price * 0.5;
}
};
class MovieTicket {
public:
void setDiscount(AbstractDiscount* discount) {
_discount = discount;
}
double getPrice(double price) {
return _discount->calculate(price);
}
private:
AbstractDiscount* _discount;
};
int main()
{
MovieTicket ticket;
AbstractDiscount* discount = new StudentDiscount();
ticket.setDiscount(discount);
double price = ticket.getPrice(100); //80
std::cout << "final price: " << price << std::endl;
return 0;
}
如果需要增加新的打折方式,原有代码无需改动,只需要增加一个新的折扣类作为抽象折扣类的子类,实现对应的打折方法即可。
五、策略模式总结
5.1 策略模式的优点
1)策略模式支持用户在不修改原有代码的基础上新增算法。
2)策略模式可以降低代码的耦合度,业务处理无需关心具体的算法实现。
5.2 策略模式的缺点
1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
2)策略模式将造成系统产生很多的具体策略类,每个细小的改动都要增加一个新的具体策略类。
5.3 策略模式的使用场景
1)一个系统需要动态的在多种算法中选择一种,那么可以将这些算法封装到一个个的具体策略类中,需要哪一个就调用哪一个。
2)不希望客户端知道具体的算法实现,只管调用。
六、结语
在写这篇文章前,看了一些大佬的文章,里面涉及到一些复杂的应用场景。但小编学识有限,还没有接触到这些复杂场景,很难说清楚,所以举的例子就尽可能的简单。同时,设计模式有很多种,它们不是孤立的,可以结合使用。
完~