SOLID là gì?
SOLID trong lập trình hướng đối tượng là tập hợp các nguyên lý thiết kế phần mềm nhằm giúp lập trình viên tạo ra các hệ thống dễ bảo trì, dễ mở rộng và có tính ổn định. Các nguyên lý này bao gồm: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation và Dependency Inversion.
2. Các nguyên lý trong SOLID
2.1 Single Responsibility Principle (SRP)
Single Responsibility Principle (SRP) là nguyên lý đầu tiên của SOLID trong lập trình hướng đối tượng, ý nghĩa của nó có thể được hiểu như sau:
Khi một class có nhiều trách nhiệm, nó sẽ trở nên khó quản lý hơn, khó kiểm tra lỗi, mở rộng và bảo trì. Bạn có thể nhìn vào class OrderManagement trước khi áp dung SRP, class này có quá nhiều nhiệm vụ, từ xử lý đơn hàng, tính toán, đến in hoá đơn. Nếu cần thay đổi logic tính toán hoặc định dạng hóa đơn, ta phải thay đổi toàn bộ lớp này, điều này sẽ làm cho mã nguồn trở nên phức tạp và khó quản lý.
Trước khi áp dụng SRP: Class có nhiều trách nhiệm
class OrderManagement:
def take_order(self, order):
# Logic để xử lý đơn hàng
print("Order taken.")
def calculate_bill(self, order):
# Logic để tính toán hóa đơn
total = sum(item['price'] * item['quantity'] for item in order)
print(f"Bill calculated: ${total}")
return total
def generate_invoice(self, order, total):
# Logic để tạo file PDF hóa đơn
print("Invoice generated in PDF format.")
# Sử dụng lớp OrderManagement
order = [
{"name": "Item 1", "price": 10, "quantity": 2},
{"name": "Item 2", "price": 20, "quantity": 1}
]
order_management = OrderManagement()
order_management.take_order(order)
total = order_management.calculate_bill(order)
order_management.generate_invoice(order, total)
Sau khi áp dụng SRP: Mỗi class mới chỉ đảm nhận một nhiệm vụ duy nhất: OrderProcessor xử lý đơn hàng, BillingCalculator tính toán hóa đơn, và InvoiceGenerator tạo file PDF hóa đơn. Điều này giúp mã nguồn trở nên đơn giản hơn, dễ bảo trì và mở rộng hơn.
class OrderProcessor:
def take_order(self, order):
# Logic để xử lý đơn hàng
print("Order taken.")
class BillingCalculator:
def calculate_bill(self, order):
# Logic để tính toán hóa đơn
total = sum(item['price'] * item['quantity'] for item in order)
print(f"Bill calculated: ${total}")
return total
class InvoiceGenerator:
def generate_invoice(self, order, total):
# Logic để tạo file PDF hóa đơn
print("Invoice generated in PDF format.")
# Sử dụng các lớp đã tách riêng
order = [
{"name": "Item 1", "price": 10, "quantity": 2},
{"name": "Item 2", "price": 20, "quantity": 1}
]
order_processor = OrderProcessor()
billing_calculator = BillingCalculator()
invoice_generator = InvoiceGenerator()
order_processor.take_order(order)
total = billing_calculator.calculate_bill(order)
invoice_generator.generate_invoice(order, total)
Một ví dụ dễ hiểu trong đời sống về nguyên tắt SRP
Hãy tưởng tượng bạn đang xây một ngôi nhà. Nếu bạn yêu cầu một người thợ xây vừa làm thợ nề, thợ điện, thợ ống nước và thợ sơn, thì khi bạn muốn sửa lại đường ống nước, bạn sẽ phải gọi người này và có nguy cơ ảnh hưởng đến các công việc khác mà người đó đã làm.
Thay vào đó, nếu bạn có một người chuyên làm thợ nề, một người chuyên làm thợ điện, một người chuyên làm thợ ống nước, và một người chuyên sơn, thì khi bạn muốn sửa đường ống nước, bạn chỉ cần gọi thợ ống nước mà không làm ảnh hưởng đến các phần việc khác của ngôi nhà. Điều này giúp bạn dễ dàng quản lý và sửa chữa.
2.2 Open/Closed Principle (OCP)
Open/Closed Principle (OCP) đề cập đến cách thiết kế phần mềm sao cho nó có thể mở rộng bằng cách thêm các tính năng mới mà không cần phải sửa đổi mã nguồn hiện có. Điều này giúp cho mã nguồn ổn định hơn, tránh việc sửa đổi dẫn đến lỗi không mong muốn.
Giả sử bạn đang xây dựng một hệ thống quản lý đơn hàng. Ban đầu, hệ thống chỉ hỗ trợ hai phương thức thanh toán là Thanh toán bằng thẻ tín dụng và Thanh toán bằng PayPal. Sau một thời gian, bạn muốn thêm một phương thức thanh toán mới, chẳng hạn Thanh toán bằng ví điện tử (eWallet).
Trước khi áp dụng OCP: Mọi thay đổi sẽ yêu cầu bạn chỉnh sửa mã nguồn hiện có, điều này có thể làm cho hệ thống dễ bị lỗi khi mở rộng.
class Order:
def __init__(self, amount):
self.amount = amount
def process_payment(self, payment_method):
if payment_method == "credit_card":
self.pay_by_credit_card()
elif payment_method == "paypal":
self.pay_by_paypal()
def pay_by_credit_card(self):
print(f"Processing credit card payment of ${self.amount}")
def pay_by_paypal(self):
print(f"Processing PayPal payment of ${self.amount}")
# Sử dụng hệ thống thanh toán
order = Order(100)
order.process_payment("credit_card")
order.process_payment("paypal")
Sau khi áp dụng OCP: Bạn có thể thiết kế hệ thống sao cho việc thêm phương thức thanh toán mới không yêu cầu phải thay đổi mã nguồn hiện có. Bạn có thể thực hiện điều này bằng cách sử dụng các lớp và kế thừa (inheritance).
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount):
pass
class CreditCardPayment(PaymentProcessor):
def process_payment(self, amount):
print(f"Processing credit card payment of ${amount}")
class PayPalPayment(PaymentProcessor):
def process_payment(self, amount):
print(f"Processing PayPal payment of ${amount}")
class EWalletPayment(PaymentProcessor):
def process_payment(self, amount):
print(f"Processing eWallet payment of ${amount}")
class Order:
def __init__(self, amount):
self.amount = amount
def process_payment(self, payment_processor: PaymentProcessor):
payment_processor.process_payment(self.amount)
# Sử dụng hệ thống thanh toán với khả năng mở rộng
order = Order(100)
order.process_payment(CreditCardPayment())
order.process_payment(PayPalPayment())
# Thêm phương thức thanh toán mới mà không thay đổi mã cũ
order.process_payment(EWalletPayment())
Một ví dụ dễ hiểu khác về nguyên lý OPC
Hãy tưởng tượng bạn đang thiết kế một chiếc xe hơi. Ban đầu, xe của bạn chỉ có một loại động cơ xăng. Sau đó, bạn muốn thêm tùy chọn động cơ điện. Nếu bạn phải tháo rời và thay đổi toàn bộ xe để thêm động cơ điện, thì điều này sẽ rất phức tạp và tốn kém.
Thay vào đó, nếu từ đầu bạn thiết kế xe sao cho động cơ có thể dễ dàng thay thế và mở rộng, việc thay động cơ mới không làm thay đổi thiết kế cơ bản của chiếc xe, tức là bạn đang tuân thủ nguyên lý OPC.
2.3 Liskov Substitution Principle (LSP)
Nguyên lý Liskov Substitution Principle (LSP) được đặt tên theo nhà khoa học máy tính Barbara Liskov và nó nói rằng: Các đối tượng của một lớp con phải có thể thay thế cho các đối tượng của lớp cha mà không làm thay đổi tính đúng đắn của chương trình.
Giả sử bạn đang phát triển một hệ thống quản lý đơn hàng, trong đó có một lớp tên Order để xử lý các đơn hàng. Bạn quyết định tạo ra một lớp con OnlineOrder để xử lý các đơn hàng trực tuyến và lớp InStoreOrder để xử lý các đơn hàng tại cửa hàng.
Vi phạm nguyên lý LSP: Bạn có lớp Order với một phương thức process_payment(), và bạn muốn mỗi loại đơn hàng có cách xử lý thanh toán khác nhau. Khi bạn gọi handle_order() với một đối tượng InStoreOrder và phương thức thanh toán không được hỗ trợ (như PayPal), hệ thống sẽ trả ra một ngoại lệ (exception) => Lớp con (OnlineOrder và InStoreOrder) không hoàn toàn thay thế được cho lớp cha Order một cách đúng đắn, gây ra các lỗi tiềm ẩn.
class Order:
def process_payment(self, payment_method):
# Xử lý thanh toán mặc định
print(f"Processing payment using {payment_method}")
class OnlineOrder(Order):
def process_payment(self, payment_method):
# Xử lý thanh toán trực tuyến
if payment_method == "credit_card":
print("Processing online payment with credit card")
else:
raise NotImplementedError("Only credit card payments are supported online")
class InStoreOrder(Order):
def process_payment(self, payment_method):
# Xử lý thanh toán tại cửa hàng
if payment_method in ["cash", "credit_card"]:
print(f"Processing in-store payment with {payment_method}")
else:
raise NotImplementedError("Only cash or credit card payments are supported in-store")
# Sử dụng các đối tượng
def handle_order(order: Order, payment_method):
order.process_payment(payment_method)
online_order = OnlineOrder()
in_store_order = InStoreOrder()
handle_order(online_order, "credit_card") # Hoạt động bình thường
handle_order(in_store_order, "cash") # Hoạt động bình thường
handle_order(in_store_order, "paypal") # Sẽ lỗi vì không hỗ trợ PayPal
SOLID trong lập trình hướng đối tượng là tập hợp các nguyên lý thiết kế phần mềm nhằm giúp lập trình viên tạo ra các hệ thống dễ bảo trì, dễ mở rộng và có tính ổn định. Các nguyên lý này bao gồm: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation và Dependency Inversion.
2. Các nguyên lý trong SOLID
2.1 Single Responsibility Principle (SRP)
Single Responsibility Principle (SRP) là nguyên lý đầu tiên của SOLID trong lập trình hướng đối tượng, ý nghĩa của nó có thể được hiểu như sau:
- Mỗi lớp (class) trong chương trình chỉ nên có một trách nhiệm duy nhất.
- Hay nói cách khác, một lớp chỉ nên có một lý do duy nhất để thay đổi.

Khi một class có nhiều trách nhiệm, nó sẽ trở nên khó quản lý hơn, khó kiểm tra lỗi, mở rộng và bảo trì. Bạn có thể nhìn vào class OrderManagement trước khi áp dung SRP, class này có quá nhiều nhiệm vụ, từ xử lý đơn hàng, tính toán, đến in hoá đơn. Nếu cần thay đổi logic tính toán hoặc định dạng hóa đơn, ta phải thay đổi toàn bộ lớp này, điều này sẽ làm cho mã nguồn trở nên phức tạp và khó quản lý.
Trước khi áp dụng SRP: Class có nhiều trách nhiệm
class OrderManagement:
def take_order(self, order):
# Logic để xử lý đơn hàng
print("Order taken.")
def calculate_bill(self, order):
# Logic để tính toán hóa đơn
total = sum(item['price'] * item['quantity'] for item in order)
print(f"Bill calculated: ${total}")
return total
def generate_invoice(self, order, total):
# Logic để tạo file PDF hóa đơn
print("Invoice generated in PDF format.")
# Sử dụng lớp OrderManagement
order = [
{"name": "Item 1", "price": 10, "quantity": 2},
{"name": "Item 2", "price": 20, "quantity": 1}
]
order_management = OrderManagement()
order_management.take_order(order)
total = order_management.calculate_bill(order)
order_management.generate_invoice(order, total)
Sau khi áp dụng SRP: Mỗi class mới chỉ đảm nhận một nhiệm vụ duy nhất: OrderProcessor xử lý đơn hàng, BillingCalculator tính toán hóa đơn, và InvoiceGenerator tạo file PDF hóa đơn. Điều này giúp mã nguồn trở nên đơn giản hơn, dễ bảo trì và mở rộng hơn.
class OrderProcessor:
def take_order(self, order):
# Logic để xử lý đơn hàng
print("Order taken.")
class BillingCalculator:
def calculate_bill(self, order):
# Logic để tính toán hóa đơn
total = sum(item['price'] * item['quantity'] for item in order)
print(f"Bill calculated: ${total}")
return total
class InvoiceGenerator:
def generate_invoice(self, order, total):
# Logic để tạo file PDF hóa đơn
print("Invoice generated in PDF format.")
# Sử dụng các lớp đã tách riêng
order = [
{"name": "Item 1", "price": 10, "quantity": 2},
{"name": "Item 2", "price": 20, "quantity": 1}
]
order_processor = OrderProcessor()
billing_calculator = BillingCalculator()
invoice_generator = InvoiceGenerator()
order_processor.take_order(order)
total = billing_calculator.calculate_bill(order)
invoice_generator.generate_invoice(order, total)
Một ví dụ dễ hiểu trong đời sống về nguyên tắt SRP

Thay vào đó, nếu bạn có một người chuyên làm thợ nề, một người chuyên làm thợ điện, một người chuyên làm thợ ống nước, và một người chuyên sơn, thì khi bạn muốn sửa đường ống nước, bạn chỉ cần gọi thợ ống nước mà không làm ảnh hưởng đến các phần việc khác của ngôi nhà. Điều này giúp bạn dễ dàng quản lý và sửa chữa.
2.2 Open/Closed Principle (OCP)
Open/Closed Principle (OCP) đề cập đến cách thiết kế phần mềm sao cho nó có thể mở rộng bằng cách thêm các tính năng mới mà không cần phải sửa đổi mã nguồn hiện có. Điều này giúp cho mã nguồn ổn định hơn, tránh việc sửa đổi dẫn đến lỗi không mong muốn.

Giả sử bạn đang xây dựng một hệ thống quản lý đơn hàng. Ban đầu, hệ thống chỉ hỗ trợ hai phương thức thanh toán là Thanh toán bằng thẻ tín dụng và Thanh toán bằng PayPal. Sau một thời gian, bạn muốn thêm một phương thức thanh toán mới, chẳng hạn Thanh toán bằng ví điện tử (eWallet).
Trước khi áp dụng OCP: Mọi thay đổi sẽ yêu cầu bạn chỉnh sửa mã nguồn hiện có, điều này có thể làm cho hệ thống dễ bị lỗi khi mở rộng.
class Order:
def __init__(self, amount):
self.amount = amount
def process_payment(self, payment_method):
if payment_method == "credit_card":
self.pay_by_credit_card()
elif payment_method == "paypal":
self.pay_by_paypal()
def pay_by_credit_card(self):
print(f"Processing credit card payment of ${self.amount}")
def pay_by_paypal(self):
print(f"Processing PayPal payment of ${self.amount}")
# Sử dụng hệ thống thanh toán
order = Order(100)
order.process_payment("credit_card")
order.process_payment("paypal")
Sau khi áp dụng OCP: Bạn có thể thiết kế hệ thống sao cho việc thêm phương thức thanh toán mới không yêu cầu phải thay đổi mã nguồn hiện có. Bạn có thể thực hiện điều này bằng cách sử dụng các lớp và kế thừa (inheritance).
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount):
pass
class CreditCardPayment(PaymentProcessor):
def process_payment(self, amount):
print(f"Processing credit card payment of ${amount}")
class PayPalPayment(PaymentProcessor):
def process_payment(self, amount):
print(f"Processing PayPal payment of ${amount}")
class EWalletPayment(PaymentProcessor):
def process_payment(self, amount):
print(f"Processing eWallet payment of ${amount}")
class Order:
def __init__(self, amount):
self.amount = amount
def process_payment(self, payment_processor: PaymentProcessor):
payment_processor.process_payment(self.amount)
# Sử dụng hệ thống thanh toán với khả năng mở rộng
order = Order(100)
order.process_payment(CreditCardPayment())
order.process_payment(PayPalPayment())
# Thêm phương thức thanh toán mới mà không thay đổi mã cũ
order.process_payment(EWalletPayment())
Một ví dụ dễ hiểu khác về nguyên lý OPC

Thay vào đó, nếu từ đầu bạn thiết kế xe sao cho động cơ có thể dễ dàng thay thế và mở rộng, việc thay động cơ mới không làm thay đổi thiết kế cơ bản của chiếc xe, tức là bạn đang tuân thủ nguyên lý OPC.
2.3 Liskov Substitution Principle (LSP)
Nguyên lý Liskov Substitution Principle (LSP) được đặt tên theo nhà khoa học máy tính Barbara Liskov và nó nói rằng: Các đối tượng của một lớp con phải có thể thay thế cho các đối tượng của lớp cha mà không làm thay đổi tính đúng đắn của chương trình.

Giả sử bạn đang phát triển một hệ thống quản lý đơn hàng, trong đó có một lớp tên Order để xử lý các đơn hàng. Bạn quyết định tạo ra một lớp con OnlineOrder để xử lý các đơn hàng trực tuyến và lớp InStoreOrder để xử lý các đơn hàng tại cửa hàng.
Vi phạm nguyên lý LSP: Bạn có lớp Order với một phương thức process_payment(), và bạn muốn mỗi loại đơn hàng có cách xử lý thanh toán khác nhau. Khi bạn gọi handle_order() với một đối tượng InStoreOrder và phương thức thanh toán không được hỗ trợ (như PayPal), hệ thống sẽ trả ra một ngoại lệ (exception) => Lớp con (OnlineOrder và InStoreOrder) không hoàn toàn thay thế được cho lớp cha Order một cách đúng đắn, gây ra các lỗi tiềm ẩn.
class Order:
def process_payment(self, payment_method):
# Xử lý thanh toán mặc định
print(f"Processing payment using {payment_method}")
class OnlineOrder(Order):
def process_payment(self, payment_method):
# Xử lý thanh toán trực tuyến
if payment_method == "credit_card":
print("Processing online payment with credit card")
else:
raise NotImplementedError("Only credit card payments are supported online")
class InStoreOrder(Order):
def process_payment(self, payment_method):
# Xử lý thanh toán tại cửa hàng
if payment_method in ["cash", "credit_card"]:
print(f"Processing in-store payment with {payment_method}")
else:
raise NotImplementedError("Only cash or credit card payments are supported in-store")
# Sử dụng các đối tượng
def handle_order(order: Order, payment_method):
order.process_payment(payment_method)
online_order = OnlineOrder()
in_store_order = InStoreOrder()
handle_order(online_order, "credit_card") # Hoạt động bình thường
handle_order(in_store_order, "cash") # Hoạt động bình thường
handle_order(in_store_order, "paypal") # Sẽ lỗi vì không hỗ trợ PayPal
Bài viết liên quan
Bài viết mới