10 nguyên lý trong thiết kế hướng đối tượng

Bài gốc : http://javarevisited.blogspot.sg/2012/03/10-object-oriented-design-principles.html

Chú thích của mình : Trước khi vào bài gốc mọi người lướt sơ qua bài này : What does “S” stands for in OOPS?

Những nguyên lý thiết kế hướng đối tượng (object oriented desing principle) là nền tảng trong lập trình hướng đối tượng, nhưng tôi thấy hầu hết các lập trình viên Java ra sức học các design patterns (Các mẫu thiết kế hướng đối tượng) như Singleton pattern, Decorator pattern hay Observer pattern nhưng không đặt sự chú ý của mình vào nghiên cứu Phân tích và thiết kế hướng đối tượng(Object oriented analysis and design). Điều quan trọng trong việc học nền tảng cơ bản của lập trình hướng đối tượng là Abstraction (Tính trừu tượng), Encapsulation(Tính đóng gói), Polymorphism(Tính đa hình) và Inheritance(Tính kế thừa),  nhưng đồng thời, việc quan trọng không kém là phải biết những nguyên lý của những tính chất cơ bản này, tạo ra một thiết kế trong sáng và có tính module hóa. Tôi thường xuyên gặp các lập trình viên Java ở nhiều cấp độ khác nhau, có những người chưa từng nghe nói về OOPS và nguyên lý thiết kế SOLID, hay đơn giản họ không biết lợi ích của việc đưa ra các nguyên lý thiết kế đối tượng đặc thù, hay làm thế nào sử dụng các nguyên lý thiết kế này vào việc viết mã lệnh.

Bài viết luôn luôn cố gắng đưa ra một giải pháp mang tính kết dính cao và giảm sự kết nối lỏng lẻo, về mã lệnh và thiết kế. Đào sâu vào mã nguồn mở từ Apache hay Sun là những ví dụ cực tốt để học về các nguyên lý thiết kế Java và OOPS. Cách đó sẽ cho chúng ta thấy làm thế nào để sử dụng các nguyên lý lập trình trong viết mã lênh và trong lập trình Java. Java Development Kit áp dụng một số mẫu thiết kế hướng đối tượng như Factory Pattern trong lớp BorderFactory, Singleton pattern trong lớp Runtime, đọc Effective Java của Joshua Bloch, một quyển sách quý từ người đã viết ra Java API. Một quyển sách ưu thích khác nữa trong các mẫu thiết kế hướng đối tượng là Head First Design Pattern của Kathy Sierra và vài người khác, và Head First Object Oriented Analysis and Design. Những cuốn sách này sẽ giúp bạn rất nhiều để viết mã tốt hơn, nắm bắt được hầu hết lợi ích của OOPS và nguyên lý thiết kế SOLID.

Các tốt nhất để học bất kì mẫu hay nguyên lý thiết kế hướng đối tượng nào đó là các ví dụ trong thực thế và hiểu được hậu quả khi vi phạm nguyên lý hướng đối tượng, tiêu đề của bài viết là Giới thiệu về các nguyên lý thiết kế hướng đối tượng cho lập trình viên Java mà chưa nắm bắt được vấn đề hay đang trong giai đoạn nghiên cứu, học tập. Cá nhân tôi nghĩ mỗi phần trong OOPS và nguyên lý thiết kế SOLID cần có một chủ đề để giải thích rõ ràng hơn về chúng, và tôi sẽ cố gắng hết sức để làm việc đó ở đây, nhưng bây giờ thì chỉ “cưỡi ngựa xem hoa trong thi trấn của các nguyên lý thiết kế hướng đối tượng”.

Mình sẽ sắp xếp lại các đề mục: SOLID trước và còn lại sau

SOLID (S – O – L – I – D)

Single Responsibility Principle (SRP)

Single responsibility principle một nguyên lý thiết kế trong SOLID, biểu thị chữ “S” trong SOLID. Như tiêu đề SRP, không nên có hơn một lý do để thay đổi một lớp hay một lớp nên luôn luôn xử lý một chức năng đơn lẻ, duy nhất. Nếu bạn đặt nhiều chức năng vào trong một lớp, điều đó dẫn đến sự phụ thuộc giữa các chức năng với nhau và mặc dù bạn chỉ thay đổi ở một chức năng thì cũng phá vỡ các chức năng còn lại, điều đó sẽ dẫn đến cần các nhiều vòng kiểm thử khác để tránh có bất cứ sự ngạc nhiên trên môi trường production (môi trường chạy thật).

Open-Closed Principle(OCP)

Những lớp, những phương thức hay những hàm nên dễ dàng(OPEN) cho việc mở rộng (thêm chức năng mới) và ĐÓNG(CLOSED) cho việc thay đổi. Đây là một nguyên lý thiết kế hay trong SOLID, nhằm tránh một ai đó thay đổi mã nguồn đã ổn định và đã qua kiểm thử. Open-Closed principle là “O” trong SOLID

Liskov Substitution Principle (LSP)

Theo nguyên lý LSP, khi các hàm hoặc phương thức của lớp cha sẽ hoạt động mà không xảy ra bất cứ vấn đề gì ở lớp con nếu đối tượng của lớp con thay thế cho đối tượng của lớp cha. LSP liên quan tương đối với SRP và ISP (mục tiếp theo). Nếu một lớp có nhiều chức năng hơn lớp con mà không thể hỗ trợ một số chức năng trên thì nó phá bỡ quy tắt LSP. Để hiện thực đúng với LSP, lớp con tăng thêm chức năng cho hàm hay phương thức ở lớp cha, nhưng không được làm giảm chức năng của phương thức đó. LSP biểu thị chữ “L” trong SOLID.

Interface Segregation Principle (ISP)

Chương trình không nên cài đặt một Interface nếu nó không sử dụng đến. Điều này thường xảy ra khi một Interface chưa nhiều hơn một chức năng, và chương trình chỉ cần một phương thức(chức năng) trong Interface đó.Thiết kế Interface là một công việc khá phức tạp bởi một khi bạn cho ra một Interface bạn không thể thay đổi nó mà không phá huỷ tất cả các cài đặt cho nó trước đó.

Dependency Injection or Inversion Principle (DIP)

Đừng yêu cầu các đối tượng phụ thuộc, nó sẽ được cung cấp bởi framework. Điều này được hiện thực rất tốt trong Spring framework, một nguyên lý thiết kế hay (đẹp) là bất cứ lớp nào cũng sẽ được tiêm (inject) bởi DI framework, dễ dàng kiểm thử với đối tượng mock và đơn giản cho việc bảo trì chương trình bởi vì việc tạo ra một đối tượng trong mã nguồn được tập trung trong framework và mã của chương trình sẽ không còn bừa bãi (litter) khi áp dùng DI framework. Có khá nhiều cách để cài đặt một DI (Dependency Injection) như thay đổi bytecode mà một số framework về AOP làm ví dụ AspectJ hay sử dụng proxies như Spring đang làm. Lướt qua example of IOC and DI design pattern để học hỏi được nhiều hơn về nguyên lý thiết kế này. DIP biểu thị cho chữ “D” trong SOLID.

Chúng ta vừa đi qua 5 nguyên lý SOLID, tiếp theo là 5 nguyên lý khác

DRY (Don’t repeat yourself)

Nguyên lý đầu tiên (ngoài SOLID) trong các nguyên lý thiết kế hướng đối tượng là DRY (don’t repeat yourself) có nghĩa là đừng bao giờ viết mã bị trùng lặp, thay vào đó sử dụng Abstraction (tính trừu tượng) để trừa tượng hoá những thứ chung tại một nơi. Nếu bạn có 2 đoạn mã y chang nhau ở hai nơi khác nhau, hãy đưa chúng vào một hàm, hay nếu bạn sử dụng một giá trị cứng (hard-coded value) hơn một lần hãy tạo cho nó một constant (public final constant). Ích lợi của nguyên lý này là để dễ bào trì mã nguồn. Quan trọng là đừng lạm dụng nó quá nhiều, trùng lặp không phải cho mã lệnh, mà là cho một chức năng. Nghĩa là, nếu bạn đã sử dụng một đoạn mã chung để kiểm tra tính hợp lệ của OrderID và SSN, không đồng nghĩa hai phương thức kiểm tra phải giống nhau hay chúng sẽ vẫn còn giống nhau trong tương lai. Bởi khi sử dụng một mã chung cho hai chức năng khác nhau (một kiểm tra OrderID, một kiểm tra SSN) bạn đã vô tình đưa chúng dính chặt vào nhau mãi mãi và khi OrderID thay đổi hình thức, chức năng kiểm tra tính hợp lệ của SSN cũng lẽ bị phá huỷ. Vì vậy để đề phòng sự dính chặt này không nên phối hợp mọi thứ lại mà nên sử dụng mã lệnh giống nhau nhưng không bị phụ thuộc vào nhau.

Encapsulate What Changes

Chỉ có một thứ bất biến trong phần mềm và đó chính là “Change” (Sự thay đổi, chơi chữ), vì vậy  phải bao đóng (encapsulate) mã nguồn mà bạn mong muôn hoặc mã nguồn bạn nghi ngờ sẽ bị thay đổi trong tương lai. Nếu bạn đang lập trình Java, hãy làm theo nguyên lý này bằng cách luôn tạo mặc định private cho biến hay phương thức và tăng độ truy cập từng bước từng bước một như là from private đến protected, không nên lên thẳng public. Một số mẫu thiết kế hướng đối tượng sử dụng nguyên lý này, Factory design pattern là một ví dụ điển hình, nó bao đóng việc tạo ra đối tượng và cung cấp một cách mềm dẻo để tạo ra các “sản phẩm” mới mà không ảnh hưởng đến mã hiện tại.

Favor Composition over Inheritance

Luôn luôn sử dụng favor composition thay cho inheritance nếu có thể. Có thể bạn sẽ tranh cãi với tôi về điều này, nhưng tôi đã tìm ra rằng Composition mềm dẻo hơn nhiều so với inheritance. Composition chấp nhận thay đổi hành vi của một lớp trong lúc Runtime (lúc chương trình đang chạy) bằng cách thiết lập những thuộc tính trong suốt quá trình runtime và bằng cách sử dụng Interface thay thế cho lớp, chúng ta sử dụng tính đa hình (polymorphism) để thay thế lớp cài đặt Interface tốt hơn một cách mềm dẻo bất cứ lúc nào. Ngay cả trong Effective Java khuyên nên dùng composition hơn là inheritance.

Để hiểu rõ hơn phần trên, các bạn nên tìm hiểu thêm favor composition là gì

Programming  for Interface not Implementation

Luôn luôn lập trình cho Interface, không lập trình cho việc cài đặt. Điều này sẽ giúp tạo ra mã lệnh mềm dẻo, và mã lệnh sẽ luôn làm việc với kì một cài đặt mới nào cho Interface. Vì vậy hãy sử dụng Interface cho biến, giá trị trả về của hàm hay các tham số của hàm trong Java. Đây cũng là một lời khuyên từ nhiều lập trình viên Java, được nhắt đến trong 2 cuốn Effective Java và Head First Design Pattern.

Delegation principle

Đừng tự mình làm hết mọi thứ, hãy giao nó cho lớp tương ứng. Một ví dụ kinh điển của nguyên lý Delegation là 2 hàm equals() và hashCode() trong Java. Để so sánh hai đối tượng có giống nhau, chúng ta yêu cầu chính nó so sánh thay vì đưa cho một lớp khác kiểm tra. Lợi ích của nguyên lý này là để trùng lặp mã và dễ dàng thay đổi hành vi của lớp.

Tất cả những nguyên lý thiết kế hướng đối tượng sẽ giúp cho bạn viết mã linh hoạt và tốt hơn, giảm sự phụ thuộcthuộc và tạo được mối liên kết cao. Lý thuyết là bước đầu tiên, nhưng nó rất quan trong trong quá trình quát triển khả năng và tìm cách ứng dụng những nguyên lý này.

One Comment on "10 nguyên lý trong thiết kế hướng đối tượng"

  1. TuanDK says:

    Dịch khá tốt và dễ hiểu, nội công của các hạ ta đoán ko nhầm thì khá là thâm hậu đấy.

Got something to say? Go for it!