Skip to content

Commit 75ab402

Browse files
authored
Merge pull request mouredev#4710 from hozlucas28/Solution-27-Python
#27 - Python
2 parents 66da025 + 585c5bb commit 75ab402

File tree

1 file changed

+346
-0
lines changed

1 file changed

+346
-0
lines changed
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# pylint: disable=pointless-string-statement,missing-class-docstring,missing-function-docstring,missing-module-docstring,too-few-public-methods,useless-parent-delegation
2+
3+
from typing import Union, Literal, Self
4+
from abc import abstractmethod, ABCMeta
5+
6+
"""
7+
Open-Close Principle (OCP)...
8+
"""
9+
10+
print("Open-Close Principle (OCP)...")
11+
12+
print("\nBad implementation of Open-Close Principle (OCP)...")
13+
14+
15+
class AbcProduct(metaclass=ABCMeta):
16+
name: str
17+
price: float
18+
19+
def __init__(self, *, name: str, price: float) -> None:
20+
self.name = name
21+
self.price = price
22+
23+
24+
class Product(AbcProduct):
25+
def __init__(self, *, name: str, price: float) -> None:
26+
super().__init__(name=name, price=price)
27+
28+
29+
DiscountStrategy = Union[Literal["holyday"], Literal["seasonal"]]
30+
31+
32+
class BadDiscountCalculator:
33+
def __init__(self) -> None:
34+
pass
35+
36+
def get_discount(
37+
self, *, product: AbcProduct, discount_strategy: DiscountStrategy
38+
) -> float:
39+
if discount_strategy == "holyday":
40+
return product.price * 0.15
41+
42+
if discount_strategy == "seasonal":
43+
return product.price * 0.21
44+
45+
return -1
46+
47+
48+
print(
49+
"\n```",
50+
"""class AbcProduct(metaclass=ABCMeta):
51+
name: str
52+
price: float
53+
54+
def __init__(self, *, name: str, price: float) -> None:
55+
self.name = name
56+
self.price = price
57+
58+
59+
class Product(AbcProduct):
60+
def __init__(self, *, name: str, price: float) -> None:
61+
super().__init__(name=name, price=price)
62+
63+
64+
DiscountStrategy = Union[Literal["holyday"], Literal["seasonal"]]
65+
66+
67+
class BadDiscountCalculator:
68+
def __init__(self) -> None:
69+
pass
70+
71+
def get_discount(
72+
self, *, product: AbcProduct, discount_strategy: DiscountStrategy
73+
) -> float:
74+
if discount_strategy == "holyday":
75+
return product.price * 0.15
76+
77+
if discount_strategy == "seasonal":
78+
return product.price * 0.21
79+
80+
return -1""",
81+
"```",
82+
sep="\n",
83+
)
84+
85+
print(
86+
"\nThis is a bad implementation of Open-Close Principle (OCP),",
87+
"because the method 'get_discount' of class 'BadDiscountCalculator'",
88+
"will must change if we have to add more discount strategies",
89+
sep="\n",
90+
)
91+
92+
print("\nGood implementation of Open-Close Principle (OCP)...")
93+
94+
95+
class AbcDiscountService(metaclass=ABCMeta):
96+
product: AbcProduct
97+
98+
def __init__(self, *, product: AbcProduct) -> None:
99+
self.product = product
100+
101+
@abstractmethod
102+
def get_discount(self) -> float:
103+
pass
104+
105+
106+
class HolidayDiscountService(AbcDiscountService):
107+
def __init__(self, *, product: AbcProduct) -> None:
108+
super().__init__(product=product)
109+
110+
def get_discount(self) -> float:
111+
return self.product.price * 0.15
112+
113+
114+
class SeasonalDiscountService(AbcDiscountService):
115+
def __init__(self, *, product: AbcProduct) -> None:
116+
super().__init__(product=product)
117+
118+
def get_discount(self) -> float:
119+
return self.product.price * 0.21
120+
121+
122+
class AbcGoodDiscountCalculator(metaclass=ABCMeta):
123+
@abstractmethod
124+
def get_discount(self, *, discount_service: AbcDiscountService) -> float:
125+
pass
126+
127+
128+
class GoodDiscountCalculator(AbcGoodDiscountCalculator):
129+
def __init__(self) -> None:
130+
pass
131+
132+
def get_discount(self, *, discount_service: AbcDiscountService) -> float:
133+
return discount_service.get_discount()
134+
135+
136+
print(
137+
"\n```",
138+
"""class AbcDiscountService(metaclass=ABCMeta):
139+
product: AbcProduct
140+
141+
def __init__(self, *, product: AbcProduct) -> None:
142+
self.product = product
143+
144+
@abstractmethod
145+
def get_discount(self) -> float:
146+
pass
147+
148+
149+
class HolidayDiscountService(AbcDiscountService):
150+
def __init__(self, *, product: AbcProduct) -> None:
151+
super().__init__(product=product)
152+
153+
def get_discount(self) -> float:
154+
return self.product.price * 0.15
155+
156+
157+
class SeasonalDiscountService(AbcDiscountService):
158+
def __init__(self, *, product: AbcProduct) -> None:
159+
super().__init__(product=product)
160+
161+
def get_discount(self) -> float:
162+
return self.product.price * 0.21
163+
164+
165+
class AbcGoodDiscountCalculator(metaclass=ABCMeta):
166+
@abstractmethod
167+
def get_discount(self, *, discount_service: AbcDiscountService) -> float:
168+
pass
169+
170+
171+
class GoodDiscountCalculator(AbcGoodDiscountCalculator):
172+
def __init__(self) -> None:
173+
pass
174+
175+
def get_discount(self, *, discount_service: AbcDiscountService) -> float:
176+
return discount_service.get_discount()""",
177+
"```",
178+
sep="\n",
179+
)
180+
181+
print(
182+
"\nThis is a good implementation of Open-Close Principle (OCP),",
183+
"because the method 'get_discount' of class 'GoodDiscountCalculator' will must",
184+
"not change if we have to add more discount services. So, 'get_discount' is closed",
185+
"to modification but it is open to extension throw any discount service which implements",
186+
"'AbcDiscountService' abstract class.",
187+
sep="\n",
188+
)
189+
190+
print(
191+
"\n# ---------------------------------------------------------------------------------- #\n"
192+
)
193+
194+
"""
195+
Additional challenge...
196+
"""
197+
198+
print("Additional challenge...")
199+
200+
201+
class AbcMathOperation(metaclass=ABCMeta):
202+
@abstractmethod
203+
def execute(self, *, a: float, b: float) -> float:
204+
pass
205+
206+
207+
class AddOperation(AbcMathOperation):
208+
def __init__(self) -> None:
209+
pass
210+
211+
def execute(self, *, a: float, b: float) -> float:
212+
return a + b
213+
214+
215+
class DivideOperation(AbcMathOperation):
216+
def __init__(self) -> None:
217+
pass
218+
219+
def execute(self, *, a: float, b: float) -> float:
220+
if b == 0:
221+
raise ValueError("The second parameter must not be zero")
222+
223+
return a / b
224+
225+
226+
class MultiplyOperation(AbcMathOperation):
227+
def __init__(self) -> None:
228+
pass
229+
230+
def execute(self, *, a: float, b: float) -> float:
231+
return a * b
232+
233+
234+
class SubtractOperation(AbcMathOperation):
235+
def __init__(self) -> None:
236+
pass
237+
238+
def execute(self, *, a: float, b: float) -> float:
239+
return a - b
240+
241+
242+
class AbcCalculator(metaclass=ABCMeta):
243+
operations: dict[str, AbcMathOperation]
244+
245+
def __init__(self, *, operations) -> None:
246+
self.operations = operations
247+
248+
@abstractmethod
249+
def add_operation(self, *, name: str, operation: AbcMathOperation) -> Self:
250+
pass
251+
252+
@abstractmethod
253+
def execute_operation(self, *, name: str, a: float, b: float) -> float:
254+
pass
255+
256+
257+
class Calculator(AbcCalculator):
258+
operations: dict[str, AbcMathOperation]
259+
260+
def __init__(
261+
self, *, operations: None | dict[str, AbcMathOperation] = None
262+
) -> None:
263+
if operations is None:
264+
operations = {}
265+
266+
super().__init__(operations=operations)
267+
268+
def add_operation(self, *, name: str, operation: AbcMathOperation) -> Self:
269+
operation_exist: bool = self.operations.get(name) is not None
270+
271+
if operation_exist:
272+
raise ValueError(f"The operation with '{name}' name already exist")
273+
274+
self.operations[name] = operation
275+
return self
276+
277+
def execute_operation(self, *, name: str, a: float, b: float) -> float:
278+
operation_not_exist: bool = self.operations.get(name) is None
279+
280+
if operation_not_exist:
281+
raise ValueError(f"There is not operation with '${name}' name")
282+
283+
return self.operations[name].execute(a=a, b=b)
284+
285+
286+
print("\nTesting the OCP system without a pow operation...")
287+
288+
CALCULATOR: Calculator = Calculator()
289+
290+
CALCULATOR.add_operation(name="add", operation=AddOperation())
291+
CALCULATOR.add_operation(name="divide", operation=DivideOperation())
292+
CALCULATOR.add_operation(name="multiply", operation=MultiplyOperation())
293+
CALCULATOR.add_operation(name="subtract", operation=SubtractOperation())
294+
295+
A: float = 4.5
296+
B: float = 6.1
297+
298+
print(
299+
f"\nAdd operation result ({A} + {B}):",
300+
CALCULATOR.execute_operation(name="add", a=A, b=B),
301+
)
302+
303+
A = 5
304+
B = 2.2
305+
306+
print(
307+
f"Divide operation result ({A} / {B}):",
308+
CALCULATOR.execute_operation(name="divide", a=A, b=B),
309+
)
310+
311+
A = 3
312+
B = 1.75
313+
314+
print(
315+
f"Multiply operation result ({A} * {B}):",
316+
CALCULATOR.execute_operation(name="multiply", a=A, b=B),
317+
)
318+
319+
A = 10
320+
B = 8.7
321+
322+
print(
323+
f"Subtract operation result ({A} - {B}):",
324+
CALCULATOR.execute_operation(name="subtract", a=A, b=B),
325+
)
326+
327+
print("\nTesting the OCP system with a pow operation...")
328+
329+
330+
class PowOperation(AbcMathOperation):
331+
def __init__(self) -> None:
332+
pass
333+
334+
def execute(self, *, a: float, b: float) -> float:
335+
return pow(base=a, exp=b)
336+
337+
338+
CALCULATOR.add_operation(name="pow", operation=PowOperation())
339+
340+
A = 11
341+
B = 2
342+
343+
print(
344+
f"\nPow operation result ({A}^{B}):",
345+
CALCULATOR.execute_operation(name="pow", a=A, b=B),
346+
)

0 commit comments

Comments
 (0)