Nguyên lí của lập trình hướng đối tượng

Phương pháp lập trình hướng đối tượng đã được nghiên cứu và phát triển từlâu

nhưng việc vận dụng nó nhưthếnào cho hiệu quảtrong việc xây dựng phần mềm là điều

vẫn còn khá mơhồ đối với nhiều người. Thếnào là một phần mềm hướng đối tượng ? Đâu

là những cơsởnền tảng đểxây dựng được phần mềm theo tưtưởng hướng đối tượng

đúng nghĩa ? Bài viết này trình bày vềcác nguyên lý lập trình hướng đối tượng. Đó là

những quy tắc phân tích thiết kếhướng đối tượng cơbản, mang tính chất khái quát. Do là

nguyên lý nên nó có tính trừu tượng cao chứkhông đi vào chi tiết cách thức giải quyết

vấn đềcụthể(việc hiện thực hóa những nguyên lý lập trình hướng đối tượng đòi hỏi

chúng ta phải xem xét đến Design Patterns)

pdf12 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 1668 | Lượt tải: 4download
Tóm tắt nội dung Nguyên lí của lập trình hướng đối tượng, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
ùng để phát hiện kế thừa. Khi lớp đối tượng B về
mặt ngữ nghĩa là một trường hợp đặc biệt của lớp đối tượng A thì ta có thể cho B
kế thừa từ A. Nhưng thực tế cho thấy, trong một số ngữ cảnh của phần mềm, một
lớp đối tượng có quan hệ “IS-A” với những lớp đối tượng khác nhưng việc để nó kế
thừa những lớp đối tượng này sẽ dẫn đến việc vi phạm nguyên lý Thay thế Liskov.
Xét đoạn chương trình sau.
public class Rectangle
{
// Data members of rectangle...
// Member functions of rectangle...
}
public class Square: Rectangle
{
// Data members of square...
// Member functions of square...
}
public double doSomething(Rectangle obj)
{
obj.setWidth(5);
obj.setHeight(6);
if (obj.Area == 30)
return obj.Area;
throw new ArgumentException();
}
Ở đoạn chương trình trên, mặc dù về mặt ngữ nghĩa, hình vuông là một trường hợp
của hình chữ nhật. Điều này hoàn toàn đúng!!! Nhưng trong ngữ cảnh này, việc để
“Square” kế thừa “Rectangle” là không phù hợp. Lúc này hàm “doSomething” cư xử
khác nhau trên các đối tượng của “Rectangle” và “Square”. Như vậy hàm
“doSomething” đã vi phạm nguyên lý Thay thế Liskov. Để hàm “doSomething” có
thể làm việc được trên cả “Rectangle” và “Square” chúng ta phải chỉnh sửa lại nó.
Như vậy việc vi phạm nguyên lý Thay thế Liskov đã làm cho hàm “doSomething” vi
phạm nguyên lý Open-Closed.
v) Nguyên lý Thay thế Liskov có mối liên hệ mật thiết với kỹ thuật “Design by
Contract” được đề cập bởi Bertrand Meyers. Kỹ thuật này chỉ ra rằng: mỗi phương
thức trong một lớp đối tượng, khi được định nghĩa, đã hàm chứa trong nó tiền điều
kiện (pre-condition) và hậu điều kiện (post-condition). Tiền điều kiện là những điều
kiện cần để phương thức có thể thực hiện được. Hậu điều kiện là những ràng buộc
phát sinh sau khi thực hiện phương thức. Khi thực hiện việc kế thừa, phương thức
được định nghĩa lại trong lớp kế thừa phải có tiền điều kiện lỏng lẻo hơn (weaker)
và hậu điều kiện chặt chẽ hơn (stronger). Điều này có nghĩa là trước khi thực hiện,
phương thức được định nghĩa lại trong lớp kế thừa không được đòi hỏi nhiều hơn
như khi nó được định nghĩa trong lớp cơ sở. Và sau khi thực hiện, phương thức
được định nghĩa lại trong lớp kế thừa phải đảm bảo tất cả những ràng buộc phát
sinh như khi nó được định nghĩa trong lớp cơ sở. Chỉ khi nào những điều trên được
đáp ứng cho mọi phương thức trong lớp kế thừa thì lớp kế thừa mới được xem là cư
xử như lớp cơ sở. Và khi đó, việc để nó kế thừa từ lớp cơ sở mới là đúng đắn trong
ngữ cảnh phần mềm đang xét.
vi) Nguyên lý Thay thế Liskov và kỹ thuật “Design by Contract” vô tình làm cho việc
kế thừa trở nên rất khó thực hiện. Khi cần thêm vào một lớp kế thừa, chúng ta phải
xem xét rất kỹ lưỡng lại tất cả hàm có thao tác trên lớp cơ sở xem chúng có vi
phạm nguyên lý Thay thế Liskov hay không. Chúng ta cũng cần phải xem xét tất cả
các phương thức của lớp kế thừa xem chúng có vi phạm những quy định của kỹ
thuật “Design by Contract” hay không. Tất cả những điều này là do lớp kế thừa có
một mối liên hệ mật thiết với lớp cơ sở. Lớp kế thừa bị kết dính (coupling) chặt chẽ
với lớp cơ sở. Sự kết dính này rõ ràng làm cho phần mềm kém linh động (flexibility)
một khi có sự thay đổi xảy ra. Do đó, để hạn chế sự kết dính này mà vẫn đảm bảo
được tính tái sử dụng, chúng ta chỉ nên kế thừa interface và sử dụng composition
thay cho việc kế thừa.
Ý nghĩa
Nguyên lý Thay thế Liskov có mối liên hệ mật thiết với nguyên lý Open-Closed và là
một trong bốn nguyên lý cơ bản làm nền tảng cho phân tích thiết kế hướng đối tượng. Nó
giúp nâng cao tính tái sử dụng và bền vững của phần mềm trước những sự thay đổi.
Nguyên lý Phân tách interface (The Interface Segregation)
Phát biểu
Không nên buộc các thực thể phần mềm phụ thuộc vào những interface mà chúng
không sử dụng đến.
Nội dung
Khi xây dựng một lớp đối tượng, đặc biệt là những lớp trừu tượng (abstract class),
nhiều người thường có xu hướng để cho lớp đối tượng thực hiện càng nghiều chức năng
càng tốt, đưa thật nhiều thuộc tính và phương thức vào lớp đối tượng đó. Những lớp đối
tượng như vậy được gọi là những lớp đối tượng có interface bị “ô nhiễm” (fat interface or
polluted interface).
Khi một lớp đối tượng có interface bị “ô nhiễm”, nó sẽ trở nên cồng kềnh. Một thực
thể phần mềm nào đó chỉ cần thực hiện một công việc đơn giản mà lớp đối tượng này hỗ
trợ buộc phải làm việc với toàn bộ interface của lớp đối tượng đó. Việc phải truyền đi
truyền lại nhiều lần những đối tượng có interface bị “ô nhiễm” sẽ làm giảm hiệu năng của
phần mềm.
Đặc biệt đối với lớp trừu tượng có interface bị “ô nhiễm”, một số lớp kế thừa chỉ quan
tâm đến một phần interface của lớp cơ sở nhưng bị buộc phải thực hiện việc cài đặt cho cả
phần interface không hề có ý nghĩa đối với chúng. Điều này dẫn đến sự dư thừa không
cần thiết trong các thực thể phần mềm. Quan trọng hơn nữa, việc buộc các lớp kế thừa
phụ thuộc vào phần interface mà chúng không sử dụng đến sẽ làm tăng sự kết dính
(coupling) giữa các thực thể phần mềm. Một khi sự nâng cấp, mở rộng diễn ra, đòi hỏi
phần interface đó phải thay đổi, các lớp kế thừa này bị buộc phải chỉnh sửa theo. Điều này
làm cho chúng vi phạm nguyên lý Open-Closed.
Hình bên dươi là sơ đồ lớp cho đoạn chương trình tính điện trở mạch điện. “Resistor”
và “Lamp” là những mạch điện đơn giản với điện trở là một thuộc tính của mạch. Trong
khi “SeriesCircuit” và “ParallelCircuit” là những mạch điện phức hợp với điện trở của mạch
được tính từ các mạch điện con. Để có thể cư xử như nhau trên các loại mạch điện này
hay nói cách khác là truy xuất đến chúng một cách “trong suốt” (transparency), chúng ta
có “Circuit” là lớp trừu tượng chung đại diện cho các mạch điện khác nhau.
Lớp “Circuit” được thiết kế như trên được gọi là có interface bị “ô nhiễm”. “Resistor”
và “Lamp” bị buộc phải thực hiện việc cài đặt cho các phương thức “add” và “remove”
hoàn toàn chẳng có ý nghĩa gì với chúng. Điều này gây ra sự dư thừa code không cần
thiết cũng như gây “khó chịu” cho những thực thể phần mềm khác sử dụng “Resistor” và
“Lamp”.
Nhưng vấn đề chỉ thật sự xảy ra khi chúng ta nâng cấp, mở rộng đoạn chương trình
trên. Giả sử chúng ta cần thêm vào phương thức “removeAt” để hỗ trợ việc xóa mạch điện
con tại vị trí nào đó trong mạch điện phức hợp. Lúc này, chúng ta phải thực hiện việc
chỉnh sửa trên tất cả các lớp đối tượng kế thừa từ “Circuit”. Việc chỉnh sửa trên
“SeriesCircuit” và “ParallelCircuit” xem ra còn có thể chấp nhận được. Nhưng việc phải
chỉnh sửa trên “Resistor” và “Lamp” là không thể chấp nhận được vì phương thức
“removeAt” chẳng hề có ý nghĩa gì đối với chúng. Điều này rõ ràng làm cho “Resistor” và
“Lamp” vi phạm nguyên lý Open-Closed một cách “không chính đáng”.
Chú ý
i) Nguyên lý Phân tách interface có mối liên hệ với nguyên lý Open-Closed. Sự vi
phạm nguyên lý Phân tách interface có khả năng dẫn đến sự vi phạm nguyên lý
Open-Closed (xem phân tích ở trên).
ii) Để tránh vi phạm nguyên lý Phân tách Inteface, chúng ta nên giữ cho interface của
lớp đối tượng đơn giản và gọn nhẹ, nên làm theo tiêu chí “a class should do one
thing and do it well”. Chúng ta không nên để cho lớp đối tượng đảm nhận quá
nhiều trách nhiệm vì điều này dễ làm cho interface của nó bị “ô nhiễm”.
iii) Interface bị “ô nhiễm” của lớp đối tượng nên được phân tách ngay khi có thể để
tránh khả năng dẫn đến sự vi phạm nguyên lý Open-Closed. Việc phân tách
interface bị “ô nhiễm” của một lớp cơ sở có thể được thực hiện thông qua việc tăng
thêm mức độ trừu tượng trong cây kế thừa của nó. Lớp cơ sở ban đầu chỉ nên có
interface đơn giản mà mọi lớp kế thừa của nó đều cần phải có. Sau đó, phần
interface chung của một bộ phận lớp kế thừa được tổng hợp lại trong một lớp cơ
sở. Và lớp cơ sở này lại kế thừa từ lớp cơ sở ban đầu. Như vậy những lớp kế thừa
thuộc nhánh khác không bị phụ thuộc vào phần interface mà chúng không sử dụng
đến của bộ phận lớp kế thừa kia. 
Với trường hợp đoạn chương trình tính điện trở mạch điện, để giải quyết vấn đề
interface của “Circuit” bị “ô nhiễm”, chúng ta tăng thêm một mức độ trừu tượng
trong cây kế thừa của nó. Khi đó, “Circuit” đóng vai trò là lớp trừu tượng cho các
mạch điện khác nhau. Nó chỉ chứa phần interface chung nhất của tất cả các mạch
điện này. Và trong ngữ cảnh bài toán tính điện trở đơn giản thì nó chỉ chứa phương
thức “calcResistance”.
Chúng ta sẽ có lớp “SingleCircuit” đại diện cho các mạch điện đơn giản và
“ComplexCircuit” đại diện cho cách mạch điện phức hợp. “SingleCircuit” chứa phần
interface chung của các mạch điện đơn giản như “Resistor” và “Lamp” trong khi
“ComplexCircuit” chứa phần interface chung của các mạch điện phức hợp. Chúng ta
sẽ có được cây kế thừa như hình bên dưới.
Lúc này, khi cần thêm vào phương thức “removeAt” chúng ta chỉ việc nâng cấp
phần interface của “ComplexCircuit”, nhánh kế thừa bên “SingleCircuit” sẽ không bị
ảnh hưởng.
iv) Trong một số trường hợp, sau khi phân tách interface, một số lớp kế thừa mới
thêm vào muốn sử dụng những phần interface đã phân tách, chúng có thể thực
hiện việc đa kế thừa từ những lớp đối tượng hỗ trợ những phần interface này hoặc
cũng có thể kế thừa từ một lớp đối tượng hỗ trợ một phần interface chúng cần và
thực hiện composition đối với những đối tượng hỗ trợ phần interface còn lại.
Ý nghĩa
Nguyên lý Phân tách interface có mối liên hệ với nguyên lý Open-Closed và là một
trong bốn nguyên lý cơ bản làm nền tảng cho phân tích thiết kế hướng đối tượng. Nó giúp
giảm sự cồng kềnh, dư thừa không cần thiết cho phần mềm và quan trọng hơn là giảm sự
kết dính (copuling) làm hạn chế tính linh động (flexibility) của phần mềm.
Tài liệu tham khảo
- Robert C. Martin, The Open-Closed Principle, Object Mentor, 1996.
- Robert C. Martin, The Dependency Inversion Principle, Object Mentor, 1996.
- Robert C. Martin, The Liskov Substitution Principle, Object Mentor, 1996.
- Robert C. Martin, The Interface Segregation Principle, Object Mentor, 1996.
- Allen Holub, Why extends is evil?, Java World, 2003.

File đính kèm:

  • pdfNguyenLyOOP.pdf