namespaceRefactoringGuru.DesignPatterns.Strategy.Conceptual { // The Context defines the interface of interest to clients. classContext { // The Context maintains a reference to one of the Strategy objects. The // Context does not know the concrete class of a strategy. It should // work with all strategies via the Strategy interface. private IStrategy _strategy;
publicContext() { }
// Usually, the Context accepts a strategy through the constructor, but // also provides a setter to change it at runtime. publicContext(IStrategy strategy) { this._strategy = strategy; }
// Usually, the Context allows replacing a Strategy object at runtime. publicvoidSetStrategy(IStrategy strategy) { this._strategy = strategy; }
// The Context delegates some work to the Strategy object instead of // implementing multiple versions of the algorithm on its own. publicvoidDoSomeBusinessLogic() { Console.WriteLine("Context: Sorting data using the strategy (not sure how it'll do it)"); var result = this._strategy.DoAlgorithm(new List<string> { "a", "b", "c", "d", "e" });
string resultStr = string.Empty; foreach (var element in result as List<string>) { resultStr += element + ","; }
Console.WriteLine(resultStr); } }
// The Strategy interface declares operations common to all supported // versions of some algorithm. // // The Context uses this interface to call the algorithm defined by Concrete // Strategies. publicinterfaceIStrategy { objectDoAlgorithm(object data); }
// Concrete Strategies implement the algorithm while following the base // Strategy interface. The interface makes them interchangeable in the // Context. classConcreteStrategyA : IStrategy { publicobjectDoAlgorithm(object data) { var list = data as List<string>; list.Sort();
return list; } }
classConcreteStrategyB : IStrategy { publicobjectDoAlgorithm(object data) { var list = data as List<string>; list.Sort(); list.Reverse();
return list; } }
classProgram { staticvoidMain(string[] args) { // The client code picks a concrete strategy and passes it to the // context. The client should be aware of the differences between // strategies in order to make the right choice. var context = new Context();
Console.WriteLine("Client: Strategy is set to normal sorting."); context.SetStrategy(new ConcreteStrategyA()); context.DoSomeBusinessLogic(); Console.WriteLine(); Console.WriteLine("Client: Strategy is set to reverse sorting."); context.SetStrategy(new ConcreteStrategyB()); context.DoSomeBusinessLogic(); } } }
Output.txt: 执行结果
1 2 3 4 5 6 7
Client: Strategy is set to normal sorting. Context: Sorting data using the strategy (not sure how it'll do it) a,b,c,d,e
Client: Strategy is set to reverse sorting. Context: Sorting data using the strategy (not sure how it'll do it) e,d,c,b,a
在 C++ 中使用模式
复杂度: ★☆☆
流行度: ★★★
使用示例: 策略模式在 C++ 代码中很常见。 它经常在各种框架中使用, 能在不扩展类的情况下向用户提供改变其行为的方式。
/** * The Strategy interface declares operations common to all supported versions * of some algorithm. * * The Context uses this interface to call the algorithm defined by Concrete * Strategies. */ classStrategy { public: virtual ~Strategy() {} virtual std::string DoAlgorithm(const std::vector<std::string> &data)const= 0; };
/** * The Context defines the interface of interest to clients. */
classContext { /** * @var Strategy The Context maintains a reference to one of the Strategy * objects. The Context does not know the concrete class of a strategy. It * should work with all strategies via the Strategy interface. */ private: Strategy *strategy_; /** * Usually, the Context accepts a strategy through the constructor, but also * provides a setter to change it at runtime. */ public: Context(Strategy *strategy = nullptr) : strategy_(strategy) { } ~Context() { deletethis->strategy_; } /** * Usually, the Context allows replacing a Strategy object at runtime. */ voidset_strategy(Strategy *strategy) { deletethis->strategy_; this->strategy_ = strategy; } /** * The Context delegates some work to the Strategy object instead of * implementing +multiple versions of the algorithm on its own. */ voidDoSomeBusinessLogic()const { // ... std::cout << "Context: Sorting data using the strategy (not sure how it'll do it)\n"; std::string result = this->strategy_->DoAlgorithm(std::vector<std::string>{"a", "e", "c", "b", "d"}); std::cout << result << "\n"; // ... } };
/** * Concrete Strategies implement the algorithm while following the base Strategy * interface. The interface makes them interchangeable in the Context. */ classConcreteStrategyA : public Strategy { public: std::string DoAlgorithm(const std::vector<std::string> &data)constoverride { std::string result; std::for_each(std::begin(data), std::end(data), [&result](const std::string &letter) { result += letter; }); std::sort(std::begin(result), std::end(result));
return result; } }; classConcreteStrategyB : public Strategy { std::string DoAlgorithm(const std::vector<std::string> &data)constoverride { std::string result; std::for_each(std::begin(data), std::end(data), [&result](const std::string &letter) { result += letter; }); std::sort(std::begin(result), std::end(result)); for (int i = 0; i < result.size() / 2; i++) { std::swap(result[i], result[result.size() - i - 1]); }
return result; } }; /** * The client code picks a concrete strategy and passes it to the context. The * client should be aware of the differences between strategies in order to make * the right choice. */
voidClientCode() { Context *context = newContext(new ConcreteStrategyA); std::cout << "Client: Strategy is set to normal sorting.\n"; context->DoSomeBusinessLogic(); std::cout << "\n"; std::cout << "Client: Strategy is set to reverse sorting.\n"; context->set_strategy(new ConcreteStrategyB); context->DoSomeBusinessLogic(); delete context; }
intmain() { ClientCode(); return0; }
Output.txt: 执行结果
1 2 3 4 5 6 7
Client: Strategy is set to normal sorting. Context: Sorting data using the strategy (not sure how it'll do it) abcde
Client: Strategy is set to reverse sorting. Context: Sorting data using the strategy (not sure how it'll do it) edcba
/** * Order class. Doesn't know the concrete payment method (strategy) user has * picked. It uses common strategy interface to delegate collecting payment data * to strategy object. It can be used to save order to database. */ publicclassOrder { privateinttotalCost=0; privatebooleanisClosed=false;
publicvoidprocessOrder(PayStrategy strategy) { strategy.collectPaymentDetails(); // Here we could collect and store payment data from the strategy. }
// Client creates different strategies based on input from user, // application configuration, etc. if (paymentMethod.equals("1")) { strategy = newPayByPayPal(); } else { strategy = newPayByCreditCard(); } }
// Order object delegates gathering payment data to strategy object, // since only strategies know what data they need to process a // payment. order.processOrder(strategy);
System.out.print("Pay " + order.getTotalCost() + " units or Continue shopping? P/C: "); Stringproceed= reader.readLine(); if (proceed.equalsIgnoreCase("P")) { // Finally, strategy handles the payment. if (strategy.pay(order.getTotalCost())) { System.out.println("Payment has been successful."); } else { System.out.println("FAIL! Please, check your data."); } order.setClosed(); } } } }
Please, select a product: 1 - Mother board 2 - CPU 3 - HDD 4 - Memory 1 Count: 2 Do you wish to continue selecting products? Y/N: y Please, select a product: 1 - Mother board 2 - CPU 3 - HDD 4 - Memory 2 Count: 1 Do you wish to continue selecting products? Y/N: n Please, select a payment method: 1 - PalPay 2 - Credit Card 1 Enter the user's email: user@example.com Enter the password: qwerty Wrong email or password! Enter user email: amanda@ya.com Enter password: amanda1985 Data verification has been successful. Pay 6250 units or Continue shopping? P/C: p Paying 6250 using PayPal. Payment has been successful.
/** * The Context defines the interface of interest to clients. */ classContext { /** * @var Strategy The Context maintains a reference to one of the Strategy * objects. The Context does not know the concrete class of a strategy. It * should work with all strategies via the Strategy interface. */ private$strategy;
/** * Usually, the Context accepts a strategy through the constructor, but also * provides a setter to change it at runtime. */ publicfunction__construct(Strategy $strategy) { $this->strategy = $strategy; }
/** * Usually, the Context allows replacing a Strategy object at runtime. */ publicfunctionsetStrategy(Strategy $strategy) { $this->strategy = $strategy; }
/** * The Context delegates some work to the Strategy object instead of * implementing multiple versions of the algorithm on its own. */ publicfunctiondoSomeBusinessLogic(): void { // ...
echo"Context: Sorting data using the strategy (not sure how it'll do it)\n"; $result = $this->strategy->doAlgorithm(["a", "b", "c", "d", "e"]); echoimplode(",", $result) . "\n";
// ... } }
/** * The Strategy interface declares operations common to all supported versions * of some algorithm. * * The Context uses this interface to call the algorithm defined by Concrete * Strategies. */ interfaceStrategy { publicfunctiondoAlgorithm(array$data): array; }
/** * Concrete Strategies implement the algorithm while following the base Strategy * interface. The interface makes them interchangeable in the Context. */ classConcreteStrategyAimplementsStrategy { publicfunctiondoAlgorithm(array$data): array { sort($data);
/** * The client code picks a concrete strategy and passes it to the context. The * client should be aware of the differences between strategies in order to make * the right choice. */ $context = newContext(newConcreteStrategyA()); echo"Client: Strategy is set to normal sorting.\n"; $context->doSomeBusinessLogic();
echo"\n";
echo"Client: Strategy is set to reverse sorting.\n"; $context->setStrategy(newConcreteStrategyB()); $context->doSomeBusinessLogic();
Output.txt: 执行结果
1 2 3 4 5 6 7
Client: Strategy is set to normal sorting. Context: Sorting data using the strategy (not sure how it'll do it) a,b,c,d,e
Client: Strategy is set to reverse sorting. Context: Sorting data using the strategy (not sure how it'll do it) e,d,c,b,a
/** * This is the router and controller of our application. Upon receiving a * request, this class decides what behavior should be executed. When the app * receives a payment request, the OrderController class also decides which * payment method it should use to process the request. Thus, the class acts as * the Context and the Client at the same time. */ classOrderController { /** * Handle POST requests. * * @param $url * @param $data * @throws \Exception */ publicfunctionpost(string$url, array$data) { echo"Controller: POST request to $url with " . json_encode($data) . "\n";
// The payment method (strategy) is selected according to the value // passed along with the request. $paymentMethod = PaymentFactory::getPaymentMethod($matches[2]);
/** * POST /order {data} */ publicfunctionpostNewOrder(array$data): void { $order = newOrder($data); echo"Controller: Created the order #{$order->id}.\n"; }
/** * GET /order/123/payment/XX */ publicfunctiongetPayment(PaymentMethod $method, Order $order, array$data): void { // The actual work is delegated to the payment method object. $form = $method->getPaymentForm($order); echo"Controller: here's the payment form:\n"; echo$form . "\n"; }
/** * GET /order/123/payment/XXX/return?key=AJHKSJHJ3423&success=true */ publicfunctiongetPaymentReturn(PaymentMethod $method, Order $order, array$data): void { try { // Another type of work delegated to the payment method. if ($method->validateReturn($order, $data)) { echo"Controller: Thanks for your order!\n"; $order->complete(); } } catch (\Exception$e) { echo"Controller: got an exception (" . $e->getMessage() . ")\n"; } } }
/** * A simplified representation of the Order class. */ classOrder { /** * For the sake of simplicity, we'll store all created orders here... * * @var array */ privatestatic$orders = [];
/** * ...and access them from here. * * @param int $orderId * @return mixed */ publicstaticfunctionget(int$orderId = null) { if ($orderId === null) { returnstatic::$orders; } else { returnstatic::$orders[$orderId]; } }
/** * The Order constructor assigns the values of the order's fields. To keep * things simple, there is no validation whatsoever. * * @param array $attributes */ publicfunction__construct(array$attributes) { $this->id = count(static::$orders); $this->status = "new"; foreach ($attributesas$key => $value) { $this->{$key} = $value; } static::$orders[$this->id] = $this; }
/** * The method to call when an order gets paid. */ publicfunctioncomplete(): void { $this->status = "completed"; echo"Order: #{$this->id} is now {$this->status}."; } }
/** * This class helps to produce a proper strategy object for handling a payment. */ classPaymentFactory { /** * Get a payment method by its ID. * * @param $id * @return PaymentMethod * @throws \Exception */ publicstaticfunctiongetPaymentMethod(string$id): PaymentMethod { switch ($id) { case"cc": returnnewCreditCardPayment(); case"paypal": returnnewPayPalPayment(); default: thrownew\Exception("Unknown Payment Method"); } } }
/** * The Strategy interface describes how a client can use various Concrete * Strategies. * * Note that in most examples you can find on the Web, strategies tend to do * some tiny thing within one method. However, in reality, your strategies can * be much more robust (by having several methods, for example). */ interfacePaymentMethod { publicfunctiongetPaymentForm(Order $order): string;
/** * This Concrete Strategy provides a payment form and validates returns for * credit card payments. */ classCreditCardPaymentimplementsPaymentMethod { staticprivate$store_secret_key = "swordfish";
echo"\nClient: I'd like to pay for the second, show me the payment form\n";
$controller->get("/order/1/payment/paypal");
echo"\nClient: ...pushes the Pay button...\n"; echo"\nClient: Oh, I'm redirected to the PayPal.\n"; echo"\nClient: ...pays on the PayPal...\n"; echo"\nClient: Alright, I'm back with you, guys.\n";
Client: Let's create some orders Controller: POST request to /orders with {"email":"me@example.com","product":"ABC Cat food (XL)","total":9.95} Controller: Created the order #0. Controller: POST request to /orders with {"email":"me@example.com","product":"XYZ Cat litter (XXL)","total":19.95} Controller: Created the order #1.
Client: List my orders, please Controller: GET request to /orders Controller: Here's all orders: { "id": 0, "status": "new", "email": "me@example.com", "product": "ABC Cat food (XL)", "total": 9.95 } { "id": 1, "status": "new", "email": "me@example.com", "product": "XYZ Cat litter (XXL)", "total": 19.95 }
Client: I'd like to pay for the second, show me the payment form Controller: GET request to /order/1/payment/paypal Controller: here's the payment form: <form action="https://paypal.com/payment" method="POST"> <input type="hidden" id="email" value="me@example.com"> <input type="hidden" id="total" value="19.95"> <input type="hidden" id="returnURL" value="https://our-website.com/order/1/payment/paypal/return"> <input type="submit" value="Pay on PayPal"> </form>
Client: ...pushes the Pay button...
Client: Oh, I'm redirected to the PayPal.
Client: ...pays on the PayPal...
Client: Alright, I'm back with you, guys. Controller: GET request to /order/1/payment/paypal/return?key=c55a3964833a4b0fa4469ea94a057152&success=true&total=19.95 PayPalPayment: ...validating... Done! Controller: Thanks for your order! Order: #1 is now completed.
from __future__ import annotations from abc import ABC, abstractmethod from typing importList
classContext(): """ The Context defines the interface of interest to clients. """
def__init__(self, strategy: Strategy) -> None: """ Usually, the Context accepts a strategy through the constructor, but also provides a setter to change it at runtime. """
self._strategy = strategy
@property defstrategy(self) -> Strategy: """ The Context maintains a reference to one of the Strategy objects. The Context does not know the concrete class of a strategy. It should work with all strategies via the Strategy interface. """
return self._strategy
@strategy.setter defstrategy(self, strategy: Strategy) -> None: """ Usually, the Context allows replacing a Strategy object at runtime. """
self._strategy = strategy
defdo_some_business_logic(self) -> None: """ The Context delegates some work to the Strategy object instead of implementing multiple versions of the algorithm on its own. """
# ...
print("Context: Sorting data using the strategy (not sure how it'll do it)") result = self._strategy.do_algorithm(["a", "b", "c", "d", "e"]) print(",".join(result))
# ...
classStrategy(ABC): """ The Strategy interface declares operations common to all supported versions of some algorithm. The Context uses this interface to call the algorithm defined by Concrete Strategies. """
""" Concrete Strategies implement the algorithm while following the base Strategy interface. The interface makes them interchangeable in the Context. """
if __name__ == "__main__": # The client code picks a concrete strategy and passes it to the context. # The client should be aware of the differences between strategies in order # to make the right choice.
context = Context(ConcreteStrategyA()) print("Client: Strategy is set to normal sorting.") context.do_some_business_logic() print()
print("Client: Strategy is set to reverse sorting.") context.strategy = ConcreteStrategyB() context.do_some_business_logic()
Output.txt: 执行结果
1 2 3 4 5 6 7
Client: Strategy is set to normal sorting. Context: Sorting data using the strategy (not sure how it'll do it) a,b,c,d,e
Client: Strategy is set to reverse sorting. Context: Sorting data using the strategy (not sure how it'll do it) e,d,c,b,a
# The Context defines the interface of interest to clients. classContext # The Context maintains a reference to one of the Strategy objects. The # Context does not know the concrete class of a strategy. It should work with # all strategies via the Strategy interface. attr_writer:strategy
# Usually, the Context accepts a strategy through the constructor, but also # provides a setter to change it at runtime. definitialize(strategy) @strategy = strategy end
# Usually, the Context allows replacing a Strategy object at runtime. defstrategy=(strategy) @strategy = strategy end
# The Context delegates some work to the Strategy object instead of # implementing multiple versions of the algorithm on its own. defdo_some_business_logic # ...
puts 'Context: Sorting data using the strategy (not sure how it\'ll do it)' result = @strategy.do_algorithm(%w[a b c d e]) print result.join(',')
# ... end end
# The Strategy interface declares operations common to all supported versions of # some algorithm. # # The Context uses this interface to call the algorithm defined by Concrete # Strategies. classStrategy # @abstract # # @param [Array] data defdo_algorithm(_data) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end end
# Concrete Strategies implement the algorithm while following the base Strategy # interface. The interface makes them interchangeable in the Context.
classConcreteStrategyA < Strategy # @param [Array] data # # @return [Array] defdo_algorithm(data) data.sort end end
classConcreteStrategyB < Strategy # @param [Array] data # # @return [Array] defdo_algorithm(data) data.sort.reverse end end
# The client code picks a concrete strategy and passes it to the context. The # client should be aware of the differences between strategies in order to make # the right choice.
context = Context.new(ConcreteStrategyA.new) puts 'Client: Strategy is set to normal sorting.' context.do_some_business_logic puts "\n\n"
puts 'Client: Strategy is set to reverse sorting.' context.strategy = ConcreteStrategyB.new context.do_some_business_logic
output.txt: 执行结果
1 2 3 4 5 6 7
Client: Strategy is set to normal sorting. Context: Sorting data using the strategy (not sure how it'll do it) a,b,c,d,e
Client: Strategy is set to reverse sorting. Context: Sorting data using the strategy (not sure how it'll do it) e,d,c,b,a
在 Swift 中使用模式
复杂度: ★☆☆
流行度: ★★★
使用示例: 策略模式在 Swift 代码中很常见。 它经常在各种框架中使用, 能在不扩展类的情况下向用户提供改变其行为的方式。
/// The Context defines the interface of interest to clients. classContext {
/// The Context maintains a reference to one of the Strategy objects. The /// Context does not know the concrete class of a strategy. It should work /// with all strategies via the Strategy interface. privatevar strategy: Strategy
/// Usually, the Context accepts a strategy through the constructor, but /// also provides a setter to change it at runtime. init(strategy: Strategy) { self.strategy = strategy }
/// Usually, the Context allows replacing a Strategy object at runtime. funcupdate(strategy: Strategy) { self.strategy = strategy }
/// The Context delegates some work to the Strategy object instead of /// implementing multiple versions of the algorithm on its own. funcdoSomeBusinessLogic() { print("Context: Sorting data using the strategy (not sure how it'll do it)\n")
let result = strategy.doAlgorithm(["a", "b", "c", "d", "e"]) print(result.joined(separator: ",")) } }
/// The Strategy interface declares operations common to all supported versions /// of some algorithm. /// /// The Context uses this interface to call the algorithm defined by Concrete /// Strategies. protocolStrategy {
/// Concrete Strategies implement the algorithm while following the base /// Strategy interface. The interface makes them interchangeable in the Context. classConcreteStrategyA: Strategy {
/// Let's see how it all works together. classStrategyConceptual: XCTestCase {
functest() {
/// The client code picks a concrete strategy and passes it to the /// context. The client should be aware of the differences between /// strategies in order to make the right choice.
let context =Context(strategy: ConcreteStrategyA()) print("Client: Strategy is set to normal sorting.\n") context.doSomeBusinessLogic()
print("\nClient: Strategy is set to reverse sorting.\n") context.update(strategy: ConcreteStrategyB()) context.doSomeBusinessLogic() } }
Output.txt: 执行结果
1 2 3 4 5 6 7 8 9 10 11
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
/// This example shows a simple implementation of a list controller that is /// able to display models from different data sources: /// /// (MemoryStorage, CoreDataStorage, RealmStorage)
functest() {
let controller =ListController()
let memoryStorage =MemoryStorage<User>() memoryStorage.add(usersFromNetwork())
/** * The Context defines the interface of interest to clients. */ classContext { /** * @type {Strategy} The Context maintains a reference to one of the Strategy * objects. The Context does not know the concrete class of a strategy. It * should work with all strategies via the Strategy interface. */ privatestrategy: Strategy;
/** * Usually, the Context accepts a strategy through the constructor, but also * provides a setter to change it at runtime. */ constructor(strategy: Strategy) { this.strategy = strategy; }
/** * Usually, the Context allows replacing a Strategy object at runtime. */ publicsetStrategy(strategy: Strategy) { this.strategy = strategy; }
/** * The Context delegates some work to the Strategy object instead of * implementing multiple versions of the algorithm on its own. */ publicdoSomeBusinessLogic(): void { // ...
console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)'); const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']); console.log(result.join(','));
// ... } }
/** * The Strategy interface declares operations common to all supported versions * of some algorithm. * * The Context uses this interface to call the algorithm defined by Concrete * Strategies. */ interfaceStrategy { doAlgorithm(data: string[]): string[]; }
/** * Concrete Strategies implement the algorithm while following the base Strategy * interface. The interface makes them interchangeable in the Context. */ classConcreteStrategyAimplementsStrategy { publicdoAlgorithm(data: string[]): string[] { return data.sort(); } }
/** * The client code picks a concrete strategy and passes it to the context. The * client should be aware of the differences between strategies in order to make * the right choice. */ const context = newContext(newConcreteStrategyA()); console.log('Client: Strategy is set to normal sorting.'); context.doSomeBusinessLogic();
console.log('');
console.log('Client: Strategy is set to reverse sorting.'); context.setStrategy(newConcreteStrategyB()); context.doSomeBusinessLogic();
Output.txt: 执行结果
1 2 3 4 5 6 7
Client: Strategy is set to normal sorting. Context: Sorting data using the strategy (not sure how it'll do it) a,b,c,d,e
Client: Strategy is set to reverse sorting. Context: Sorting data using the strategy (not sure how it'll do it) e,d,c,b,a