forked from faif/python-patterns
-
Notifications
You must be signed in to change notification settings - Fork 0
/
strategy.py
92 lines (68 loc) · 2.62 KB
/
strategy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""
*What is this pattern about?
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.
*TL;DR
Enables selecting an algorithm at runtime.
"""
from __future__ import annotations
from typing import Callable
class DiscountStrategyValidator: # Descriptor class for check perform
@staticmethod
def validate(obj: Order, value: Callable) -> bool:
try:
if obj.price - value(obj) < 0:
raise ValueError(
f"Discount cannot be applied due to negative price resulting. {value.__name__}"
)
except ValueError as ex:
print(str(ex))
return False
else:
return True
def __set_name__(self, owner, name: str) -> None:
self.private_name = f"_{name}"
def __set__(self, obj: Order, value: Callable = None) -> None:
if value and self.validate(obj, value):
setattr(obj, self.private_name, value)
else:
setattr(obj, self.private_name, None)
def __get__(self, obj: object, objtype: type = None):
return getattr(obj, self.private_name)
class Order:
discount_strategy = DiscountStrategyValidator()
def __init__(self, price: float, discount_strategy: Callable = None) -> None:
self.price: float = price
self.discount_strategy = discount_strategy
def apply_discount(self) -> float:
if self.discount_strategy:
discount = self.discount_strategy(self)
else:
discount = 0
return self.price - discount
def __repr__(self) -> str:
return f"<Order price: {self.price} with discount strategy: {getattr(self.discount_strategy,'__name__',None)}>"
def ten_percent_discount(order: Order) -> float:
return order.price * 0.10
def on_sale_discount(order: Order) -> float:
return order.price * 0.25 + 20
def main():
"""
>>> order = Order(100, discount_strategy=ten_percent_discount)
>>> print(order)
<Order price: 100 with discount strategy: ten_percent_discount>
>>> print(order.apply_discount())
90.0
>>> order = Order(100, discount_strategy=on_sale_discount)
>>> print(order)
<Order price: 100 with discount strategy: on_sale_discount>
>>> print(order.apply_discount())
55.0
>>> order = Order(10, discount_strategy=on_sale_discount)
Discount cannot be applied due to negative price resulting. on_sale_discount
>>> print(order)
<Order price: 10 with discount strategy: None>
"""
if __name__ == "__main__":
import doctest
doctest.testmod()