Chia ra để trị

Nếu chức vô địch thuộc về Template Method pattern thì ngôi á quân chắc chắn sẽ phải thuộc về Composite, một design pattern giúp chúng ta chia source code thành các tầng khác nhau và giúp chúng ta đảm bảo nguyên tắc mỗi lớp chỉ làm một nhiệm vụ riêng biệt (Single Responsibility), và việc thay đổi code của một tầng sẽ không làm ảnh hưởng đến các tầng khác từ đó chúng ta cũng đảm toàn được nguyên tắc đóng với thay đổi, mở với mở rộng (Open/Close)

Vấn đề thực tế

Có bao giờ bạn viết một lớp nào đó 300 dòng chưa? kiểu thế này

public class BookController {
    private EntityManager entityManager;
    public BookResponse addBook(BookRequest request) {
        BookEntity bookEntity = new BookEntity();
        bookEntity.setName(request.getBookName());
        bookEntity.setAuthor(request.getAuthor());
        bookEntity.setCategory(request.getCategory());
        bookEntity.setCreatedDate(new Date());

        // bookEntity set các field khác
        BookEntity savedBookEntity = entityManager.persist(bookEntity);
        BookResponse bookResponse = new BookResponse();
        bookResponse.setBookId(savedBookEntity.getId());
        bookResponse.setBookName(savedBookEntity.getName());
        bookResponse.setAuthor(savedBookEntity.getAuthor());
        bookResponse.setCategory(savedBookEntity.getCategory());

        // bookResponse set các field khác
        return bookResponse;
    }
}

Và một ngày nào đó chúng ta cần thêm 2 trường price (giá sách) và releaseDate (ngày phát hành) thì sao? bạn có thể thấy code của chúng ta sẽ càng ngày càng dài ra, và việc sửa đổi một lớp dài ngoằng như vậy sẽ chứa đựng rất nhiều rủi ro. Vậy chúng ta nên làm thế nào? Đó là khi chúng ta cần đến composite design pattern.

Mục tiêu ra đời

Composite pattern ra đời nhằm mục tiêu:

  1. Tách các lớp lớn với nhiều dòng code ra thành các lớp nhỏ với ít dòng code hơn, thuật tiện cho việc đọc hiểu và maintain sau này
  2. Đảm bảo mỗi lớp sẽ chỉ thực hiện các các nhiệm vụ riêng việc, tăng khả common (tái sử dụng) code
  3. Hạn chế việc thay đổi lớp sử dụng và tăng khả năng mở rộng source code về sau này
  4. Tăng khả năng đa thừa kế, đưa ra nhiều sự lựa chọn hơn là chỉ sử dụng extends

Trong phạm vi của bài viết này, chúng ta hãy nói về việc tách lớp trước nhé.

Giải quyết vấn đề

Quay trở lại bài toán ở trên, chúng ta sẽ cần tách 3 lớp

Sơ đồ lớp
  1. Lớp RequestToEntityConverter: chịu trách nhiệm chuyển Request thành Entity để lưu được xuống cơ sở dữ liệu, chúng ta không thể lưu đối tượng Request xuống cơ sở dữ liệu vì nó thiếu trường IdcreatedDate
  2. EntityToResponseConverter: Chịu trách nhiệm chuyển Entity thành Response, chúng ta không nên trả về đối tượng Entity cho client vì sẽ có những trường chứa dữ liệu nhạy cảm hoặc những trường mà client ko cần dùng, ví dụ như password hoặc createdDate
  3. BookController: chỉ còn phải chịu trách nhiệm xử lý nghiệp vụ và không còn phải thực hiện các thao tác chuyển đổi nữa
Source code

Khi đã có sơ đồ lớp rồi thì tiến hành cài đặt thôi nhỉ.

class RequestToEntityConverter {
    public BookEntity bookRequestToEntity(BookRequest request) {
        BookEntity bookEntity = new BookEntity();
        bookEntity.setName(request.getBookName());
        bookEntity.setAuthor(request.getAuthor());
        bookEntity.setCategory(request.getCategory());
        bookEntity.setCreatedDate(new Date());
        return bookEntity;
    }
}
class EntityToResponseConverter {
    public BookResponse bookEntityToResponse(BookEntity entity) {
        BookResponse bookResponse = new BookResponse();
        bookResponse.setBookId(entity.getId());
        bookResponse.setBookName(entity.getName());
        bookResponse.setAuthor(entity.getAuthor());
        bookResponse.setCategory(entity.getCategory());
        return bookResponse;
    }
}
public class BookController {

    private EntityManager entityManager;
    private RequestToEntityConverter requestToEntityConverter;
    private EntityToResponseConverter entityToResponseConverter;

    public BookResponse addBook(BookRequest request) {
        BookEntity bookEntity = requestToEntityConverter.bookRequestToEntity(request);
        BookEntity savedBookEntity = entityManager.persist(bookEntity);
        BookResponse bookResponse = entityToResponseConverter.bookEntityToResponse(savedBookEntity);
        return bookResponse;
    }
}

Bạn có thể thấy lớp BookController của chúng ta, lớp quan trọng nhất sẽ ngắn gọn hơn rất nhiều, và sau này khi có thêm mới trường cho Book chẳng hạn thì chúng ta cũng không cần sửa đổi gì lớp BookController.

Tổng kết

Trong thực tế đối với các dự án lớn, việc quản lý source code là tương đối khó khăn, việc phải maintain hàng trăm nghìn, hàng triệu dòng code sẽ khiến chúng ta chìm trong mớ hỗn độn, nếu không chia nhỏ được source code, không thể #unitest thì sẽ đến một ngày nào đó chúng ta sẽ phải đập dự án đi và xây lại từ đầu Việc phân chia source code và tách nhỏ file thế nào nó phụ thuộc rất nhiều vào sự nhạy cảm của lập trình viên, muốn có được sự nhạy cảm này không có cách nào khác ngoài luôn luôn thử nghiệm nhiều cách làm khác nhau, từ đó tích lũy được kinh nghiệm và đưa ra được quyết định hợp lý, nhưng cũng đừng lạm dụng Composite pattern này nhé.

Tham khảo

  1. oodesign
  2. Ví dụ