SOLID

SOLID là một nguyên tắc lập trình hướng đối tượng được đặt tên theo chữ cái đầu của các từ trong các nguyên tắc này: Single Responsibility Principle (SRP), Open/Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), và Dependency Inversion Principle (DIP).

Các nguyên tắc SOLID đề cao việc thiết kế các lớp và đối tượng trong hệ thống phần mềm, nhằm tăng tính linh hoạt, dễ bảo trì, mở rộng và tái sử dụng của hệ thống.

Dưới đây là một số định nghĩa cho từng nguyên tắc SOLID:

  1. Single Responsibility Principle (SRP): Một lớp nên chỉ có một trách nhiệm duy nhất và không nên bị phân tán với nhiều trách nhiệm khác.
  2. Open/Closed Principle (OCP): Một lớp nên được thiết kế để dễ dàng mở rộng mà không cần sửa đổi mã nguồn hiện tại.
  3. Liskov Substitution Principle (LSP): Các lớp con phải có thể thay thế được cho các lớp cha của chúng mà không làm thay đổi tính năng của hệ thống.
  4. Interface Segregation Principle (ISP): Các giao diện nên được thiết kế theo những chức năng cụ thể và không nên yêu cầu các phương thức không cần thiết.
  5. Dependency Inversion Principle (DIP): Mã nguồn nên phụ thuộc vào các giao diện và không phải các lớp cụ thể, để tạo tính linh hoạt và dễ bảo trì.

1 chút mở rộng với Interface Segregation Principle

interface PaymentGatewayInterface {
    public function processPayment(float $amount, string $paymentMethod): bool;
    public function refundPayment(float $amount, string $paymentMethod): bool;
    public function savePaymentInfo(array $paymentInfo): bool;
}

Trong ví dụ này, PaymentGatewayInterface định nghĩa các phương thức không cần thiết như refundPayment() và savePaymentInfo(). Nếu PaymentGateway không sử dụng chúng, nó vẫn phải triển khai các phương thức này và sẽ phụ thuộc vào chúng. Việc này có thể dẫn đến các vấn đề về độ phức tạp và hiệu suất của ứng dụng, đồng thời cũng làm cho việc thay đổi PaymentGateway khó khăn hơn nếu chúng ta muốn loại bỏ các phương thức không cần thi

Còn đối với Dependency Inversion Principle (DIP) là một trong những nguyên tắc trong SOLID và nhấn mạnh rằng “Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào abstraction”. Nguyên tắc này khuyến khích chúng ta thiết kế các module để chúng phụ thuộc vào abstraction chung hơn là phụ thuộc vào các chi tiết cụ thể.

DIP có thể được áp dụng bằng cách sử dụng các design pattern như Dependency Injection (DI), Inversion of Control (IoC) và Service Locator.

Ví dụ, giả sử bạn đang phát triển một ứng dụng quản lý đơn hàng. Trong ứng dụng của bạn, bạn có một lớp OrderService để xử lý các thao tác liên quan đến đơn hàng, ví dụ như tạo đơn hàng, cập nhật đơn hàng, hoàn tất đơn hàng, v.v.

Trong trường hợp này, nếu OrderService phụ thuộc vào các lớp cấp thấp như OrderRepository, ProductRepository và CustomerRepository, nó sẽ phải biết chi tiết cụ thể của các lớp này, gây ra sự phụ thuộc chặt chẽ và làm cho việc bảo trì, mở rộng và thay đổi khó khăn hơn.

Thay vào đó, nếu chúng ta áp dụng DIP, chúng ta sẽ tạo ra các abstraction (interface hoặc abstract class) để mô tả các chức năng của các lớp cấp thấp như OrderRepository, ProductRepository và CustomerRepository. Sau đó, chúng ta sẽ thiết kế OrderService để phụ thuộc vào các abstraction này thay vì các lớp cụ thể. Ví dụ:

interface OrderRepositoryInterface {
    public function createOrder(array $orderData): int;
    public function updateOrder(int $orderId, array $orderData): bool;
    public function completeOrder(int $orderId): bool;
}

interface ProductRepositoryInterface {
    public function getProductById(int $productId): array;
}

interface CustomerRepositoryInterface {
    public function getCustomerById(int $customerId): array;
}

class OrderService {
    private $orderRepository;
    private $productRepository;
    private $customerRepository;

    public function __construct(OrderRepositoryInterface $orderRepository, ProductRepositoryInterface $productRepository, CustomerRepositoryInterface $customerRepository) {
        $this->orderRepository = $orderRepository;
        $this->productRepository = $productRepository;
        $this->customerRepository = $customerRepository;
    }

    public function createOrder(array $orderData): int {
        // Use the injected order repository to create the order
        $orderId = $this->orderRepository->createOrder($orderData);

        // Use the injected product repository to get the product information
        $product = $this->productRepository->getProductById($orderData['product_id']);

        // Use the injected customer repository to get the customer information
        $customer = $this->customerRepository->getCustomer