Skip to content

Refactor Strategy #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 29 additions & 52 deletions behavioral/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,48 @@

"""
*What is this pattern about?
This pattern aims to encapsulate each algorithm and allow them to be
interchangeable. Separating algorithms allows the client to scale
with larger and more complex algorithms, since the client and the
strategies are kept independent of each other.

Having the algorithms as an integral part of the client can cause the
client to be larger and harder to maintain. This is more evident when
supporting multiple algorithms. The separation of client and algorithm
allows us to easily replace and vary the algorithm.

*What does this example do?
Below the 'StrategyExample' is an example of the client while the two
functions; 'execute_replacement1' and 'execute_replacement2' are
examples of the implementation or strategy. In the example we can see
that the client can vary it's 'execute' method by changing the
strategy which is responsible for implementation.

http://stackoverflow.com/questions/963965/how-is-this-strategy-pattern
-written-in-python-the-sample-in-wikipedia
In most of other languages Strategy pattern is implemented via creating some
base strategy interface/abstract class and subclassing it with a number of
concrete strategies (as we can see at
http://en.wikipedia.org/wiki/Strategy_pattern), however Python supports
higher-order functions and allows us to have only one class and inject
functions into it's instances, as shown in this example.
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.

*TL;DR80
Enables selecting an algorithm at runtime.
"""

import types


class StrategyExample:
def __init__(self, func=None):
self.name = 'Strategy Example 0'
if func is not None:
self.execute = types.MethodType(func, self)

def execute(self):
print(self.name)

class Order:
def __init__(self, price, discount_strategy=None):
self.price = price
self.discount_strategy = discount_strategy

def execute_replacement1(self):
print(self.name + ' from execute 1')
def price_after_discount(self):
if self.discount_strategy:
discount = self.discount_strategy(self)
else:
discount = 0
return self.price - discount

def __repr__(self):
fmt = "<Price: {}, price after discount: {}>"
return fmt.format(self.price, self.price_after_discount())

def execute_replacement2(self):
print(self.name + ' from execute 2')

def ten_percent_discount(order):
return order.price * 0.10

if __name__ == '__main__':
strat0 = StrategyExample()

strat1 = StrategyExample(execute_replacement1)
strat1.name = 'Strategy Example 1'
def on_sale_discount(order):
return order.price * 0.25 + 20

strat2 = StrategyExample(execute_replacement2)
strat2.name = 'Strategy Example 2'

strat0.execute()
strat1.execute()
strat2.execute()
if __name__ == "__main__":
order0 = Order(100)
order1 = Order(100, discount_strategy=ten_percent_discount)
order2 = Order(1000, discount_strategy=on_sale_discount)
print(order0)
print(order1)
print(order2)

### OUTPUT ###
# Strategy Example 0
# Strategy Example 1 from execute 1
# Strategy Example 2 from execute 2
# <Price: 100, price after discount: 100>
# <Price: 100, price after discount: 90.0>
# <Price: 1000, price after discount: 730.0>
2 changes: 1 addition & 1 deletion tests/test_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_print_output(self):
"""
output = subprocess.check_output(["python", "behavioral/strategy.py"])
expected_output = os.linesep.join(
['Strategy Example 0', 'Strategy Example 1 from execute 1', 'Strategy Example 2 from execute 2', '']
['<Price: 100, price after discount: 100>', '<Price: 100, price after discount: 90.0>', '<Price: 1000, price after discount: 730.0>', '']
)
# byte representation required due to EOF returned subprocess
expected_output_as_bytes = expected_output.encode(encoding='UTF-8')
Expand Down