Skip to content

Commit 5f47b07

Browse files
authored
Merge pull request #374 from TolstochenkoDaniil/strategy_pattern
Added type hints and pytest tests
2 parents 4fcbf1b + c0677d7 commit 5f47b07

File tree

4 files changed

+113
-21
lines changed

4 files changed

+113
-21
lines changed

patterns/behavioral/strategy.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,81 @@
88
"""
99

1010

11+
from __future__ import annotations
12+
13+
from typing import Callable, Type
14+
15+
16+
class DiscountStrategyValidator: # Descriptor class for check perform
17+
@staticmethod
18+
def validate(obj: Order, value: Callable) -> bool:
19+
try:
20+
if obj.price - value(obj) < 0:
21+
raise ValueError(
22+
f"Discount cannot be applied due to negative price resulting. {value.__name__}"
23+
)
24+
except ValueError as ex:
25+
print(str(ex))
26+
return False
27+
else:
28+
return True
29+
30+
def __set_name__(self, owner, name: str) -> None:
31+
self.private_name = f"_{name}"
32+
33+
def __set__(self, obj: Order, value: Callable = None) -> None:
34+
if value and self.validate(obj, value):
35+
setattr(obj, self.private_name, value)
36+
else:
37+
setattr(obj, self.private_name, None)
38+
39+
def __get__(self, obj: object, objtype: Type = None):
40+
return getattr(obj, self.private_name)
41+
42+
1143
class Order:
12-
def __init__(self, price, discount_strategy=None):
13-
self.price = price
44+
discount_strategy = DiscountStrategyValidator()
45+
46+
def __init__(self, price: float, discount_strategy: Callable = None) -> None:
47+
self.price: float = price
1448
self.discount_strategy = discount_strategy
1549

16-
def price_after_discount(self):
50+
def apply_discount(self) -> float:
1751
if self.discount_strategy:
1852
discount = self.discount_strategy(self)
1953
else:
2054
discount = 0
55+
2156
return self.price - discount
2257

23-
def __repr__(self):
24-
fmt = "<Price: {}, price after discount: {}>"
25-
return fmt.format(self.price, self.price_after_discount())
58+
def __repr__(self) -> str:
59+
return f"<Order price: {self.price} with discount strategy: {getattr(self.discount_strategy,'__name__',None)}>"
2660

2761

28-
def ten_percent_discount(order):
62+
def ten_percent_discount(order: Order) -> float:
2963
return order.price * 0.10
3064

3165

32-
def on_sale_discount(order):
66+
def on_sale_discount(order: Order) -> float:
3367
return order.price * 0.25 + 20
3468

3569

3670
def main():
3771
"""
38-
>>> Order(100)
39-
<Price: 100, price after discount: 100>
40-
41-
>>> Order(100, discount_strategy=ten_percent_discount)
42-
<Price: 100, price after discount: 90.0>
43-
44-
>>> Order(1000, discount_strategy=on_sale_discount)
45-
<Price: 1000, price after discount: 730.0>
72+
>>> order = Order(100, discount_strategy=ten_percent_discount)
73+
>>> print(order)
74+
<Order price: 100 with discount strategy: ten_percent_discount>
75+
>>> print(order.apply_discount())
76+
90.0
77+
>>> order = Order(100, discount_strategy=on_sale_discount)
78+
>>> print(order)
79+
<Order price: 100 with discount strategy: on_sale_discount>
80+
>>> print(order.apply_discount())
81+
55.0
82+
>>> order = Order(10, discount_strategy=on_sale_discount)
83+
Discount cannot be applied due to negative price resulting. on_sale_discount
84+
>>> print(order)
85+
<Order price: 10 with discount strategy: None>
4686
"""
4787

4888

patterns/structural/3-tier.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
class Data:
10-
""" Data Store Class """
10+
"""Data Store Class"""
1111

1212
products = {
1313
"milk": {"price": 1.50, "quantity": 10},
@@ -22,7 +22,7 @@ def __get__(self, obj, klas):
2222

2323

2424
class BusinessLogic:
25-
""" Business logic holding data store instances """
25+
"""Business logic holding data store instances"""
2626

2727
data = Data()
2828

@@ -36,7 +36,7 @@ def product_information(
3636

3737

3838
class Ui:
39-
""" UI interaction class """
39+
"""UI interaction class"""
4040

4141
def __init__(self) -> None:
4242
self.business_logic = BusinessLogic()

patterns/structural/front_controller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def dispatch(self, request):
3131

3232

3333
class RequestController:
34-
""" front controller """
34+
"""front controller"""
3535

3636
def __init__(self):
3737
self.dispatcher = Dispatcher()
@@ -44,7 +44,7 @@ def dispatch_request(self, request):
4444

4545

4646
class Request:
47-
""" request """
47+
"""request"""
4848

4949
mobile_type = "mobile"
5050
tablet_type = "tablet"

tests/behavioral/test_strategy.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest
2+
3+
from patterns.behavioral.strategy import Order, ten_percent_discount, on_sale_discount
4+
5+
6+
@pytest.fixture
7+
def order():
8+
return Order(100)
9+
10+
11+
@pytest.mark.parametrize(
12+
"func, discount",
13+
[
14+
(ten_percent_discount, 10.0),
15+
(on_sale_discount, 45.0)
16+
]
17+
)
18+
def test_discount_function_return(func, order, discount):
19+
assert func(order) == discount
20+
21+
22+
@pytest.mark.parametrize(
23+
"func, price",
24+
[
25+
(ten_percent_discount, 100),
26+
(on_sale_discount, 100)
27+
]
28+
)
29+
def test_order_discount_strategy_validate_success(func, price):
30+
order = Order(price, func)
31+
32+
assert order.price == price
33+
assert order.discount_strategy == func
34+
35+
36+
def test_order_discount_strategy_validate_error():
37+
order = Order(10, discount_strategy=on_sale_discount)
38+
39+
assert order.discount_strategy is None
40+
41+
42+
@pytest.mark.parametrize(
43+
"func, price, discount",
44+
[
45+
(ten_percent_discount, 100, 90.0),
46+
(on_sale_discount, 100, 55.0)
47+
]
48+
)
49+
def test_discount_apply_success(func, price, discount):
50+
order = Order(price, func)
51+
52+
assert order.apply_discount() == discount

0 commit comments

Comments
 (0)