You are on page 1of 24

SOILD

Nguyên lý S.O.L.I.D trong lập trình hướng


đối tượng
Nguyên lý S.O.L.I.D được sinh ra để giúp chúng ta trong quá trình lập trình
hướng đối tượng có thể tạo ra được những ứng dụng tốt hơn. Cụ thể là
những nguyên tắc này khuyến khích chúng ta tạo được những phần
mềm dễ maintain hơn, code dễ hiểu hơn. Và đồng thời "mềm dẻo" hơn.
Dẫn tới khi ứng dụng "phình to" , chúng ta sẽ giảm thiểu được độ phức
tạp. Đội ngũ phát triển sẽ tốn ít thời gian hơn.

Có quá nhiều lợi ích để chúng ta tuân thủ theo S.O.L.I.D phải không nào?
Tất nhiên nguyên lý vẫn là nguyên lý. Bạn hoàn toàn có thể không thể tuân
thủ. Hứng đâu thì viết code ở đó. Nhưng nên nhớ là đây là trích rút ra từ
kinh nghiệm của rất nhiều thế hệ developer đi trước. Còn bây giờ chúng ta
cùng đào sâu tìm hiểu về những nguyên tắc này:
1. Single Responsibility
2. Open/Closed
3. Liskov Substitution
4. Interface Segregation
5. Dependency Inversion

S.O.L.I.D là thực chất là ghép của 5 chữ cái đầu của các nguyên lý. Đọc vậy
cho dễ nhớ. Mà thực chất khi bạn có thể áp dụng thành thục những
nguyên lý trên bạn cũng có thể trở thành Solid developer hay chính là "Lập
trình viên cứng". Bây giờ chúng ta cùng mổ sẻ từng nguyên lý một.

1. Single Responsibility principle


Nội dung của nguyên lý:
Mỗi class chỉ nên chịu trách nhiệm về một nhiệm vụ cụ thể nào đó mà thôi.
Không có nhiều hơn một lý do để chỉnh sửa một class.

Ví dụ thực tế: Ta có class Coder

Coder.java

public class Coder {

private String name;

public void code(){

System.out.println("Đang code");

String allLettersToUpperCase(String s) { ... }

String findSubTextAndDelete(String s) { ... }

Tuy ở class Coder trên thì 2 phương thức ở allLettersToUpperCase và


findSubTextAndDelete đều hoạt động bình thường. Nhưng về mặt logic các
bạn có thể tự hỏi là nó có đúng là trách nhiệm của class trên không? Và
dường như hai phương thức - 2 chức năng đó không thuộc về class Coder.

Áp dụng nguyên lý Single Responsibility - đơn nhiệm, ta có thể refactor


code lại như sau:

Coder.java
public class Coder {

private String name;

public void code(){

System.out.println("Đang code");

TextUtils.java

public class TextUtils {

public static String allLettersToUpperCase(String s) { ... }

public static String findSubTextAndDelete(String s) { ... }

Sau khi refactor xong thì code của chúng ta đã dễ hiểu, dành mạch, rõ ràng
hơn rất nhiều. Tất nhiên là cũng dễ maintain hơn nữa.

2. Open/closed principle
Đây là một trong những nguyên lý rất quan trọng trong quá trình phát
triển phần mềm. Nguyên lý mà chúng ta cần luôn luôn ghi nhớ và áp dụng
thường xuyên. Nội dung nguyên lý:
Chúng ta có thể thoải mái mở rộng class nhưng không được chỉnh sửa nội
dung bên trong nó.
Nguyên lý này nghĩa là: Mỗi khi chúng ta thiết kế một class nào đó thì
chúng ta cũng viết làm sao cho sau này, mỗi khi một developer muốn thay
đổi luồng trong ứng dụng. Họ chỉ cần thừa kế class ta đã viết hoặc override
một hàm nào đó.

Nếu chúng ta không thiết kế class đủ tốt. Mà do đó những developer khác


khi muốn thay đổi luồng của ứng dụng. Họ bắt buộc phải sửa class của
chúng ta. Dẫn đến logic của toàn bộ ứng dụng cũng thay đổi theo. Điều này
rất gây hại cho quá trình phát triển ứng dụng.

Đây là một ví dụ tuân thủ nguyên lý OCP của chúng ta:

Khi tôi muốn viết một class tính toán diên tích của các hình thay vì viết một
class cụ thể để thực hiện điều này tôi sẽ tạo một abstract class chung. Sau
đó cho các class là các hình cụ thể implement nó:

Shape.java

public abstract class Shape

public abstract double Area();

public class Rectangle : Shape

public double Width { get; set; }

public double Height { get; set; }

public override double Area()

return Width*Height;
}

public class Circle : Shape

public double Radius { get; set; }

public override double Area()

return Radius*Radius*Math.PI;

Sau này có một developer X nào đó muốn tính thêm diện tích hình nào đó
thì cũng làm tương tự như với class Rectangle và Circle. Cũng chỉ cần thừa
kế class Shape và override phương thức Area.

3. Liskov Substitution Principle


Nội dung nguyên lý này như sau:
Trong một chương trình, các object của class con có thể thay thế class cha
mà không làm thay đổi tính đúng đắn của chương trình.

Giả sử chúng ta có 3 class; Vit ( base class ) và VitPin (class con thừa kế từ
class Vit), VitBau (class con thừa kế từ class Vit)

Vit.java
public abstract class Vit {

public void keu(){

// kêu quạc quạc

VitPin.java

public class VitPin extends Vit {

private Boolean Pin;

@Override

public void keu() {

if (Pin){

super.keu();

else {

try {

throw new Exception("Không có Pin không kêu được!!!");

} catch (Exception e) {

e.printStackTrace();

public Boolean getPin() {


return Pin;

public void setPin(Boolean pin) {

Pin = pin;

VitBau.java

public class VitBau extends Vit {

@Override

public void keu() {

super.keu();

Bây giờ ta thử chạy đoạn code sau:

Vit vitBau = new VitBau();

vitBau.keu(); // chạy bình thường

Vit vitPin = new VitPin();

// Chạy phương thức bên dưới gây ra Exception vì lúc này chúng ta
chưa set Pin

// Pin = false
vitPin.keu();

Như bậy clas VitPin đã vi phạm nguyên lý của chúng ta vì VitPin là class con
không thể thay thế hoàn toàn class cha - Vit.

4. Interface Segregation Principle


Nguyên lý này rất dễ hiều. Nội dung nguyên lý phát biểu như sau:
Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với
nhiều mục đích cụ thể

Bạn tưởng tượng chúng ta có một chiếc sạc điện thoại đa năng. Một đầu là
một ổ cắm điện. Đầu kia là 3, 4 chiếc dây để phù hợp với từng dòng này
khác nhau: Type - C, Micro Usb, Sạc chân to, sạc chân nhỏ.... Thoạt nhìn thì
chúng ta có vẻ rất tiện và gọn. Nhưng trong quá trình sử dụng thực tế thì
cực kì vướng víu. Tôi chỉ cần một đầu dây Type - C để sạc điện thoại của
mình. Trong khi đó lại có một mớ dây sạc loại điện thoại khác không cần
dùng đến. Việc thiết kế interface cũng như vậy. Nên tuân thủ nguyên lý
trên để tránh tình trạng "sạc điện thoại đa năng".

Ví dụ thực tế:

Giả sử ta có interface cho các vận động viên như sau:

IVanDongVien.java

public interface IVanDongVien {

void nhayCao();

void nhayXa();

void boi();
}

Vận động viên bơi lội implement:

VdvBoiLoi.java

public class VdvBoiLoi implements IVanDongVien {

@Override

public void nhayCao() {

// vận động viên bơi lội không thể nhảy cao

// không implement

@Override

public void nhayXa() {

// vận động viên bơi lội không thể nhảy xa

// không implement

@Override

public void boi() {

System.out.println("Đang bơi");

Vận động viên thi nhảy implement:


public class VdvNhay implements IVanDongVien {

@Override

public void nhayCao() {

System.out.println("Nhay cao");

@Override

public void nhayXa() {

System.out.println("Nhay xa");

@Override

public void boi() {

// Vận động viên thi nhảy không bơi

// Không implement

Như bạn đã thấy VdvBoiLoi không implement phương thức nhayCao(),


nhayXa() , VdvNhay thì không implement phương thức boi(). Để tránh tình
trạng trên ta nên chia nhỏ interface IVanDongVien của chúng ta thành:

IVdvBoi.java

public interface IVdvBoi {

void boi();
}

IVdvNhay.java

public interface IVdvNhay {

void nhayCao();

void nhayXa();

Khi implement lại các interface ta được:

VdvBoiLoi.java

public class VdvBoiLoi implements IVdvBoi {

@Override

public void boi() {

System.out.println("Đang bơi");

VdvNhay.java

public class VdvNhay implements IVdvNhay {

@Override

public void nhayCao() {


System.out.println("Nhay cao");

@Override

public void nhayXa() {

System.out.println("Nhay xa");

Vậy là chúng ta tránh được tình trạng có phương thức mà không được
implement sau khi chia nhỏ interface.

5. Dependency inversion principle


Nội dung nguyên lý của chúng ta như sau:
1. Các module cấp cao không nên phụ thuộc vào các modules cấp thấp.
Cả 2 nên phụ thuộc vào abstraction.
2. Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược
lại. ( Các class giao tiếp với nhau thông qua interface, không phải
thông qua implementation.)

Khi trong quá trình làm ứng dụng thực tế, khi áp dụng Dependency
Inverse, ta chỉ cần quan tâm tới interface. Để gửi một đoạn message chắng
hạn ta chỉ cần quan tâm đến hàm sendMessage() của interface
ISendMessage. Sau này khi cần thay đổi ta cũng cần thay đổi implement
của interface trên mà thôi. Ta có thể swap qua lại giữa: SendEmailMessage,
SendSmsMessage cùng implement interface ISendMessage.

Ví dụ:
ISendMessage.java

public interface ISendMessage {

void sendMessage();

SendEmailMessage.java

public class SendEmailMessage implements ISendMessage {

private String message;

public SendEmailMessage(String message){

this.message = message;

@Override

public void sendMessage() {

System.out.println(message + " được gửi qua Email");

SendSmsMessage.java

public class SendSmsMessage implements ISendMessage {

private String message;


public SendSmsMessage(String message){

this.message = message;

@Override

public void sendMessage() {

System.out.println(message + " được gửi qua Sms");

User.java

public class User {

public void sendMessage(ISendMessage iSendMessage){

iSendMessage.sendMessage();

Chúng ta cùng sử dụng:

User user = new User();

user.sendMessage(new SendEmailMessage("hello"));

// in ra: hello được gửi qua Email

user.sendMessage(new SendSmsMessage("hello"));
// in ra: hello được gửi qua Sms

Điểm thuận lợi lớn nhất của nguyên lý này:

Khi thay đổi yêu cầu thì ta cũng dễ dàng chuyển đổi mà không ảnh hưởng
tới code cũ. Như trên là ta có thể chuyển tùy ý giữa các class:
SendEmailMessage và SendSmsMessage. Hoặc thậm chí nếu phát sinh
thêm yêu cầu mới là gửi message qua Facebook. Thì ta cũng chỉ cần tạo
thêm class SendFacebookMessage, sau đó implement ISendMessage
interface.

Vừa rồi là trình bày của mình về S.O.L.I.D nguyên lý trong lập trình
hướng đối tượng. Nội dung cũng khá dài. Các bạn cố gắng áp dụng vào
code. Vì một tương lai Solid Developer. Chúc các bạn thành công.
Lập trình HDT là 1 pp lập trình mà chúng ta xem xét 1 ctr là tập hợp của các đối tượng

Các object ở đây có ID là duy nhất, thứ 2 là có trạng thái , hành vi, đặc điểm

Class giống như 1 template(khung sườn chung) để thể hiện các method,properties và behaviors của đối
tượng đó

Object là 1 instance của 1 class

Trong lập trình HDT có 4 tính chất

+ Encapsulation

- Là 1 trong các tính chất lập trình hdt. Mục đích của nó bảo vệ tính nhất quán, toàn vẹn của dữ liệu ,che
dấu ẩn i nhựng thông tin .hạn chế ko cho phép truy cập thay đổi trực tiếp các properties, data. Mà chỉ
cho phép truy cập,xem,update thông qua các phương thức được public,geter/seter

- Encapsulation thể hiện qua 4 Access modifier

-> Public là everywhere. Truy cập mọi nơi. Trực tiếp các properties

-> Default là truy cập trong nội bộ những lớp có cùng package

+ Package là tập hợp nhiều class java có mối liên hệ vs nhau

-> Protected tương tự default truy cập tại lớp khai báo và lớp con được kế thừa

-> private dc truy cập nội bộ trong class đó thôi

+Inheritance

- là 1 trong các tính chất của lập trình hdt. Cho phép tạo 1 lớp con kế thừa từ lớp cha. Class con có quyền
dc use các method, properties của class cha mà ko phải private. Class cha có những cái j mà ko private
thì thằng con use dc.

- Inheritance thể hiện qua từ khóa extends

+Polymorphism

- overload cùng tên nhưng khác thứ tụ tham số truyền vào. Khi compiler se bt chính xác thằng nào dc gọi

+Abstraction

- nó chỉ khao báo phần chung. ẩn phần chi tiết. Các class con sẽ triển khai
1) Trựu tường là những thứ trựu tượng chưa rõ ràng

- Tính trừu tượng là cách thể hiện mà chỉ cần khai báo phần chung là phần cần thiết

+ ViDu: Lớp staff có getWorkingTime() là phần chung. Nhưng chúng ta ko biết lấy thời gian của ai

+ Nên các lớp con kế thừa từ lớp staff phải implement lại phương thức getWorkingTime của lớp
Staff

- Có 2 hình thức trừu tượng:

+ interface

+ Abstract class
1) Interface chỉ cung cấp bộ khung xương
- chỉ dc khai báo những pthuc cần thiết
- Interface chỉ khao báo các phần tên ko có phần body
- Nếu ko khai báo acess modifer cho các pthuc trong interface thì mặc định là public

- Interface nó sẽ tập hợp các pthuc trừu tượng


+ Phương thức trừ tượng ko có body
+ Lớp con kế thừa từ interface phải implement hết tất cả các pthuc trừ tượng trong
interface(từ khóa implement dùng để kế thừa)
- Interface hỗ trợ đa kế thừa
- Interface thể hiện tính đa hình tốt hơn
2) Abstract class xem như một class bth cũng có properties. Nó có thêm các phương thức trừ
tượng
- Khai báo abstract class buộc phải thêm từ khóa abstract vào phương thức
- Lớp bth ko có từ khóa abstract class. Lớp bth ko thể có abstract method
- Lớp trừu tượng có thể có hoặc ko có abstract method. Một class chứa pthuc trừu tượng bắt
buộc nó là abstract class
- Không thể tạo đối tượng từ abstract class vì lớp chưa đủ chi tiết , abstract class còn những
pthuc chưa được triển khai.

-
- Lớp con kế thừa từ abstract class buộc phải implement các method của abstract class
- Abstract lưu lại những thông tin chung

- nếu 1 lớp abstract khai báo những phương thức có phần thân ko có từ khóa abstract thì lớp con kế
thừa có ko bắt buộc phải implement các method này

- Abstract class ko hỗ trợ đa kế thừa


3) So sánh

- Abstract class và interface ai chạy nhanh hơn?

+ Abstract class chạy nhanh hơn

-> Lý do:

+ vì abstract class khi khai báo. Lớp con kế thừa và bt chính xác implement chỗ nào

+ Còn interface thì rất nhiều thằng implement . có những class nó implement khác nhau. Class a
im class b. class b im class a. Nên khi chạy nó phải kiềm thằng nào muốn implement nên nó tốn thời
gian

4) Khi nào dùng inteface và abstract


- Interface chỉ khai báo ra những method. Những thằng xài nó phải implement lại
- Vậy chúng ta sử dụng interface khi chúng ta muốn đưa ra những nội dung chung cần thiết. Mà
những thằng khác sử dụng và triển khai theo nhiều cách khác
- Interface nó chỉ quan tâm tới khung xương của nó. Còn việc có quan hệ hoặc các class triển
khai nó ko quan tâm
- Interface và abstract class mục đích chung là đưa ra những method chung để lớp con khi kế
thừa có thể implement lại
1) khi sử dụng abstract class thì chúng phải có quan hệ cha con thì thằng con kế thừa và
implement
Tính đa hình trong hình là hành động nhưng có nhiều cách thể hiện hành vị khác nhay tùy thuộc vào
từng đối tượng

- Overload : Cùng tên khác địa chỉ

+ Cùng 1 hành động tính tổng nhưng có nhiều cách tính khác nhau

+ Tính đa hình trong overload thể hiện lúc complier(biên dịch)


- In ra Name, address, companyName

Tính đa hình là 1 trong những tính chất cơ bản của lập trình hướng doituong

+ Tính dh cho phép các đối tượng cùng kiểu (cùng kiểu lớp cha) thực hiện những hành vi khác nhau dựa
vào từng đối tượng

+ Tính dh thể hiện qua overload trong khoản thời complier time. Nó sẽ bt chính xác thằng nào dc gọi.Khi
Đưa vào thứ tự tham số truyền vào

+ Tính dh thể hiện qua overiding. nó thể hiện thông qua lúc run time

You might also like