1. Tổng quan
SOLID là viết tắt của 5 chữ cái đầu trong 5 nguyên tắc thiết kế hướng đối tượng. Giúp cho lập trình viên viết ra những đoạn code dễ đọc, dễ hiểu, dễ maintain. Nó được đưa ra bởi Robert C. Martin và Michael Feathers. 5 nguyên tắc đó bao gồm:
- Single responsibility priciple (SRP)
- Open/Closed principle (OCP)
- Liskov substitution principe (LSP)
- Interface segregation principle (ISP)
- Dependency inversion principle (DIP)
2. Các nguyên tắc
The Single Responsibility Principle (SRP)
There should never be more than one reason for a class to change. In other words, every class should have only one responsibility.
Nguyên lý này nói rằng mỗi lớp chỉ nên chịu một trách nhiệm (chịu 1 công việc) cụ thể nào đó mà thôi.
Do đó mỗi lần chúng ta tạo/sửa một class. Phải luôn tự hỏi trong lớp này đã đảm nhiệm bao nhiêu vai trò rồi?
Hãy xem ví dụ sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">Handler</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">handle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> data <span class="token operator">=</span> <span class="token function">requestDataToAPI</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">let</span> array <span class="token operator">=</span> <span class="token function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">:</span> data<span class="token punctuation">)</span> <span class="token function">saveToDB</span><span class="token punctuation">(</span>array<span class="token punctuation">:</span> array<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">func</span> <span class="token function-definition function">requestDataToAPI</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Data</span> <span class="token punctuation">{</span> <span class="token comment">// send API request and wait the response</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">func</span> <span class="token function-definition function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">:</span> <span class="token class-name">Data</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">[</span><span class="token class-name">String</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token comment">// parse the data and create the array</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">func</span> <span class="token function-definition function">saveToDB</span><span class="token punctuation">(</span>array<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token class-name">String</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// save the array in a database (CoreData/Realm/...)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Sau khi mọi người đọc đoạn code trên, thì mọi người thấy class trên chịu trách nhiệm làm bao nhiêu công việc?
Class Handler chịu trách nhiệm lấy data từ API (1), parse data sang kiểu mảng string (2) và lưu data vào database (3). Áp dụng vào dự án thực tế, chúng ta dùng Alamofire để call API (1), ObjectMapper để parse data (2) và dùng Core Data để lưu data vào database (3). Đến lúc đó, code ở ví dụ sẽ trở nên cồng kềnh, khó bảo trì vào mở rộng.
Cách xử lý:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">class</span> <span class="token class-name">Handler</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> apiHandler<span class="token punctuation">:</span> <span class="token class-name">APIHandler</span> <span class="token keyword">let</span> parseHandler<span class="token punctuation">:</span> <span class="token class-name">ParseHandler</span> <span class="token keyword">let</span> dbHandler<span class="token punctuation">:</span> <span class="token class-name">DBHandler</span> <span class="token keyword">init</span><span class="token punctuation">(</span>apiHandler<span class="token punctuation">:</span> <span class="token class-name">APIHandler</span><span class="token punctuation">,</span> parseHandler<span class="token punctuation">:</span> <span class="token class-name">ParseHandler</span><span class="token punctuation">,</span> dbHandler<span class="token punctuation">:</span> <span class="token class-name">DBHandler</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>apiHandler <span class="token operator">=</span> apiHandler <span class="token keyword">self</span><span class="token punctuation">.</span>parseHandler <span class="token operator">=</span> parseHandler <span class="token keyword">self</span><span class="token punctuation">.</span>dbHandler <span class="token operator">=</span> dbHandler <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">handle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> data <span class="token operator">=</span> apiHandler<span class="token punctuation">.</span><span class="token function">requestDataToAPI</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">let</span> array <span class="token operator">=</span> parseHandler<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">:</span> data<span class="token punctuation">)</span> dbHandler<span class="token punctuation">.</span><span class="token function">saveToDB</span><span class="token punctuation">(</span>array<span class="token punctuation">:</span> array<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">class</span> <span class="token class-name">APIHandler</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">requestDataToAPI</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Data</span> <span class="token punctuation">{</span> <span class="token comment">// send API request and wait the response</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">ParseHandler</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">parse</span><span class="token punctuation">(</span>data<span class="token punctuation">:</span> <span class="token class-name">Data</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">[</span><span class="token class-name">String</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token comment">// parse the data and create the array</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">DBHandler</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">saveToDB</span><span class="token punctuation">(</span>array<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token class-name">String</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// save the array in a database (CoreData/Realm/...)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Nguyên lý này giúp class của bạn clean nhất có thể. Ngoài ra, ở ví dụ đầu tiên, bạn không thể test được requestDataToAPI, parse and saveToDB một cách trực tiếp, vì nó là các private methods. Sau khi sửa lại code, chúng ra có thể dễ dàng testing các hàm này.
The Open-Closed Principle (OCP)
Software entities … should be open for extension, but closed for modification.
Nguyên lý nói rằng chúng ta không nên sửa đổi class có sẵn, chỉ nên mở rộng nó.
Nếu bạn muốn tạo một lớp dễ bảo trì, thì phải có 2 điều kiện quan trọng sau đây:
- Open for extension: Có thể dễ dàng thêm và thay đổi hành vi (behaviours) của class đó 1 cách dễ dàng.
- Close for modification: Không được thay đổi những hành vi đã có sẵn của class.
Chúng ta có ví dụ sau, class Logger có nhiệm vụ in ra chi tiết những lớp Car
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 | <span class="token keyword">class</span> <span class="token class-name">Logger</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">printData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> cars <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Batmobile"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Black"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"SuperCar"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Gold"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"FamilyCar"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Grey"</span></span><span class="token punctuation">)</span> <span class="token punctuation">]</span> cars<span class="token punctuation">.</span>forEach <span class="token punctuation">{</span> car <span class="token keyword">in</span> <span class="token function">print</span><span class="token punctuation">(</span>car<span class="token punctuation">.</span><span class="token function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Car</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> name<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">let</span> color<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">init</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name <span class="token keyword">self</span><span class="token punctuation">.</span>color <span class="token operator">=</span> color <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string-literal"><span class="token string">"I'm </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">name</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string"> and my color is </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">color</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Vậy nếu chúng ta muốn class Logger in thêm chi tiết của lớp mới khác, thì chúng ta phải thay đổi hàm printData() mỗi lần như vậy. Điều này vi phạm nguyên lý OCP mà chúng ta đang giới thiệu.
Ví dụ mình sẽ thêm 1 class Bicycle. Mọi người sẽ thấy mình phải thay đổi lại hàm printData() của class Logger
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 | <span class="token keyword">class</span> <span class="token class-name">Logger</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">printData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> cars <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Batmobile"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Black"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"SuperCar"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Gold"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"FamilyCar"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Grey"</span></span><span class="token punctuation">)</span> <span class="token punctuation">]</span> cars<span class="token punctuation">.</span>forEach <span class="token punctuation">{</span> car <span class="token keyword">in</span> <span class="token function">print</span><span class="token punctuation">(</span>car<span class="token punctuation">.</span><span class="token function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> bicycles <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token class-name">Bicycle</span><span class="token punctuation">(</span>type<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"BMX"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Bicycle</span><span class="token punctuation">(</span>type<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Tandem"</span></span><span class="token punctuation">)</span> <span class="token punctuation">]</span> bicycles<span class="token punctuation">.</span>forEach <span class="token punctuation">{</span> bicycles <span class="token keyword">in</span> <span class="token function">print</span><span class="token punctuation">(</span>bicycles<span class="token punctuation">.</span><span class="token function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Car</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> name<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">let</span> color<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">init</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name <span class="token keyword">self</span><span class="token punctuation">.</span>color <span class="token operator">=</span> color <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string-literal"><span class="token string">"I'm </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">name</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string"> and my color is </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">color</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Bicycle</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> type<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">init</span><span class="token punctuation">(</span>type<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>type <span class="token operator">=</span> type <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string-literal"><span class="token string">"I'm a </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">type</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Cách giải quyết: Chúng ta sẽ tạo ra 1 protocol Printable, những class phương tiện (Car, Bicycle) sẽ conform protocol này. Mà hàm printData() sẽ in ra 1 mảng của Printable
Bằng cách đó, chúng ta đã tạo thêm 1 lớp trừu tượng nằm giữa printData() và lớp cần in dữ liệu. Cho phép thêm những lớp mới (ví dụ như Bicycle) mà không cần phải thay đổi code trong hàm printData()
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 | <span class="token keyword">protocol</span> <span class="token class-name">Printable</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Logger</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">printData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> cars<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token class-name">Printable</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Batmobile"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Black"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"SuperCar"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Gold"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Car</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"FamilyCar"</span></span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Grey"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Bicycle</span><span class="token punctuation">(</span>type<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"BMX"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Bicycle</span><span class="token punctuation">(</span>type<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Tandem"</span></span><span class="token punctuation">)</span> <span class="token punctuation">]</span> cars<span class="token punctuation">.</span>forEach <span class="token punctuation">{</span> car <span class="token keyword">in</span> <span class="token function">print</span><span class="token punctuation">(</span>car<span class="token punctuation">.</span><span class="token function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Car</span><span class="token punctuation">:</span> <span class="token class-name">Printable</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> name<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">let</span> color<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">init</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> color<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name <span class="token keyword">self</span><span class="token punctuation">.</span>color <span class="token operator">=</span> color <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string-literal"><span class="token string">"I'm </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">name</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string"> and my color is </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">color</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Bicycle</span><span class="token punctuation">:</span> <span class="token class-name">Printable</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> type<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token keyword">init</span><span class="token punctuation">(</span>type<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>type <span class="token operator">=</span> type <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">printDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string-literal"><span class="token string">"I'm a </span><span class="token interpolation-punctuation punctuation">(</span><span class="token interpolation">type</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The Liskov Substitution Principle (LSP)
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
Nguyên lý này nói rằng: nếu class A là class con của class B. Thì những hàm trong class A phải thực hiện những hành động giống với class B. Để hiểu hơn nguyên lý này. Chúng ta hãy xem ví dụ sau:
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 | <span class="token keyword">class</span> <span class="token class-name">Rectangle</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> width<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token keyword">var</span> height<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token keyword">init</span><span class="token punctuation">(</span>width<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> height<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>width <span class="token operator">=</span> width <span class="token keyword">self</span><span class="token punctuation">.</span>height <span class="token operator">=</span> height <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Int</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> width <span class="token operator">*</span> height <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Square</span><span class="token punctuation">:</span> <span class="token class-name">Rectangle</span> <span class="token punctuation">{</span> <span class="token keyword">override</span> <span class="token keyword">var</span> width<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token punctuation">{</span> <span class="token keyword">didSet</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">.</span>height <span class="token operator">=</span> width <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">var</span> height<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token punctuation">{</span> <span class="token keyword">didSet</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">.</span>width <span class="token operator">=</span> height <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Chúng ta có class Rectangle và 1 hàm tính diện tích (chiều dài nhân chiều rộng), class Square vì là hình vuông nên chúng ta có chiều dài bằng chiều rộng.
Chúng ta hãy xem cách tính diện tích của class Square:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">func</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> square <span class="token operator">=</span> <span class="token class-name">Square</span><span class="token punctuation">(</span>width<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span> height<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">let</span> rectangle<span class="token punctuation">:</span> <span class="token class-name">Rectangle</span> <span class="token operator">=</span> square rectangle<span class="token punctuation">.</span>height <span class="token operator">=</span> <span class="token number">7</span> rectangle<span class="token punctuation">.</span>width <span class="token operator">=</span> <span class="token number">5</span> <span class="token function">print</span><span class="token punctuation">(</span>rectangle<span class="token punctuation">.</span><span class="token function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// As a rectangle we should expect the area as 7 x 5 = 35, but we got 5 x 5 = 25</span> <span class="token punctuation">}</span> |
Theo nguyên lý LSP, vì class Square kế thừa từ class Rectangle. Nên hàm area() phải luôn có giá trị bằng chiều dài nhân với chiều rộng (ở đây là 7×5 = 35). Tuy nhiên chúng ta lại nhận được diện tích là 25. Do đó, ví dụ trên đã vi phạm nguyên lý LSP này.
Cách giải quyết: Chúng ta sử dụng protocol Geometrics chứa hàm tính diện tích. Vậy class Square không còn kế thừa từ class Rectangle nữa. Mà cả 2 class này kế thừa từ protocol.
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 | <span class="token keyword">protocol</span> <span class="token class-name">Geometrics</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Int</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Rectangle</span><span class="token punctuation">:</span> <span class="token class-name">Geometrics</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> width<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token keyword">var</span> height<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token keyword">init</span><span class="token punctuation">(</span>width<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> height<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>width <span class="token operator">=</span> width <span class="token keyword">self</span><span class="token punctuation">.</span>height <span class="token operator">=</span> height <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Int</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> width <span class="token operator">*</span> height <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Square</span><span class="token punctuation">:</span> <span class="token class-name">Geometrics</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> edge<span class="token punctuation">:</span> <span class="token class-name">Int</span> <span class="token keyword">init</span><span class="token punctuation">(</span>edge<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>edge <span class="token operator">=</span> edge <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Int</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> edge <span class="token operator">*</span> edge <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> rectangle<span class="token punctuation">:</span> <span class="token class-name">Geometrics</span> <span class="token operator">=</span> <span class="token class-name">Rectangle</span><span class="token punctuation">(</span>width<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span> height<span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token function">print</span><span class="token punctuation">(</span>rectangle<span class="token punctuation">.</span><span class="token function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 10*5 = 50</span> <span class="token keyword">let</span> rectangle2<span class="token punctuation">:</span> <span class="token class-name">Geometrics</span> <span class="token operator">=</span> <span class="token class-name">Square</span><span class="token punctuation">(</span>edge<span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token function">print</span><span class="token punctuation">(</span>rectangle2<span class="token punctuation">.</span><span class="token function">area</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 5*5 = 25</span> <span class="token punctuation">}</span> |
The Interface Segregation Principle (ISP)
Many client-specific interfaces are better than one general-purpose interface.
Nguyên lý này giới thiệu 1 trong những vấn đề của lập trình hướng đối tượng: interface quá to (the fat interface). Interface quá to, nghĩa là trong interface đó (ở trong lập trình iOS có thể hiểu interface là protocol) có quá nhiều hàm/thuộc tính. Nó chứa nhiều thông tin không cần thiết. Hãy xem ví dụ sau:
Chúng ta có protocol GestureProtocol với hàm didTap()
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 | <span class="token keyword">protocol</span> <span class="token class-name">GestureProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token class-name">Sau</span> <span class="token number">1</span> thời gian chúng ta làm việc<span class="token punctuation">,</span> thì <span class="token keyword">protocol</span> này phình to ra<span class="token punctuation">.</span> <span class="token class-name">Nh</span>ư sau<span class="token punctuation">:</span> <span class="token keyword">protocol</span> <span class="token class-name">GestureProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function-definition function">didDoubleTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function-definition function">didLongPress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token class-name">Class</span> <span class="token class-name">SuperButton</span> của chúng ta cần cả <span class="token number">3</span> hàm trên<span class="token punctuation">,</span> nên nó sẽ đúng khi chúng ta <span class="token function">inform</span> <span class="token punctuation">(</span>kế thừa<span class="token punctuation">)</span> <span class="token class-name">SuperButton</span> với <span class="token class-name">GestureProtocol</span> <span class="token keyword">class</span> <span class="token class-name">SuperButton</span><span class="token punctuation">:</span> <span class="token class-name">GestureProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send tap action</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">didDoubleTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send double tap action</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">didLongPress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send long press action</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name">Tuy</span> nhiên<span class="token punctuation">,</span> bắt đầu phát sinh <span class="token number">1</span> vấn đề khác là<span class="token punctuation">:</span> chúng ta có <span class="token number">1</span> <span class="token keyword">class</span> <span class="token class-name">PoorButton</span><span class="token punctuation">.</span> <span class="token class-name">V</span>à <span class="token keyword">class</span> này chỉ cần duy nhất <span class="token number">1</span> hàm <span class="token function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">PoorButton</span><span class="token punctuation">:</span> <span class="token class-name">GestureProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send tap action</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">didDoubleTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">didLongPress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Vậy là class PoorButton này bỏ trống 2 hàm didDoubleTap() và didLongPress(), nghĩa là chúng ta đã truyền bị dư 2 hàm không sử dụng cho class PoorButton. Do đó đã vi phạm nguyên lý ISP chúng ta đang đề cập ở đây.
Cách giải quyết: Đưa những hàm này ra thành các protocol riêng lẻ. Và chỉ truyền những protocol cần thiết.
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 | <span class="token keyword">protocol</span> <span class="token class-name">TapProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">protocol</span> <span class="token class-name">DoubleTapProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didDoubleTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">protocol</span> <span class="token class-name">LongPressProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didLongPress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">SuperButton</span><span class="token punctuation">:</span> <span class="token class-name">TapProtocol</span><span class="token punctuation">,</span> <span class="token class-name">DoubleTapProtocol</span><span class="token punctuation">,</span> <span class="token class-name">LongPressProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send tap action</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">didDoubleTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send double tap action</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function-definition function">didLongPress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send long press action</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">PoorButton</span><span class="token punctuation">:</span> <span class="token class-name">TapProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function-definition function">didTap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// send tap action</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The Dependency Inversion Principle (DIP)
- High level modules should not depend upon low level modules. both should depend upon abstractions.
- Abstractions should not depend upon details. details should depend upon abstractions.
Nguyên lý này khá quan trọng đối với lập trình viên. Và nó cũng liên quan đến Dependency Injection (DJ). Vậy Dependency Inversion (DI) là gì? Và DJ là gì? Giữa DI và DJ có mối quan hệ như thế nào? Mình sẽ giới thiệu ở bài blog sau.
3. Kết luận
Nếu bạn làm theo nguyên lý SOLID, bạn có thể tăng chất lượng code của mình. Code của chúng ta sẽ trở nên dễ đọc, dễ bảo trì và dễ mở rộng hơn cho sau này. Tuy nhiên, để áp dụng nguyên lý SOLID vào dự án thực tế sẽ có nhiều khó khăn hơn. Nhưng vì thế, chúng ta càng phải nên nắm vững kiến thức nền tảng này để có thể áp dụng và sửa đổi khi làm dự án thực tế nhé.