What is SOLID in Programming? The SOLID principles of Object-Oriented Programming

SOLID - năm nguyên tắc nền tảng giúp lập trình viên tạo ra phần mềm hướng đối tượng dễ bảo trì, linh hoạt và vững chắc.
Please wait 0 seconds...
Scroll Down and click on Go to Link for destination
Congrats! Link is Generated

 


What is SOLID principles

SOLID là từ viết tắt của năm nguyên tắc thiết kế hướng đối tượng (OOD - Object-Oriented Design) được giới thiệu bởi Robert C. Martin
SOLID là viết tắt của:
  • S - Single-Responsibility Principle (SRP)
  • O - Open-closed Principle (OCP)
  • L - Liskov Substitution Principle (LSP)
  • I - Interface Segregation Principle (ISP)
  • D - Dependency Inversion Principle (DIP)

Single-Responsibility Principlec (SRP)

A class should have one reason to change

Nguyên tắc đơn trách nhiệm nêu rõ rằng một class chỉ nên có một lý do để thay đổi. Điều đó có nghĩa là một class nên chỉ có một trách nhiệm duy nhất, được định nghĩa rõ ràng và không nên chịu trách nhiệm cho nhiều thứ.

Tuân theo nguyên tắc đơn trách nhiệm có thể giúp bạn viết code dễ bảo trì và kiểm tra hơn, bởi vì những thay đổi với class được tách biệt thành một trách nhiệm duy nhất. Nó cũng có thể giúp bạn tránh trùng lặp và cải thiện tính module cho phần mềm của bạn.

Để minh họa nguyên tắc đơn trách nhiệm và cách nó có thể giúp bạn cải thiện thiết kế hướng đối tượng của mình, giả sử chúng ta có class FileManager như sau

# file_manager_srp.py

from pathlib import Path
from zipfile import ZipFile

class FileManager:
def __init__(self, filename):
self.path = Path(filename)

def read(self, encoding="utf-8"):
return self.path.read_text(encoding)

def write(self, data, encoding="utf-8"):
self.path.write_text(data, encoding)

def compress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="w") as archive:
archive.write(self.path)

def decompress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="r") as archive:
archive.extractall()

Trong ví dụ trên, class FileManager có hai trách nhiệm khác nhau. Nó sử dụng hàm read() và write() để quản lý file. Nó cũng xử lý các kho lưu trữ Zip bằng hàm compress() decompress().

Class này vi phạm nguyên tắc đơn trách nhiệm bởi vì nó có hai lý do để thay đổi cách triển khai nội bộ. Để sửa vấn đề này và làm cho thiết kế của bạn mạnh mẽ hơn, bạn có thể chia class thành hai class nhỏ hơn, tập trung hơn, mỗi class có một mối quan tâm cụ thể hơn

    
# file_manager_srp.py

from pathlib import Path
from zipfile import ZipFile

class FileManager:
def __init__(self, filename):
self.path = Path(filename)

def read(self, encoding="utf-8"):
return self.path.read_text(encoding)

def write(self, data, encoding="utf-8"):
self.path.write_text(data, encoding)

class ZipFileManager:
def __init__(self, filename):
self.path = Path(filename)

def compress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="w") as archive:
archive.write(self.path)

def decompress(self):
with ZipFile(self.path.with_suffix(".zip"), mode="r") as archive:
archive.extractall()
Bây giờ, bạn có hai class nhỏ hơn, mỗi class có duy nhất một trách nhiệm. FileManager quản lý file, trong khi ZipFileManager xử lý việc nén và giải nén tệp bằng định dạng Zip. Hai class này nhỏ hơn nên dễ dàng quản lý hơn. Chúng cũng dễ dàng hơn cho việc lý luận (easier to reason about), kiểm tra và gỡ lỗi.
Khái niệm về trách nhiệm trong ngữ cảnh này có thể khá chủ quan. Việc có một trách nhiệm duy nhất không nhất thiết có nghĩa là có một phương thức duy nhất. Trách nhiệm không gắn trực tiếp với số lượng của phương thức mà gắn liền với nhiệm vụ cốt lõi mà class của bạn chịu trách nhiệm, tùy thuộc vào ý tưởng của bạn mà class đó đại diện cho điều gì trong code của bạn. Tuy nhiên sự chủ quan đó không nên ngăn cản bạn cố gắng sử dụng SRP.

Open-closed Principle (OCP)

Software entities (classes, modules, functions, etc.) must be open to extension but closed for modification

Nguyên tắc đóng mở nêu rõ rằng một class nên được mở rộng, nhưng không được sửa đổi. Điều đó có nghĩa là bạn nên cho phép thêm các tính năng mới vào trong một class mà không làm thay đổi mã nguồn hiện có của nó.

Tuân thủ theo nguyên tắc đóng mở có thể giúp bạn viết code linh hoạt và dễ dàng cho việc bảo trì hơn, bởi vì bạn có thể thêm tính năng mới mà không phá vỡ các tính năng hiện có. Nó cũng có thể giúp bạn giảm thiểu nguy cơ phát sinh lỗi khi sửa đổi mã nguồn hiện có.

Để hiểu rõ hơn nguyên tắc đóng mở là gì, hãy xem ví dụ về class Shape sau

# shapes_ocp.py

from math import pi

class Shape:
def __init__(self, shape_type, **kwargs):
self.shape_type = shape_type
if self.shape_type == "rectangle":
self.width = kwargs["width"]
self.height = kwargs["height"]
elif self.shape_type == "circle":
self.radius = kwargs["radius"]

def calculate_area(self):
if self.shape_type == "rectangle":
return self.width * self.height
elif self.shape_type == "circle":
return pi * self.radius**2

Trình khởi tạo của Shape nhận đối số shape_type có thể là "rectangle" hoặc "circle". Nó cũng lấy tập hợp đối số từ khóa cụ thể bằng cách sử dụng cú pháp **kwargs. Nếu bạn đặt loại hình dạng thành "rectangle", bạn cũng nên đặt các đối số từ khóa "width" và "height" để có thể tạo một hình chữ nhật phù hợp (construct a proper rectangle).

Ngược lại (In contrast) nếu bạn đặt loại hình dạng là "circle", thì bạn cũng phải truyền đối số "radius" để tạo một hình tròn (to construct a circle). 

Shape cũng có một phương thức .calculate_area() để tính diện tích (the area) của hình hiện tại theo .shape_type của nó.

>>> from shapes_ocp import Shape

>>> rectangle = Shape("rectangle", width=10, height=5)
>>> rectangle.calculate_area()
50
>>> circle = Shape("circle", radius=5)
>>> circle.calculate_area()
78.53981633974483

Class hoạt động. Bạn có thể tạo các hình tròn và hình chữ nhật, tính diện tích của chúng, v.v. (and so on). Tuy nhiên, class trông khá tệ (pretty bad). Có gì đó không ổn ngay từ cái nhìn đầu tiên.

Hãy thử tưởng tượng bạn cần thêm một loại hình mới, ví dụ như hình vuông chẳng hạn. Bạn sẽ làm điều đó như thế nào? Chà, tùy chọn ở đây là thêm một mệnh đề (clause) mới vào trong .__init__() và trong .calculate_area(), từ đó bạn có thể giải quyết các yêu cầu (address the requirements) của hình vuông.

Phải thực hiện những thay đổi này để tạo một hình mới có nghĩa là class của bạn có thể sửa đổi. Điều đó vi phạm nguyên tắc đóng mở. Làm thế nào để bạn có thể sửa class của mình khiến cho nó có thể mở rộng, nhưng không thể sửa đổi? Đây là một biện pháp khả thi

# shapes_ocp.py

from abc import ABC, abstractmethod
from math import pi

class Shape(ABC):
def __init__(self, shape_type):
self.shape_type = shape_type

@abstractmethod
def calculate_area(self):
pass

class Circle(Shape):
def __init__(self, radius):
super().__init__("circle")
self.radius = radius

def calculate_area(self):
return pi * self.radius**2

class Rectangle(Shape):
def __init__(self, width, height):
super().__init__("rectangle")
self.width = width
self.height = height

def calculate_area(self):
return self.width * self.height

class Square(Shape):
def __init__(self, side):
super().__init__("square")
self.side = side

def calculate_area(self):
return self.side**2

Trong đoạn code trên, bạn đã cấu trúc lại hoàn toàn (completely refactored) class Shape, biến nó thành (turning it into) một class cơ sở trừu tượng (ABC). Class này cung cấp giao diện bắt buộc (API) cho bất kì loại hình dạng nào mà bạn muốn xác định. Giao diện đó bao gồm (consist) thuộc tính .shape_type và phương thức .calculate_area() mà bạn phải ghi đè trong tất cả các class con.

Sự cập nhật này sẽ đóng gói class để không thể sửa đổi. Bây giờ bạn có thể các hình dạng mới vào thiết kế lớp của mình mà không cần phải thay đổi Shape. Trong mọi trường hợp, bạn phải triển khai giao diện cần thiết, điều này cũng khiến cho các class của bạn đa hình (polymorphic).

Liskov Substitution Principle (LSP)

Subtypes must be substituable for their base types

Nguyên tắc thay thế Liskov nêu rõ rằng các kiểu con nên có thể thay thế cho các kiểu cơ sở của chúng. Điều này có nghĩa rằng nếu một lớp là một kiểu con của một lớp khác, nó nên có thể được sử dụng cùng một cách như lớp cơ bản mà không ảnh hưởng đến tính đúng đắn của chương trình.

Tuân thủ theo nguyên tắc thay thế Liskov có thể giúp bạn viết code linh hoạt và dễ bảo trì hơn, bởi vì bạn có thể sử dụng các kiểu con giống như kiểu cơ sở mà không cần phải lo lắng về những ảnh hưởng không mong muốn. Nó cũng có thể giúp bạn viết code dễ hiểu hơn, bởi vì các kiểu con có thể được sử dụng cùng một cách giống như lớp cơ bản.

Ví dụ, nếu bạn có một đoạn code hoạt động với class Shape, thì bạn có thể thay thế class đó với bất kì lớp con nào của nó, chẳng hạn Circle hoặc Rectangle, mà không vi phạm mã.

Trong thực tế (In practice), nguyên tắc này là làm cho các lớp con của bạn hoạt động giống như các lớp cơ sở của chúng mà không phá vỡ sự kì vọng của bất cứ ai khi họ gọi cùng một phương thức. Để tiếp tục với các ví dụ liên quan đến hình dạng, giả sử bạn có (say you have) một class Rectangle như sau

  
# shapes_lsp.py

class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height

def calculate_area(self):
return self.width * self.height

Trong Rectangle, bạn cung cấp phương thức .calculate_area(), phương thức này hoạt động với các thuộc tính phiên bản (instance attributes.width.height.

Bởi vì hình vuông là một hình chữ nhật đặc biệt với các cạnh bằng nhau, nên bạn nghĩ đến việc lấy lớp Square từ lớp Rectangle để tái sử dụng code. Sau đó bạn ghi đè phương thức setter cho các thuộc tính .width và .height để khi một bên thay đổi, bên kia cũng thay đổi theo.

  
# shapes_lsp.py

# ...

class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)

def __setattr__(self, key, value):
super().__setattr__(key, value)
if key in ("width", "height"):
self.__dict__["width"] = value
self.__dict__["height"] = value

Trong đoạn mã này, bạn định nghĩa Square như một lớp con của Rectangle. Như một người dùng mong đợi, hàm tạo chỉ lấy một cạnh của hình vuông làm đối số. Bên trong phương thức .__init__() khởi tạo các thuộc tính cha, .width.height, với đối số side.

Bạn cũng đã xác định một phương thức đặc biệt, .__setattr__(), để nối vào cơ chế thiết lập thuộc tính của Python và chặn việc gán giá trị mới cho thuộc tính .width hoặc .height. Cụ thể, khi bạn đặt một trong các thuộc tính đó thì thuộc tính còn lại cũng được đặt cùng giá trị

  
>>> from shapes_lsp import Square

>>> square = Square(5)
>>> vars(square)
{'width': 5, 'height': 5}

>>> square.width = 7
>>> vars(square)
{'width': 7, 'height': 7}

>>> square.height = 9
>>> vars(square)
{'width': 9, 'height': 9}

Bây giờ bạn đã đảm bảo rằng đối tượng Square luôn là một hình vuông hợp lệ, giúp cuộc sống của bạn dễ dàng hơn với chi phí nhỏ là lãng phí một chút bộ nhớ. Thật không may, điều này vi phạm nguyên tắc thay thế Liskov vì bạn không thể thay thế các phiên bản Hình chữ nhật bằng các phiên bản Hình vuông của chúng. (counterparts - đối tác)

Khi ai đó mong đợi một đối tượng hình chữ nhật trong mã của họ, họ có thể cho rằng nó sẽ hoạt động giống như một đối tượng bằng cách hiển thị (exposting - vạch trần, phơi ra) hai thuộc tính .width và .height độc lập (independent). Trong khi đó (meanwhile), lớp Square của bạn phá vỡ giả định đó bằng cách thay đổi hành vi được hứa hẹn bởi giao diện của đối tượng. Điều đó có thể gây ra những hậu quả (consequences - hậu quả) đáng ngạc nhiên và không mong muốn (unwanted), khó có thể gỡ lỗi.

Mặc dù hình vuông là một loại hình chữ nhật cụ thể trong toán học (mathematics), nhưng các lớp đại diện cho những hình dạng đó không nên có mối quan hệ cha-con nếu bạn muốn chúng tuân thủ (comply - tuân thủ) nguyên tắc thay thế Liskov. Một cách để giải quyết vấn đề này là tạo một lớp cơ sở cho cả RectangleSquare để mở rộng (extend)

  
# shapes_lsp.py

from abc import ABC, abstractmethod

class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass

class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height

def calculate_area(self):
return self.width * self.height

class Square(Shape):
def __init__(self, side):
self.side = side

def calculate_area(self):
return self.side ** 2

Shape trở thành loại mà bạn có thể thay thế thông qua tính đa hình bằng Rectangle hoặc Square, hiện là anh em ruột (siblings - anh chị em ruột) chứ không phải là cha mẹ và con cái. Lưu ý rằng cả hai loại hình dạng cụ thể đều có các bộ thuộc tính riêng biệt, các phương thức khởi tạo khác nhau và có khả năng (potentially - có tiềm năng) thực hiện nhiều hành vi riêng biệt hơn. Điểm chung duy nhất của họ là khả năng tính diện tích.

Với cách triển khai này, bạn có thể sử dụng loại Shape thay thế cho nhau (interchangeably - có thể thay thế cho nhau) với các loại phụ (subtypes) SquareRectangle khi bạn chỉ quan tâm đến hành vi chung của chúng

  
>>> from shapes_lsp import Rectangle, Square

>>> def get_total_area(shapes):
... return sum(shape.calculate_area() for shape in shapes)

>>> get_total_area([Rectangle(10, 5), Square(5)])
75

Ở đây, bạn chuyển một cặp gồm một hình chữ nhật và một hình vuông vào một hàm tính tổng diện tích của chúng. Bởi vì hàm chỉ quan tâm đến phương thức .calculate_area() nên việc các hình dạng khác nhau không thành vấn đề. Đây là bản chất (essence - nước hoa, bản chất) của nguyên lý thay thế Liskov.

Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use

Nguyên tắc phân chia giao diện nêu rõ rằng khách hàng không nên bị buộc phải phụ thuộc vào các giao diện mà họ không sử dụng. Điều này có nghĩa bạn nên tạo ra các giao diện nhỏ, cụ thể thay vì các giao diện lớn, chung chung.

Tuân theo nguyên tắc phân chia giao diện có thể giúp bạn viết code linh hoạt và dễ hiểu hơn, bởi vì bạn có thể tạo ra các giao diện chuyên biệt dễ dàng triển khai và sử dụng hơn. Nó có thể giúp bạn tránh những sự phụ thuộc không cần thiết và cải thiện tính module của phần mềm. 

Trong trường hợp này, máy khách là các lớp và lớp con, còn giao diện bao gồm các phương thức và thuộc tính. Nói cách khác (In other words), nếu một lớp không sử dụng các phương thức hoặc thuộc tính cụ thể thì các phương thức và thuộc tính đó sẽ được tách riêng thành các lớp cụ thể hơn.

Hãy xem xét ví dụ sau về phân cấp (hierarchy - hệ thống cấp bậc) lớp cho các máy in mô hình

# printers_isp.py

from abc import ABC, abstractmethod

class Printer(ABC):
@abstractmethod
def print(self, document):
pass

@abstractmethod
def fax(self, document):
pass

@abstractmethod
def scan(self, document):
pass

class OldPrinter(Printer):
def print(self, document):
print(f"Printing {document} in black and white...")

def fax(self, document):
raise NotImplementedError("Fax functionality not supported")

def scan(self, document):
raise NotImplementedError("Scan functionality not supported")

class ModernPrinter(Printer):
def print(self, document):
print(f"Printing {document} in color...")

def fax(self, document):
print(f"Faxing {document}...")

def scan(self, document):
print(f"Scanning {document}...")

Trong ví dụ này, lớp cơ sở, Printer, cung cấp giao diện mà các lớp con của nó phải triển khai. OldPrinter kế thừa từ Printer và phải triển khai cùng một giao diện. Tuy nhiên, OldPrinter không sử dụng các phương thức .fax().scan() vì loại máy in này không hỗ trợ các chức năng này.

Việc triển khai này vi phạm ISP vì nó buộc OldPrinter phải tiết lộ (expose) một giao diện mà lớp không triển khai hoặc cần thiết. Để khắc phục vấn đề này, bạn nên phân chia các giao diện thành các lớp nhỏ hơn và cụ thể hơn. Sau đó, bạn có thể tạo ra các lớp cụ thể (concrete classes) bằng cách kế thừa từ nhiều lớp giao diện theo cách cần thiết.

# printers_isp.py

from abc import ABC, abstractmethod

class Printer(ABC):
@abstractmethod
def print(self, document):
pass

class Fax(ABC):
@abstractmethod
def fax(self, document):
pass

class Scanner(ABC):
@abstractmethod
def scan(self, document):
pass

class OldPrinter(Printer):
def print(self, document):
print(f"Printing {document} in black and white...")

class NewPrinter(Printer, Fax, Scanner):
def print(self, document):
print(f"Printing {document} in color...")

def fax(self, document):
print(f"Faxing {document}...")

def scan(self, document):
print(f"Scanning {document}...")

Bây giờ Printer, FaxScanner là các lớp cơ sở cung cấp các giao diện cụ thể với mỗi lớp đảm nhiệm một trách nhiệm duy nhất. Để tạo OldPrinter, bạn chỉ kế thừa từ giao diện Printer. Như vậy, lớp sẽ không có các phương thức không sử dụng. Để tạo ra lớp ModernPrinter, bạn cần kế thừa từ tất cả các giao diện. Tóm lại (In short), bạn đã phân tách giao diện Printer.

Thiết kế lớp này cho phép bạn tạo ra các máy khác nhau với các bộ chức năng khác nhau, làm cho thiết kế của bạn linh hoạt và có thể mở rộng.

Dependency Inversion Principle (DIP)

Entities must depend on abstractions, not on concretions. The high-level module must not depend on the low-level module, but they should depend on abstractions.

 "If the Open-Closed Principle states the goal of Object-Oriented architecture, the Dependency Inversion Principle states the primary mechanism."

Nếu nguyên tắc đóng mở nêu rõ mục tiêu của kiến trúc hướng đối tượng, thì nguyên tắc đảo ngược phụ thuộc nêu rõ cơ chế chính. 

Nguyên tắc đảo ngược phụ thuộc nêu rõ rằng các module cao cấp không nên phụ thuộc vào các module cấp thấp, mà cả hai phải phụ thuộc vào sự trừ tượng hóa. Nó có nghĩa là bạn nên phụ thuộc vào các khái niệm trừu tượng (như là các giao diện hoặc các lớp trừu tượng) hơn là phụ thuộc vào các triển khai cụ thể khi viết code.

Việc tuân thủ nguyên tắc đảo ngược phụ thuộc có thể giúp bạn viết code linh hoạt hơn và dễ bảo trì hơn, bởi vì bạn có thể thay đổi cách triển khai cụ thể của một phần phụ thuộc mà không làm ảnh hưởng đển đoạn mã mà nó phụ thuộc vào. Nó cũng có thể giúp bạn cải thiện tính module cho phần mềm của bạn, bởi vì các module cấp cao không được liên kế chặt chẽ vào các module cấp thấp.

Giả sử bạn đang xây dựng một ứng dụng và có một lớp FrontEnd để hiển thị dữ liệu cho người dùng một cách thân thiện. Ứng dụng hiện đang lấy dữ liệu từ cơ sở dữ liệu, vì vậy bạn có mã sau

# app_dip.py

class FrontEnd:
def __init__(self, back_end):
self.back_end = back_end

def display_data(self):
data = self.back_end.get_data_from_database()
print("Display data:", data)

class BackEnd:
def get_data_from_database(self):
return "Data from the database"

Trong ví dụ này, lớp FrontEnd phụ thuộc vào lớp BackEnd và triển khai cụ thể của nó. Bạn có thể nói rằng cả hai lớp đều liên kết chặt chẽ (tightly coupled). Sự kết nối này có thể dẫn đến các vấn đề về khả năng mở rộng. Ví dụ, giả sử ứng dụng của bạn đang phát triển nhanh chóng, và bạn muốn ứng dụng có thể đọc dữ liệu từ một REST API. Làm thế nào bạn sẽ làm điều đó? 

Bạn có thể nghĩ đến việc thêm một phương thức mới vào BackEnd để lấy dữ liệu từ REST API. Tuy nhiên, điều đó cũng sẽ đòi hỏi bạn phải sửa đổi FrontEnd, điều đó không nên xảy ra theo nguyên lý mở đóng.

Để khắc phục vấn đề, bạn có thể áp dụng nguyên lý đảo ngược phụ thuộc và làm cho các lớp của bạn phụ thuộc vào các trừu tượng thay vì vào các triển khai cụ thể như BackEnd. Trong ví dụ cụ thể này, bạn có thể giới thiệu một lớp DataSource cung cấp giao diện để sử dụng trong các lớp cụ thể của bạn

# app_dip.py

from abc import ABC, abstractmethod

class FrontEnd:
def __init__(self, data_source):
self.data_source = data_source

def display_data(self):
data = self.data_source.get_data()
print("Display data:", data)

class DataSource(ABC):
@abstractmethod
def get_data(self):
pass

class Database(DataSource):
def get_data(self):
return "Data from the database"

class API(DataSource):
def get_data(self):
return "Data from the API"

Trong việc thiết kế lại các lớp của bạn, bạn đã thêm một lớp DataSource như một sự trừu tượng cung cấp giao diện cần thiết, hoặc phương thức .get_data(). Lưu ý cách FrontEnd bây giờ phụ thuộc vào giao diện được cung cấp bởi DataSource, đó là một lớp trừu tượng. 

Sau đó, bạn định nghĩa lớp Database, là một sự triển khai cụ thể cho những trường hợp mà bạn muốn lấy dữ liệu từ cơ sở dữ liệu của bạn. Lớp này phụ thuộc vào lớp trừu tượng DataSource thông qua kế thừa. Cuối cùng, bạn định nghĩa lớp API để hỗ trợ lấy dữ liệu từ REST API. Lớp này cũng phụ thuộc vào lớp trừu tượng DataSource.

Dưới đây là cách bạn có thể sử dụng lớp FrontEnd trong mã của bạn

>>> from app_dip import API, Database, FrontEnd

>>> db_front_end = FrontEnd(Database())
>>> db_front_end.display_data()
Display data: Data from the database

>>> api_front_end = FrontEnd(API())
>>> api_front_end.display_data()
Display data: Data from the API

Ở đây, bạn đầu tiên khởi tạo FrontEnd bằng cách sử dụng một đối tượng Database và sau đó lại sử dụng một đối tượng API. Mỗi khi bạn gọi .display_data(), kết quả sẽ phụ thuộc vào nguồn dữ liệu cụ thể mà bạn sử dụng. Lưu ý rằng bạn cũng có thể thay đổi nguồn dữ liệu một cách linh hoạt (dynamically - năng động) bằng cách gán lại thuộc tính .data_source trong phiên bản FrontEnd của bạn.

Conclusion 

Có một số ưu điểm khi tuân theo nguyên tắc SOLID khi lập trình.

Improved flexibility - Cải thiện tính linh hoạt 

 - Tuân theo các nguyên tắc SOLID có thể giúp bạn viết code linh hoạt hơn và dễ dàng thay đổi, bởi vì bạn có thể thêm tính năng mới mà không phá vỡ các chức năng hiện có, và bạn có thể sử dụng trừu tượng hóa thay vì triển khai vụ thể để giảm sự liên kết giữa các phần khác nhau trong code của mình.

Increased reuseable - Tăng khả năng tái sử dụng

 - Tuân theo các nguyên tắc SOLID có thể giúp bạn viết code dễ tái sử dụng hơn, bởi vì bạn có thể thiết kế các lớp và các giao diện tập trung vào một trách nhiệm duy nhất và có thể dễ dàng sử dụng trong các ngữ cảnh khác nhau.

Better code organization - Tổ chức code tốt hơn

 - Tuân theo các nguyên tắc SOLID có thể giúp bạn viết code dễ hiểu và dễ bảo trì hơn, bởi vì bạn có thể sắp xếp code của mình thành các thành phần module nhỏ, dễ hiểu và dễ kiểm tra.

Reduce risk of introducing bugs - Giảm thiểu rủi ro của việc xuất hiện lỗi

 - Tuân theo các nguyên tắc SOLID có thể giúp bạn giảm thiểu rủi ro của việc xuất hiện các lỗi khi bạn thay đổi các đoạn mã của mình, bởi vì bạn có thể thêm các tính năng mới mà không chỉnh sửa các đoạn mã có sẵn, và bạn có thể sử dụng sự trừu tượng thay vì triển khai cụ thể để giảm sự liên kết giữa các phần khác nhau trong code của mình.

Đăng nhận xét

Tham gia cuộc trò chuyện