Skip to content

Commit f5c7ec4

Browse files
authored
Merge pull request mouredev#4724 from neslarra/patch-38
Reto# 27 - python
2 parents b47fb02 + 7b06ad5 commit f5c7ec4

File tree

1 file changed

+343
-0
lines changed

1 file changed

+343
-0
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
from functools import reduce
2+
from typing import final
3+
4+
"""
5+
EJERCICIO:
6+
Explora el "Principio SOLID Abierto-Cerrado (Open-Close Principle, OCP)"
7+
y crea un ejemplo simple donde se muestre su funcionamiento
8+
de forma correcta e incorrecta.
9+
10+
DIFICULTAD EXTRA (opcional):
11+
Desarrolla una calculadora que necesita realizar diversas operaciones matemáticas.
12+
Requisitos:
13+
- Debes diseñar un sistema que permita agregar nuevas operaciones utilizando el OCP.
14+
Instrucciones:
15+
1. Implementa las operaciones de suma, resta, multiplicación y división.
16+
2. Comprueba que el sistema funciona.
17+
3. Agrega una quinta operación para calcular potencias.
18+
4. Comprueba que se cumple el OCP.
19+
"""
20+
21+
print(f"{'#' * 47}")
22+
print(f"## Explicación {'#' * 30}")
23+
print(f"{'#' * 47}")
24+
25+
print(r"""
26+
Para entender fácilmente los 5 ppios SOLID recomiendo leer:
27+
28+
https://blog.damavis.com/los-principios-solid-ilustrados-en-ejemplos-sencillos-de-python/
29+
30+
en donde se explican de manera ordenada uno por uno, de manera sencilla y ejemplificada de manera progresiva (de hecho, de ahí
31+
voy a tomar el ejemplo).
32+
33+
El segundo de los ppios SOLID es "Open Close Principle" el cual establece que las clases deberían estar abiertas para su extensión
34+
pero cerradas para su modificación.
35+
36+
Retomando el caso anterior, tenemos la clase Calculate:
37+
38+
class Calculate:
39+
40+
def __init__(self, channel):
41+
self.channel = channel
42+
43+
def communicate(self, duck1 : Duck, duck2: Duck):
44+
sentence1 = f"{duck1.name}: {duck1.do_sound()}, hello {duck2.name}"
45+
sentence2 = f"{duck2.name}: {duck2.do_sound()}, hello {duck1.name}"
46+
conversation = [sentence1, sentence2]
47+
print(*conversation,
48+
f"(via {self.channel})",
49+
sep = '\n')
50+
51+
En esta clase No se puede extender la funcionalidad de Calculate para añadir diferentes tipos de conversaciones sin modificar
52+
el método communicate(). Para cumplir con el segundo principio, se crea una clase AbstractConversation que se encargará de definir
53+
diferentes tipos de conversaciones en sus subclases con implementaciones de do_conversation(). De esta manera, el método communicate()
54+
de Calculate solo se regirá a llevar a cabo la comunicación a través de una canal y nunca se requerirá de su modificación (es un método final).
55+
56+
from typing import final
57+
58+
59+
class Duck:
60+
61+
def __init__(self, name):
62+
self.name = name
63+
64+
def fly(self):
65+
print(f"{self.name} is flying not very high")
66+
67+
def swim(self):
68+
print(f"{self.name} swims in the lake and quacks")
69+
70+
@staticmethod
71+
def do_sound() -> str:
72+
return "Quack"
73+
74+
75+
class AbstractConversation:
76+
77+
def do_conversation(self) -> list:
78+
pass
79+
80+
81+
class DuckConversation(AbstractConversation):
82+
83+
def __init__(self, duck1: Duck, duck2: Duck):
84+
self.duck1 = duck1
85+
self.duck2 = duck2
86+
87+
def do_conversation(self) -> list:
88+
sentence1 = f"{self.duck1.name}: {self.duck1.do_sound()}, hello {self.duck2.name}"
89+
sentence2 = f"{self.duck2.name}: {self.duck2.do_sound()}, hello {self.duck1.name}"
90+
return [sentence1, sentence2]
91+
92+
93+
class Calculate:
94+
95+
def __init__(self, channel):
96+
self.channel = channel
97+
98+
@final
99+
def communicate(self, conversation: AbstractConversation):
100+
print(*conversation.do_conversation(), sep="\n")
101+
102+
103+
lucas = Duck("Lucas")
104+
donald = Duck("Donald")
105+
comm = Calculate(DuckConversation(lucas, donald))
106+
107+
comm.communicate(comm.channel)
108+
109+
scooby = Dog("Scooby")
110+
pluto = Dog("Pluto")
111+
comm = Calculate(DogConversation(scooby, pluto))
112+
113+
comm.communicate(comm.channel)
114+
115+
lucas = Duck("Lucas")
116+
donald = Duck("Donald")
117+
comm = Calculate(DuckConversation(lucas, donald))
118+
comm.communicate(comm.channel)
119+
120+
Lucas: Quack, hello Donald
121+
Donald: Quack, hello Lucas
122+
123+
Ahora, si necesito extender Comunicator para que puedan conversar dos perros, entonces solo tengo que agregar una clase DogConversation
124+
(subclase de AbstractConversation) que implemente SU versión canina de "do_conversation" dejando "Calculate.communicate sin cambio.
125+
126+
class Dog:
127+
128+
def __init__(self, name):
129+
self.name = name
130+
131+
def jump(self):
132+
print(f"{self.name} is jumpping not very high")
133+
134+
def run(self):
135+
print(f"{self.name} runs behind the cars")
136+
137+
@staticmethod
138+
def do_sound() -> str:
139+
return "Guau"
140+
141+
142+
class DogConversation(AbstractConversation):
143+
144+
def __init__(self, dog1: Dog, dog2: Dog):
145+
self.dog1 = dog1
146+
self.dog2 = dog2
147+
148+
def do_conversation(self) -> list:
149+
sentence1 = f"{self.dog1.name}: {self.dog1.do_sound()}, hello {self.dog2.name}"
150+
sentence2 = f"{self.dog2.name}: {self.dog2.do_sound()}, hello {self.dog1.name}"
151+
return [sentence1, sentence2]
152+
153+
154+
scooby = Dog("Scooby")
155+
pluto = Dog("Pluto")
156+
comm = Calculate(DogConversation(scooby, pluto))
157+
comm.communicate(comm.channel)
158+
159+
Scooby: Guau, hello Pluto
160+
Pluto: Guau, hello Scooby
161+
""")
162+
163+
print(f"{'#' * 52}")
164+
print(f"## Dificultad Extra {'#' * 30}")
165+
print(f"{'#' * 52}\n")
166+
167+
print(f"\nNO OCP Way {'-' * 27}\n")
168+
169+
170+
class OperationNoOCP:
171+
172+
def __init__(self, name: str):
173+
self.name = name
174+
175+
def addition(self, *args):
176+
return self.name + " addition = " + str(sum(args))
177+
178+
def substraction(self, *args):
179+
return self.name + " substraction = " + str(args[0] + (-1 * sum(args[1:])))
180+
181+
def product(self, *args):
182+
return self.name + " product = " + str(reduce(lambda a, b: a * b, args))
183+
184+
def division(self, dividend: float, divisor: float):
185+
if divisor == 0:
186+
return self.name + " Illegal operation: Divisor cannot be zero"
187+
return self.name + " division = " + str(dividend / divisor)
188+
189+
190+
operaciones = OperationNoOCP("NoOCP")
191+
print(operaciones.addition(1, 2, 3, -4))
192+
print(operaciones.substraction(15, 2, 3, -4))
193+
print(operaciones.product(1, 2, 3, -4))
194+
print(operaciones.division(12, -4))
195+
196+
print(f"\nOCP Way {'-' * 30}\n")
197+
198+
199+
class AbstractOperation:
200+
201+
def calculate(self):
202+
pass
203+
204+
205+
class Addition:
206+
207+
def __init__(self, *args):
208+
self.name = "OCP addition"
209+
self.args = args
210+
211+
def calculate(self):
212+
return self.name + " = " + str(sum(self.args))
213+
214+
215+
class Substraction:
216+
217+
def __init__(self, *args):
218+
self.name = "OCP substraction"
219+
self.args = args
220+
221+
def calculate(self):
222+
return self.name + " = " + str(self.args[0] + (-1 * sum(self.args[1:])))
223+
224+
225+
class Product:
226+
def __init__(self, *args):
227+
self.name = "OCP product"
228+
self.args = args
229+
230+
def calculate(self):
231+
return self.name + " = " + str(reduce(lambda a, b: a * b, self.args))
232+
233+
234+
class Division:
235+
236+
def __init__(self, dividend: float, divisor: float):
237+
self.name = "OCP division"
238+
self.dividend = dividend
239+
self.divisor = divisor
240+
241+
def calculate(self):
242+
if self.divisor == 0:
243+
return self.name + " Illegal operation: Divisor cannot be zero"
244+
return self.name + " = " + str(self.dividend / self.divisor)
245+
246+
247+
class DoAddition(AbstractOperation):
248+
249+
def __init__(self, operation: Addition):
250+
self.operation = operation
251+
252+
def calculate(self, *args):
253+
return self.operation.calculate()
254+
255+
256+
class DoSubstraction(AbstractOperation):
257+
258+
def __init__(self, operation: Substraction):
259+
self.operation = operation
260+
261+
def calculate(self, *args):
262+
return self.operation.calculate()
263+
264+
265+
class DoProduct(AbstractOperation):
266+
267+
def __init__(self, operation: Product):
268+
self.operation = operation
269+
270+
def calculate(self, *args):
271+
return self.operation.calculate()
272+
273+
274+
class DoDivision(AbstractOperation):
275+
276+
def __init__(self, operation: Division):
277+
self.operation = operation
278+
279+
def calculate(self, *args):
280+
return self.operation.calculate()
281+
282+
283+
class Calculate:
284+
285+
def __init__(self, channel):
286+
self.channel = channel
287+
288+
@final
289+
def calculate(self, operation: AbstractOperation):
290+
print(operation.calculate())
291+
292+
293+
suma = Addition(1, 2, 3, -4)
294+
calc = Calculate(DoAddition(suma))
295+
calc.calculate(calc.channel)
296+
297+
resta = Substraction(15, 2, 3, -4)
298+
calc = Calculate(DoSubstraction(resta))
299+
calc.calculate(calc.channel)
300+
301+
producto = Product(1, 2, 3, -4)
302+
calc = Calculate(DoProduct(producto))
303+
calc.calculate(calc.channel)
304+
305+
division = Division(12, -4)
306+
calc = Calculate(DoDivision(division))
307+
calc.calculate(calc.channel)
308+
309+
print(f"""
310+
Está claro que si ahora quiero agregar la operación "potencia", para el caso NoOCP tendría que modificar
311+
la clase OperationNoOCP agragando el nuevo método (con las posibles consecuencias de modificar una clase que está en
312+
uso para las otras cuatro operaciones).
313+
314+
En cambio, para el caso OCP, agregar la nueva operación es solo agregar el la clase y el método abstracto correspondiente
315+
sin "tocar" las operaciones que ya están en uso.
316+
""")
317+
318+
319+
class Power:
320+
def __init__(self, base: int, exponent: int):
321+
self.name = "OCP power"
322+
self.base = base
323+
self.exponent = exponent
324+
325+
def calculate(self):
326+
return self.name + " = " + str(pow(self.base, self.exponent))
327+
328+
329+
class DoPower(AbstractOperation):
330+
331+
def __init__(self, operation: Power):
332+
self.operation = operation
333+
334+
def calculate(self, *args):
335+
return self.operation.calculate()
336+
337+
338+
potencia = Power(4, 3)
339+
calc = Calculate(DoPower(potencia))
340+
calc.calculate(calc.channel)
341+
342+
print(f"""
343+
¿Se ve..? ahora "Calculate" puede llamar a Power de la misma manera que llama a las otras operaciones PERO "Calcualte" NUNCA fue modificada.""")

0 commit comments

Comments
 (0)