Nguyên tắc SOLID
SOLID là một Nguyên tắc thiết kế hướng đối tượng giúp cho chương trình phần mềm dễ đọc, dễ hiểu, dễ bảo trì và mở rộng.
Hôm nay mình muốn cùng bạn thảo luận và tìm hiểu một chút về Nguyên tắc SOLID.
Mình còn nhớ, trong buổi phỏng vấn của mình lần đầu tiên, mình đã từng được nhà tuyển dụng hỏi một câu "Bạn biết gì về Nguyên tắc SOLID?"
Và mình đã fail trong câu hỏi này.
Mình có từng biết đến SOLID.
Nhưng mình chưa bao giờ tìm hiểu.
Chắc chắn có nhiều người như mình. Vì đây là một tình trạng chung, nếu bạn vừa mới tự học thì SOLID có vẻ chẳng phải vấn đề bạn cần quan tâm.
Nhưng, đối với các doanh nghiệp. SOLID lại là một trong những nguyên tắc quan trọng nhất trong phát triển phần mềm.
Vì vậy, hôm nay, mình muốn nói đến chủ đề này với một vài định nghĩa cơ bản và các ví dụ dễ dàng để giúp chúng ta hiểu được khái niệm nói chung về SOLID.
S.O.L.I.D là gì?
S.O.L.I.D là từ đầu tiên trong 5 nguyên tắc thiết kế hướng đối tượng do Robert C. Martin đưa ra.
S --- Single responsibility principle
O --- Open closed principle
L --- Liskov substitution principle
I --- Interface segregation principle
D --- Dependency Inversion principle
Đây là một tập hợp các nguyên tắc mà chúng ta có thể ghi nhớ về mặt lập trình, tinh chỉnh và cấu trúc mã.
SOLID làm gì?
Bằng cách đọc tên nó, chắc bạn cũng như mình, chưa thể hiểu nó đùng để làm gì.
Hiểu theo cách đơn giản, những nguyên tắc này giúp giúp các lập trình viên phát triển các phần mềm dễ dàng và Clean hơn, dễ bảo trì và sửa đổi khi quy mô của phần mềm phát triển lớn hơn, phức tạp hơn.
Vì vậy, chúng ta thực sự có thể hiểu khái niệm này như một cách tiếp cận lập trình hướng đối tượng bằng cách cắt giảm phần không cần thiết và sắp xếp mã theo cách tinh chỉnh và có thể tái cấu trúc.
Một vài ví dụ về SOLID
Nguyên tắc #1: Single-responsibility Principle
Single-responsibility Principle là nguyên tắc trách nhiệm duy nhất.
Một class nên có một và chỉ một lý do để thay đổi, nghĩa là một class chỉ nên có trách nhiệm duy nhất.
Nó đơn giản là một class chỉ nên thực hiện một công việc duy nhất chứ không phải nhiều công việc trong cùng một class.
Hãy xem xét ví dụ bên dưới:
class Book {
protected $Author;
public getAuthor($Author) {
return $this->Author;
}
public function formatJson() {
return json_encode($this->getAuthor());
}
}
Bạn nghĩ sao?
Chúng ta có thể thấy rõ rằng class có tên "Book" đang thực hiện nhiều hơn một công việc.
Nó đang lấy Author của một cuốn sách và nó cũng đang mã hóa output thành định dạng Json.
Nhưng điều gì sẽ xảy ra nếu chúng ta muốn có một ouput ở một định dạng khác, không chỉ là Json?
Trong trường hợp đó, có lẽ chúng ta phải viết thêm vài dòng để thêm phương thức hoặc sửa đổi code hiện có.
Nó có thể trông giống như một công việc nhỏ vào lúc này bởi vì ví dụ này chúng ta có ngay ở đây cực kỳ đơn giản.
Nhưng hãy thử tưởng tượng, nếu bạn có hàng trăm hoặc thậm chí hàng ngàn class thì sao?
Tốt hơn hết là ngăn chặn sự nhầm lẫn ngay từ trong trứng nước bằng cách thực hiện các nguyên tắc trách nhiệm duy nhất.
Vì vậy, ví dụ trên chúng ta nên đổi thành như thế này:
class Book {
protected $Author;
public getAuthor($Author){
return $this->Author;
}
}
class JsonBookForm {
public function format(Book $Author) {
return json_encode($Author->getAuthor());
}
}
Bằng cách này, nếu chúng ta muốn có được một loại ouput khác thì chúng ta không cần phải thay đổi trực tiếp trong class Book.
Nguyên tắc #2: Open-closed Principle
Các class có thể được mở rộng thoải mái, nhưng không cho phép sửa đổi bên trong class đó.
Mình biết là cũng hơi khó hiểu, nhưng điều này có nghĩa là các class nên được mở rộng để có thể thay đổi chức năng, thay vì được sửa đổi bên trong nó.
Có nghĩa là, khi ai đó muốn thêm nhiều chức năng hơn thì nên mở rộng và thêm vào, mà không phải sửa đổi trực tiếp trên class hiện tại.
Thử xem một ví dụ phổ biến về nguyên tắc Đóng - Mở:
class Triangle{
public $width;
public $height;
}
class Board {
public $triangles= [];
public function calculateArea() {
$area = 0;
foreach ($this->triangles as $triangle) {
$area += $triangle->width * $triangle->height* 1/2;
}
}
}
Chúng ta có một class Triangle chứa dữ liệu cho tam giác có chiều rộng ($width) và chiều cao ($height) và một class Board được sử dụng như một mảng các đối tượng Triangle.
Đoạn code này tại thời điểm này trông hoàn toàn ổn nhưng khi bạn nghĩ về hình dạng và hàm tính toán diện tích. Có thể bạn sẽ muốn sử dụng lại hàm này để tính toán các hình dạng khác nhau, mà không phải chỉ để tính diện tích tam giác.
Hiện tại, đoạn code này không cho phép điều này vì nó giới hạn class Board chỉ hoạt động với class Triangle.
Cách tiếp cận mới chúng ta có thể thử ở đây là:
interface Shape {
public function area();
}
class Triangle implements Shape {
public function area() {
return $this->width * $this->height *1/2;
}
}
class Circle implements Shape {
public function area() {
return $this->radius * $this->radius * pi();
}
}
class Board {
public $shapes;
public function calculateArea() {
$area = 0;
foreach ($this->shapes as $shape) {
$area+= $shape->area();
}
return $area;
}
}
Theo cách này, chúng ta không cần chỉ định tên của class trong tên class Board
Vì vậy bất cứ khi nào chúng ta muốn tính toán diện tích khác như là hình chữ nhật. Chúng ta có thể dễ dàng thêm một class có tên Rectangle và thực hiện khai triển interface shape để chúng ta có thể sử dụng area() trong một class Board.
Nguyên tắc #3: Liskov substitution principle
Đặt q(x) là một thuộc tính có thể chứng minh được về các đối tượng x thuộc loại B. Sau đó, q(y) có thể chứng minh được đối với các đối tượng y loại A. Trong đó A là một kiểu con của B.
Hãy nhìn vào hình sau:
Cái này nhìn có quen không?
Chúng ta có thể đã nhìn thấy cái này ở trường và mình chắc chắn bạn đã được học trong môn toán.
Trường hợp biểu đồ này có thể được ngụ ý trong thế giới của chúng ta là một cái gì đó liên quan đến ví dụ trước..
Nếu nói về hình dạng, có rất nhiều hình dạng, phải không? Có hình tròn, hình tam giác và hình chữ nhật và hình vuông ..
Đợi chút..., chúng ta đều biết rằng hình vuông là một dạng hình chữ nhật đặc biệt.
Có nghĩa là hình chữ nhật + một số điều kiện để trở thành hình vuông. Vì thế, nó sẽ như thế này.
Trong khi lập trình theo nguyên tắc này cũng như vậy, ví dụ, thông thường chúng ta sẽ làm thế này:
class Rectangle {
public function setW($w) {
$this->width = $w;
}
public function setH($h) {
$this->height = $h;
}
public function Area() {
return $this->height * $this->width;
}
}
class Square extends Rectangle {
public function set($w) {
$this->width = $w;
$this->height = $w;
}
public function setH=($h) {
$this->height = $h;
$this->width = $h;
}
}
Nhưng bạn có nhận ra rằng có nhiều dòng code giống y hệt nhau không?
Và thực tế là chúng ta đang cố gắng hết sức để làm cho chương trình có thể dễ dàng duy trì và hiệu quả cao.
Vậy thì nên tránh cách trên và thử theo cách này:
interface Setshape {
public function setH($h);
public function setW($w);
public function Area();
}
class Rectangle implements Setshape;
class Square implements Setshape;
Bằng cách này, chúng ta không phải viết cùng một đoạn code nhiều lần. Chúng ta chỉ đơn giản khai triển interface cho class.
Nguyên tắc #4: Interface segregation principle
Một client không bao giờ bị buộc phải thực hiện một interface mà nó không sử dụng hoặc client không nên bị buộc phải phụ thuộc vào các phương thức mà chúng không sử dụng.
Chúng ta gần như ở đó, nguyên tắc thứ tư có nghĩa là các class không nên bị buộc phải thực hiện các interface mà chúng không sử dụng.
Ví dụ, giả sử có một số class trông giống như:
interface Employee {
public function generatereport()
public function clockin()
public function clockout()
public function customerservice()
public function getPaid()
}
Và giả sử có một người quản lý thực hiện hầu hết các chức năng trên nhưng không phải cần đến clockin và clockout, nên hai function này sẽ không bao giờ được sử dụng.
Để tuân theo nguyên tắc này một lần nữa, chúng ta nên tiếp cận nó theo cách này.
interface Generalemployee{
public function clockin()
public function clockout()
}
interface Employee{
public function customerservice()
public function getPaid()
}
interface management{
public function generatereport()
}
class Staff implements Generalemployee, Employee{
}
class Manager implements Employee, management{
}
Nguyên tắc #5: Dependency Inversion principle
Các thực thể phải phụ thuộc vào trừu tượng không phải phụ thuộc vào cụ thể. Nó nói rằng mô-đun cấp cao không được phụ thuộc vào mô-đun cấp thấp, chúng nên phụ thuộc vào trừu tượng.
Mình sẽ đi thẳng vào ví dụ:
class Getuserlists {
private $dbConn;
public function __construct(MySQLConn, $dbConn) {
$this->dbConn= $dbConn;
}
}
Đó là một lỗi phổ biến vì theo cấu trúc phân cấp, class Getuserlists là mô-đun cao hơn và MySQLConn là mô-đun thấp hơn.
Tuy nhiên, bạn có thể dễ dàng nhận thấy rằng class này phụ thuộc hoàn toàn vào MySQLConn và nó sẽ gây nhầm lẫn, nếu sau này chúng ta muốn sử dụng cơ sở dữ liệu khác không chỉ là MySQL.
Vì thế, giải pháp cho vấn đề này sẽ là ...
interface DbConnectionInterface {
public function connect();
}
class MySqlConn implements DbConnectionInterface {
public function connect() {}
}
class Getuserlists {
private $dbConn;
public function __construct(DbConnectionInterface $dbConn) {
$this->dbConn= $dbConn;
}
}
Theo đoạn code ở trên, bây giờ bạn có thể thấy rằng cả hai mô-đun cấp cao và cấp thấp đều phụ thuộc vào sự trừu tượng hóa.
Xin chúc mừng!! Chúng ta đã tiến thêm một bước trong thế giới lập trình hướng đối tượng với SOLID!!
Các nguyên tắc SOLID ban đầu dường như rất khó hiểu, và vẫn sẽ cần phải luyện tập thật nhiều trước khi chúng ta có thể hoàn toàn tuân theo 5 nguyên tắc của SOLID để vận dụng vào lập trình.
Lưu ý:
Hãy nắm một ngôn ngữ trước khi biết vận dụng SOLID một cách "Lô hỏa thuần thanh". Nếu còn chưa chắc về ngôn ngữ thì,...
Tham gia ngay: KHÓA HỌC LẬP TRÌNH JAVA (Full Stack), khóa học do chuyên gia doanh nghiệp hàng đầu hướng dẫn.
Sẽ rất tốt khi có người hướng dẫn tận tình, bạn học cùng chung chí hướng chinh phục một ngôn ngữ nào đó. Mới bắt đầu thì đừng học một mình bạn nhé!
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Học Lập trình chất lượng cao (Since 2002). Học thực tế + Tuyển dụng ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0383.180086
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #icthanoi #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp #java #php #python