Skip to content

Commit 5d2a7db

Browse files
committed
added solid principles:
- readMe file - assets needed
1 parent be6c1c0 commit 5d2a7db

File tree

5 files changed

+452
-0
lines changed

5 files changed

+452
-0
lines changed

3_solid_principles/README.md

+392
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
# **_`Solid Principles`_**
2+
3+
1. [1. Single Responsibility Principle (SRP)](<#1.-Single-Responsibility-Principle-(SRP)>)
4+
1. [2. Open closed principle](#2.-Open-closed-principle)
5+
1. [3. Liskov Substitution Principle (LSP)](<#3.-Liskov-Substitution-Principle-(LSP)>)
6+
1. [4. Interface Segregation Principle (ISP)](<#4.-Interface-Segregation-Principle-(ISP)>)
7+
1. [5. Dependency Inversion Principle (DIP)](<#5.-Dependency-Inversion-Principle-(DIP)>)
8+
9+
## 1. Single Responsibility Principle (SRP)
10+
11+
- a class should only have a single responsibility
12+
- so that it could change for one reason and no more.
13+
- In other words,
14+
- you should create classes dealing with a single duty
15+
- so that they’re easier to maintain and harder to break.
16+
17+
### ❌ Wrong
18+
19+
```dart
20+
class Shapes {
21+
List<String> cache = List<>();
22+
// Calculations
23+
double squareArea(double l) { /* ... */ }
24+
double circleArea(double r) { /* ... */ }
25+
double triangleArea(double b, double h) { /* ... */ }
26+
// Paint to the screen
27+
void paintSquare(Canvas c) { /* ... */ }
28+
void paintCircle(Canvas c) { /* ... */ }
29+
void paintTriangle(Canvas c) { /* ... */ }
30+
// GET requests
31+
String wikiArticle(String figure) { /* ... */ }
32+
void _cacheElements(String text) { /* ... */ }
33+
}
34+
```
35+
36+
### ✅ Right
37+
38+
- class for each operation or logic
39+
40+
```dart
41+
// Calculations and logic
42+
abstract class Shape {
43+
double area();
44+
}
45+
class Square extends Shape {}
46+
class Circle extends Shape {}
47+
class Rectangle extends Shape {}
48+
// UI painting
49+
class ShapePainter {}
50+
// Networking
51+
class ShapesOnline {}
52+
```
53+
54+
- There are 3 separated classes focusing on a single task to accomplish:
55+
56+
they are easier to read, test, maintain and understand.
57+
58+
---
59+
60+
---
61+
62+
## 2. Open closed principle
63+
64+
- in a good architecture
65+
- you should be able to add new behaviors
66+
- without modifying the existing source code.
67+
- This concept is notoriously described with the sentence:
68+
> **"software entities should be open for extensions but closed for modifications "**.
69+
70+
### ❌ Wrong
71+
72+
```dart
73+
/// SRP class
74+
class Rectangle {
75+
final double width;
76+
final double height;
77+
Rectangle(this.width, this.height);
78+
}
79+
80+
/// SRP class
81+
class Circle {
82+
final double radius;
83+
Rectangle(this.radius);
84+
double get PI => 3.1415;
85+
}
86+
87+
/// problem is here
88+
class AreaCalculator {
89+
double calculate(Object shape) {
90+
if (shape is Rectangle) {
91+
// Smart cast
92+
return r.width * r.height;
93+
} else {
94+
final c = shape as Circle;
95+
return c.radius * c.radius * c.PI;
96+
}
97+
}
98+
}
99+
```
100+
101+
- Both Rectangle and Circle respect the SRP
102+
- The problem is inside AreaCalculator:
103+
- because if we added other shapes,
104+
- we would have to edit the code to **add more if conditions**
105+
106+
### ✅ Right
107+
108+
- replaced if => with interface
109+
- open for extensions >> open to add new shape
110+
- close for modification >> no written class or method will modified
111+
112+
```dart
113+
// Use it as an interface
114+
abstract class Area {
115+
double computeArea();
116+
}
117+
// Every class calculates the area by itself
118+
class Rectangle implements Area {}
119+
class Circle implements Area {}
120+
class Triangle implements Area {}
121+
class Rhombus implements Area {}
122+
class Trapezoid implements Area {}
123+
class AreaCalculator {
124+
double calculate(Area shape) {
125+
return shape.computeArea();
126+
}
127+
}
128+
129+
```
130+
131+
- Thanks to the **interface**,
132+
- now we have the possibility to add or remove as many classes as we want
133+
- without changing AreaCalculator.
134+
135+
- For example, if we added class Square implements Area it would automatically be "compatible" with the double calculate(...) method.
136+
137+
- Ÿ The gist of this principle is: depend on abstractions and not on implementations.
138+
- Thanks to abstract classes you work with abstractions and not with the concrete implementations: your code doesn’t rely on "predefined" entities.
139+
140+
---
141+
142+
---
143+
144+
## 3. Liskov Substitution Principle (LSP)
145+
146+
- that **`subclasses should be replaceable with superclasses`**
147+
148+
- without altering the logical correctness of the program.
149+
150+
- In practical terms, it means that a
151+
- subtype must guarantee the "usage conditions" of its supertype
152+
- plus something more it wants
153+
154+
### ❌ Wrong
155+
156+
```dart
157+
class Rectangle {
158+
double width;
159+
double height;
160+
Rectangle(this.width, this.height);
161+
}
162+
class Square extends Rectangle {
163+
Square(double length): super(length, length);
164+
}
165+
166+
/// Fail >>> Not Valid change width and height with different values of square
167+
void main() {
168+
Rectangle fail = Square(3);
169+
fail.width = 4;
170+
fail.height = 8;
171+
}
172+
```
173+
174+
- We have a big logic problem here.
175+
- A square must have 4 sides with the same length
176+
- but the rectangle doesn’t have this restriction.
177+
- at this point we have a square with 2 sides of length 4 and 2 sides of length 8
178+
... which is absolutely wrong!
179+
180+
- This example also shows that:
181+
- inheriting from **abstract classes or interfaces**,
182+
- rather than **concrete classes**,
183+
- is a very good practice. Prefer composition (with interfaces) over inheritance.
184+
185+
### ✅ Right
186+
187+
- to solve this problem,
188+
- `simply make Rectangle and Square two independent classes`.
189+
- Breaking LSP does not occur if you depend from interfaces:
190+
- they don’t provide any logic implementation as it’s deferred to the actual classes.
191+
192+
```dart
193+
abstract class Shape {
194+
double computeArea();
195+
}
196+
class Rectangle implements Shape {}
197+
class Square extends Shape {}
198+
```
199+
200+
---
201+
202+
## 4. Interface Segregation Principle (ISP)
203+
204+
- A client doesn’t have to be forced to implement a behavior it doesn’t need.
205+
- What turns out from this is:
206+
- you should create small interfaces with minimal methods.
207+
- Generally `it’s better having 8 interfaces with 1 method instead of 1 interface with 8 methods`.
208+
209+
### ❌ Wrong
210+
211+
```dart
212+
// Interfaces
213+
abstract class Worker {
214+
void work();
215+
void sleep();
216+
}
217+
class Human implements Worker {
218+
void work() => print("I do a lot of work");
219+
void sleep() => print("I need 10 hours per night...");
220+
}
221+
class Robot implements Worker {
222+
void work() => print("I always work");
223+
void sleep() {} // ??
224+
}
225+
226+
```
227+
228+
### ✅ Right
229+
230+
This is definitely better because
231+
232+
- there are no useless methods
233+
- and we’re free to decide which behaviors should the classes implement.
234+
235+
```dart
236+
// Interfaces
237+
abstract class Worker {
238+
void work();
239+
}
240+
abstract class Sleeper {
241+
void sleep();
242+
}
243+
class Human implements Worker, Sleeper {
244+
void work() => print("I do a lot of work");
245+
void sleep() => print("I need 10 hours per night...");
246+
}
247+
class Robot implements Worker {
248+
void work() => print("I always work");
249+
}
250+
```
251+
252+
---
253+
254+
## 5. Dependency Inversion Principle (DIP)
255+
256+
`very important`
257+
258+
- DIP states that we should code against abstractions and not implementations.
259+
260+
- ✅ Extending an abstract class is good
261+
- ✅ and implement an interface is good
262+
263+
- ❌ but descending from a concrete classed with no abstract methods is bad.
264+
265+
<img src=".\assets\dipQuote.png" align="center"/>
266+
267+
<table align="center">
268+
<tr>
269+
<td> ❌ Wrong </td>
270+
<td> ✅ Right </td>
271+
</tr>
272+
<tr>
273+
<td><img src = "assets/wrong_dip.png"></td>
274+
<td><img src = "assets/right_dip.png"></td>
275+
</tr>
276+
<tr>
277+
<td>
278+
<p>in the high level (notification class) I created 2 objects of the 2 classes hotMail and gmail</p>
279+
</td>
280+
<td>
281+
<p>hotMail and gmail Implemented the IMessage interface so then in the high level (Notification class) I declared an object from the IMessage Interface</p>
282+
</td>
283+
</tr>
284+
</table>
285+
286+
<img align="center" src = "assets/dip_explanation.png">
287+
288+
client = dependency = the thing that we injected (gmail and hotMail)
289+
290+
### ❌ Wrong
291+
292+
```dart
293+
/// Low Level
294+
class HotMail {
295+
send() {}
296+
}
297+
298+
class Gmail {
299+
send() {}
300+
}
301+
302+
/// high level
303+
class Notification {
304+
HotMail hotMail = HotMail();
305+
Gmail gmail = Gmail();
306+
307+
void sendGmail() => gmail.send();
308+
void sendHotMail() => hotMail.send();
309+
}
310+
311+
/// in Main
312+
void main(List<String> args) {
313+
Notification notification = Notification();
314+
notification.sendGmail();
315+
notification.sendHotMail();
316+
}
317+
318+
```
319+
320+
### ✅ Right
321+
322+
```dart
323+
324+
/// Low Level
325+
abstract class BaseMail {
326+
send();
327+
}
328+
329+
class HotMail implements BaseMail {
330+
@override
331+
send() {}
332+
}
333+
334+
class Gmail implements BaseMail {
335+
@override
336+
send() {}
337+
}
338+
339+
/// high level
340+
class Notification {
341+
BaseMail baseMail;
342+
343+
Notification(this.baseMail);
344+
345+
void send() => baseMail.send();
346+
}
347+
348+
/// in Main
349+
void main(List<String> args) {
350+
Notification notification = Notification(Gmail());
351+
notification.send();
352+
}
353+
```
354+
355+
---
356+
357+
- Dependency injection (DI) is a very famous way to implement the DIP.
358+
- Depending on abstractions gives the freedom to be independent from the implementation.
359+
- Look at this example:
360+
361+
```dart
362+
// Use this as interface
363+
abstract class EncryptionAlgorithm {
364+
String encrypt(); // <-- abstraction
365+
}
366+
class AlgoAES implements EncryptionAlgorithm {}
367+
class AlgoRSA implements EncryptionAlgorithm {}
368+
class AlgoSHA implements EncryptionAlgorithm {}
369+
370+
class FileManager {
371+
void secureFile(EncryptionAlgorithm algo) {
372+
algo.encrypt();
373+
}
374+
}
375+
```
376+
377+
- The `FileManager class` knows nothing about how algo works, it’s just aware that the encrypt() => Method secures a file.
378+
379+
- This is essential for maintenance because we can call the method as we want:
380+
381+
```dart
382+
void main(){
383+
final fm = FileManager(...);
384+
fm.secureFile(AlgoAES());
385+
fm.secureFile(AlgoRSA());
386+
}
387+
```
388+
389+
If we added another encryption algorithm, it would be automatically compatible with secureFile
390+
as it is a subtype of EncryptionAlgorithm.
391+
392+
**In this example, Done the 5 SOLID principles all together.**
265 KB
Loading
202 KB
Loading
231 KB
Loading

0 commit comments

Comments
 (0)