Skip to content

Commit e28bf51

Browse files
committed
strategy exercise started
1 parent e8246de commit e28bf51

File tree

10 files changed

+520
-1
lines changed

10 files changed

+520
-1
lines changed

docs/exercise_1.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=============================
2-
Exercise #1: Vector addition
2+
Exercise #1: Vector operators
33
=============================
44

55
A. Preparation

docs/exercise_2.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=====================================
2+
Exercise #2: Strategy without classes
3+
=====================================
4+
5+
6+
A. Review class-based implementation
7+
------------------------------------
8+
9+
1. Go to ``exercises/vector`` directory
10+
11+
2. Run test for unary ``-`` with the ``-x`` option to stop at first error or failure:
12+
13+
.. code-block:: shell
14+
15+
$ py.test -x test_vector_neg.py
16+
17+
3. Edit ``vector.py`` to implement a ``__neg__`` method for unary negation. That method should return a new ``Vector`` instance with each component negated. See ``test_vector_neg.py`` for the expected behavior.
18+
19+
20+
C. Vector addition
21+
------------------
22+
23+
1. Run tests for vector addition ``+`` with the ``-x`` option to stop at first error or failure:
24+
25+
.. code-block:: shell
26+
27+
$ py.test -x test_vector_add.py
28+
29+
2. Edit ``vector.py`` to implement a ``__add__`` method for vector addition. That method should return a new ``Vector`` instance with each component of ``self`` added to the corresponding component of ``other``. See ``test_vector_add.py`` for the expected behaviors.

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Contents:
1414
inspiration
1515
vector
1616
exercise_1
17+
strategy
18+
exercise_2
1719

1820

1921

docs/strategy.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
===================
2+
Rethinking strategy
3+
===================
4+
5+
6+
``classic.py``
7+
----------------
8+
9+
.. literalinclude:: ../exercises/strategy/classic.py
10+
11+
12+
``strategy.py``
13+
----------------
14+
15+
.. literalinclude:: ../exercises/strategy/strategy.py
16+
17+
18+
``strategy2.py``
19+
-----------------
20+
21+
.. literalinclude:: ../exercises/strategy/strategy2.py
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# strategy.py
2+
# Strategy pattern -- function-based implementation
3+
4+
"""
5+
>>> joe = Customer('John Doe', 0) # <1>
6+
>>> ann = Customer('Ann Smith', 1100)
7+
>>> cart = [LineItem('banana', 4, .5),
8+
... LineItem('apple', 10, 1.5),
9+
... LineItem('watermellon', 5, 5.0)]
10+
>>> Order(joe, cart, fidelity_promo) # <2>
11+
<Order total: 42.00 due: 42.00>
12+
>>> Order(ann, cart, fidelity_promo)
13+
<Order total: 42.00 due: 39.90>
14+
>>> banana_cart = [LineItem('banana', 30, .5),
15+
... LineItem('apple', 10, 1.5)]
16+
>>> Order(joe, banana_cart, bulk_item_promo) # <3>
17+
<Order total: 30.00 due: 28.50>
18+
>>> long_order = [LineItem(str(item_code), 1, 1.0)
19+
... for item_code in range(10)]
20+
>>> Order(joe, long_order, large_order_promo)
21+
<Order total: 10.00 due: 9.30>
22+
>>> Order(joe, cart, large_order_promo)
23+
<Order total: 42.00 due: 42.00>
24+
"""
25+
26+
from collections import namedtuple
27+
28+
Customer = namedtuple('Customer', 'name fidelity')
29+
30+
31+
class LineItem:
32+
33+
def __init__(self, product, quantity, price):
34+
self.product = product
35+
self.quantity = quantity
36+
self.price = price
37+
38+
def total(self):
39+
return self.price * self.quantity
40+
41+
42+
class Order:
43+
44+
def __init__(self, customer, cart, promotion=None):
45+
self.customer = customer
46+
self.cart = list(cart)
47+
self.promotion = promotion
48+
49+
def total(self):
50+
if not hasattr(self, '__total'):
51+
self.__total = sum(item.total() for item in self.cart)
52+
return self.__total
53+
54+
def due(self):
55+
if self.promotion is None:
56+
discount = 0
57+
else:
58+
discount = self.promotion(self) # invoke the promotion function
59+
return self.total() - discount
60+
61+
def __repr__(self):
62+
fmt = '<Order total: {:.2f} due: {:.2f}>'
63+
return fmt.format(self.total(), self.due())
64+
65+
66+
def fidelity_promo(order):
67+
"""5% discount for customers with 1000 or more fidelity points"""
68+
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
69+
70+
71+
def bulk_item_promo(order):
72+
"""10% discount for each LineItem with 20 or more units"""
73+
discount = 0
74+
for item in order.cart:
75+
if item.quantity >= 20:
76+
discount += item.total() * .1
77+
return discount
78+
79+
80+
def large_order_promo(order):
81+
"""7% discount for orders with 10 or more distinct items"""
82+
distinct_items = {item.product for item in order.cart}
83+
if len(distinct_items) >= 10:
84+
return order.total() * .07
85+
return 0

exercises/strategy/classic.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# classic_strategy.py
2+
# Strategy pattern -- classic implementation
3+
4+
"""
5+
>>> joe = Customer('John Doe', 0) # <1>
6+
>>> ann = Customer('Ann Smith', 1100)
7+
>>> cart = [LineItem('banana', 4, .5), # <2>
8+
... LineItem('apple', 10, 1.5),
9+
... LineItem('watermellon', 5, 5.0)]
10+
>>> Order(joe, cart, FidelityPromo()) # <3>
11+
<Order total: 42.00 due: 42.00>
12+
>>> Order(ann, cart, FidelityPromo()) # <4>
13+
<Order total: 42.00 due: 39.90>
14+
>>> banana_cart = [LineItem('banana', 30, .5), # <5>
15+
... LineItem('apple', 10, 1.5)]
16+
>>> Order(joe, banana_cart, BulkItemPromo()) # <6>
17+
<Order total: 30.00 due: 28.50>
18+
>>> long_order = [LineItem(str(item_code), 1, 1.0) # <7>
19+
... for item_code in range(10)]
20+
>>> Order(joe, long_order, LargeOrderPromo()) # <8>
21+
<Order total: 10.00 due: 9.30>
22+
>>> Order(joe, cart, LargeOrderPromo())
23+
<Order total: 42.00 due: 42.00>
24+
25+
"""
26+
27+
from abc import ABC, abstractmethod
28+
from collections import namedtuple
29+
30+
Customer = namedtuple('Customer', 'name fidelity')
31+
32+
33+
class LineItem:
34+
35+
def __init__(self, product, quantity, price):
36+
self.product = product
37+
self.quantity = quantity
38+
self.price = price
39+
40+
def total(self):
41+
return self.price * self.quantity
42+
43+
44+
class Order: # the Context
45+
46+
def __init__(self, customer, cart, promotion=None):
47+
self.customer = customer
48+
self.cart = list(cart)
49+
self.promotion = promotion
50+
51+
def total(self):
52+
if not hasattr(self, '__total'):
53+
self.__total = sum(item.total() for item in self.cart)
54+
return self.__total
55+
56+
def due(self):
57+
if self.promotion is None:
58+
discount = 0
59+
else:
60+
discount = self.promotion.discount(self)
61+
return self.total() - discount
62+
63+
def __repr__(self):
64+
fmt = '<Order total: {:.2f} due: {:.2f}>'
65+
return fmt.format(self.total(), self.due())
66+
67+
68+
class Promotion(ABC): # the Strategy: an Abstract Base Class
69+
70+
@abstractmethod
71+
def discount(self, order):
72+
"""Return discount as a positive dollar amount"""
73+
74+
75+
class FidelityPromo(Promotion): # first Concrete Strategy
76+
"""5% discount for customers with 1000 or more fidelity points"""
77+
78+
def discount(self, order):
79+
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
80+
81+
82+
class BulkItemPromo(Promotion): # second Concrete Strategy
83+
"""10% discount for each LineItem with 20 or more units"""
84+
85+
def discount(self, order):
86+
discount = 0
87+
for item in order.cart:
88+
if item.quantity >= 20:
89+
discount += item.total() * .1
90+
return discount
91+
92+
93+
class LargeOrderPromo(Promotion): # third Concrete Strategy
94+
"""7% discount for orders with 10 or more distinct items"""
95+
96+
def discount(self, order):
97+
distinct_items = {item.product for item in order.cart}
98+
if len(distinct_items) >= 10:
99+
return order.total() * .07
100+
return 0

exercises/strategy/strategy.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# strategy.py
2+
# Strategy pattern -- function-based implementation
3+
4+
"""
5+
>>> joe = Customer('John Doe', 0) # <1>
6+
>>> ann = Customer('Ann Smith', 1100)
7+
>>> cart = [LineItem('banana', 4, .5),
8+
... LineItem('apple', 10, 1.5),
9+
... LineItem('watermellon', 5, 5.0)]
10+
>>> Order(joe, cart, fidelity_promo) # <2>
11+
<Order total: 42.00 due: 42.00>
12+
>>> Order(ann, cart, fidelity_promo)
13+
<Order total: 42.00 due: 39.90>
14+
>>> banana_cart = [LineItem('banana', 30, .5),
15+
... LineItem('apple', 10, 1.5)]
16+
>>> Order(joe, banana_cart, bulk_item_promo) # <3>
17+
<Order total: 30.00 due: 28.50>
18+
>>> long_order = [LineItem(str(item_code), 1, 1.0)
19+
... for item_code in range(10)]
20+
>>> Order(joe, long_order, large_order_promo)
21+
<Order total: 10.00 due: 9.30>
22+
>>> Order(joe, cart, large_order_promo)
23+
<Order total: 42.00 due: 42.00>
24+
"""
25+
26+
from abc import ABC, abstractmethod # DELETE THIS IMPORT. Yay!
27+
from collections import namedtuple
28+
29+
Customer = namedtuple('Customer', 'name fidelity')
30+
31+
32+
class LineItem:
33+
34+
def __init__(self, product, quantity, price):
35+
self.product = product
36+
self.quantity = quantity
37+
self.price = price
38+
39+
def total(self):
40+
return self.price * self.quantity
41+
42+
43+
class Order:
44+
45+
def __init__(self, customer, cart, promotion=None):
46+
self.customer = customer
47+
self.cart = list(cart)
48+
self.promotion = promotion
49+
50+
def total(self):
51+
if not hasattr(self, '__total'):
52+
self.__total = sum(item.total() for item in self.cart)
53+
return self.__total
54+
55+
def due(self):
56+
if self.promotion is None:
57+
discount = 0
58+
else:
59+
discount = self.promotion(self) # invoke the promotion function
60+
return self.total() - discount
61+
62+
def __repr__(self):
63+
fmt = '<Order total: {:.2f} due: {:.2f}>'
64+
return fmt.format(self.total(), self.due())
65+
66+
67+
class Promotion(ABC): # DELETE THIS CLASS. HOORAY!
68+
69+
@abstractmethod
70+
def discount(self, order):
71+
"""Return discount as a positive dollar amount"""
72+
73+
74+
class FidelityPromo(Promotion): # REFACTOR INTO A FUNCTION
75+
"""5% discount for customers with 1000 or more fidelity points"""
76+
77+
def discount(self, order):
78+
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
79+
80+
81+
class BulkItemPromo(Promotion): # REFACTOR INTO ANOTHER FUNCTION
82+
"""10% discount for each LineItem with 20 or more units"""
83+
84+
def discount(self, order):
85+
discount = 0
86+
for item in order.cart:
87+
if item.quantity >= 20:
88+
discount += item.total() * .1
89+
return discount
90+
91+
92+
class LargeOrderPromo(Promotion): # REFACTOR INTO A THIRD FUNCTION
93+
"""7% discount for orders with 10 or more distinct items"""
94+
95+
def discount(self, order):
96+
distinct_items = {item.product for item in order.cart}
97+
if len(distinct_items) >= 10:
98+
return order.total() * .07
99+
return 0

0 commit comments

Comments
 (0)