Pattern definition
Define a series of algorithms, encapsulate them one by one, and make them replaceable (changeable) with each other. This pattern allows the algorithm to change (expand, subclass) independently of the client program using it.
Application scenario
- In the process of software construction, the algorithms used by some objects may be diverse and often change. If these algorithms are encoded into objects, the objects will become extremely complex; And sometimes supporting unused algorithms is also a performance burden.
- How to transparently change the algorithm of an object as needed at run time? Decouple the algorithm from the object itself to avoid the above problems?
Example demonstration (C + +)
Calculation of tax law
General method (structured)
SalesOrder.h
enum TaxBase{ CN_Tax, US_Tax, DE_Tax }; class SalesOrder{ private: TaxBase tax; public: double CalculateTax(); };
SalesOrder.cpp
double SalesOrder::CalculateTax(){ //Omit early work if(tax == CN_Tax){ //The tax is calculated according to the tax law of China } else if(tax == US_Tax){ //Tax is calculated according to the tax law of the United States } else if(tax == DE_Tax){ //Tax is calculated according to German tax law } }
When you need to add a new country, you need to modify the enumeration type and add an if statement
Design with template method
TaxStrategy.h
//In formal development, each class should have its own independent files class TaxStrategy{ public: virtual double Calculate(const Context& context) = 0; virtual ~TaxStrategy(); } class CNTax : public TaxStrategy{ public: virtual double Calculate(const Context& context); } class USTax : public TaxStrategy{ public: virtual double Calculate(const Context& context); } class DETax : public TaxStrategy{ public: virtual double Calculate(const Context& context); }
TaxStrategy.cpp
TaxStrategy::~TaxStrategy(){} double CNTax::Calculate(const Context& context){ //The tax is calculated according to the tax law of China } double USTax::Calculate(const Context& context){ //Tax is calculated according to the tax law of the United States } double DETax::Calculate(const Context& context){ //Tax is calculated according to German tax law }
SalesOrder.h
class SalesOrder{ private: TaxStrategy * strategy = nullptr; public: //With factory method, the structure is better SalesOrder(StrategyFactory* strategyFactory); ~SalesOrder(); double CalculateTax(); }
SalesOrder.cpp
SalesOrder::SalesOrder(StrategyFactory* strategyFactory) { this->strategy = strategyFactory->NewStrategy(); } SalesOrder::~SalesOrder(){ if(nullptr != this->strategy){ delete this->strategy; this->strategy = nullptr; } } double SalesOrder::CalculateTax(){ //Omit preliminary preparation Context context(); //Polymorphic call, which depends on the object type returned by StrategyFactory double val = strategy->Calculate(context); //Omit post-processing }
Comparison of two methods
Structured
- if else is a divide and rule behavior in the structured era
- if else statements need to be added continuously, it will often affect some previous codes and lead to bugs
- When there are if else statements, the if statements actually need to be used are few and relatively fixed (for example, language, we generally use simplified Chinese). Most if statements do not need to be used, but they will be judged every time they run, which will undoubtedly bring performance burden. Because the code that needs to be run will be loaded into the CPU cache, a large number of invalid code will lead to insufficient cache, while other code that needs to be executed can only be forced to be stored in other locations, resulting in the decline of CPU performance.
object-oriented
- When the runtime passes in polymorphic objects, the runtime supports polymorphic calls
- To add a country's tax calculation, you need to add a subclass (change), modify the factory method mode (change), and pass in a new subclass object when calling the structure function. At this time, the content of the Calculate has not changed at all (stable), which is to separate the change from stability and follow the open and closed principle
summary
- If if else will continue to be added in the future, replace the if else statement with the policy pattern
- If if else does not increase or change (for example, if you judge the day of the week, you will judge it for 7 times), use if else
Class diagram
Stable part
- Context::ContextInterface()
Changing parts
- Subclass:: AlgorithmInterface()
Note: there is only one function, so in order to be simple, in practice, the function should be added according to the demand
Summary of key points
- Strategy and its subclasses provide a series of reusable algorithms for components, so that types can be easily switched between various algorithms as needed at run time.
- Strategy mode provides an alternative to conditional judgment statements. Eliminating conditional judgment statements is decoupling. Code with many conditional statements usually requires the strategy pattern.
- If the Strategy object has no instance variables, each context can share the same Strategy object, thus saving object overhead.
multiplexing
- Real reuse refers to the reusability at the binary level
- Copying and pasting a piece of source code is called pasting source code