SlideShare uma empresa Scribd logo
1 de 12
Baixar para ler offline
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)
Nguyên lý Open-Closed (The Open-Closed Principle)
Phát biểu
Các thực thể phần mềm (hàm, đơn thể, đối tượng, …) nên được xây dựng theo
hướng mở cho việc mở rộng (be opened for extension) nhưng đóng đối với việc sửa đổi
(be closed for modification).
Nội dung
Các thực thể trong một phần mềm không đứng riêng lẻ mà có sự gắn kết chặt chẽ
với nhau. Chúng phối hợp hoạt động để cùng nhau thực hiện các chức năng của phần
mềm. Do đó, việc nâng cấp, mở rộng một thực thể nào đó sẽ ảnh hưởng đến những thực
thể liên quan. Điều này có thể dẫn đến việc phải nâng cấp, mở rộng cả những thực thể
liên quan đó. Và trong thời đại đầy biến động hiện nay, việc phải thường xuyên nâng cấp,
mở rộng các thực thể trong phần mềm là điều khó tránh khỏi.
Để làm cho quá trình bảo trì, nâng cấp, mở rộng phần mềm diễn ra dễ dàng và hiệu
quả hơn, các thực thể phần mềm nên được xây dựng tuân theo nguyên lý Open-Closed.
Điều này có nghĩa là các thực thể phần mềm nên được xây dựng sao cho việc nâng cấp,
mở rộng đồng nghĩa với việc thêm vào những cái mới chứ không phải là thay đổi những
cái hiện có, từ đó tránh được việc phải thay đổi các thực thể liên quan.
Nguyên lý Nghịch đảo phụ thuộc (The Dependency Inversion Principle)
Phát biểu
Các thành phần trong phần mềm không nên phụ thuộc vào những cái riêng, cụ thể
(details) mà ngược lại nên phụ thuộc vào những cái chung, tổng quát (abstractions) của
những cái riêng, cụ thể đó.
Những cái chung, tổng quát (abstractions) không nên phụ vào những cái riêng, cụ
thể (details). Sự phụ thuộc này nên được đảo ngược lại.
Nội dung
Những cái chung, tổng quát là tập hợp của những đặc tính chung nhất từ những cái
riêng, cụ thể. Những cái riêng, cụ thể dù khác nhau thế nào đi nữa cũng đều tuân theo
các quy tắc chung mà cái chung, tổng quát của nó đã định nghĩa. Những cái chung, tổng
quát là những cái ít thay đổi và ít biến động. Trong khi đó, sự thay đổi lại thường xuyên
xảy ra ở những cái riêng, cụ thể. Việc phụ thuộc vào những cái chung, tổng quát sẽ giúp
cho các thành phần trong phần mềm trở nên linh động (flexible) và thích ứng tốt với sự
thay đổi thường xuyên diễn ra ở những cái riêng, cụ thể. Khi phụ thuộc vào những cái
chung, tổng quát, các thành phần trong phần mềm vẫn có thể hoạt động tốt mà không
cần phải sửa đổi một khi cái riêng, cụ thể được thay thế bằng một cái riêng, cụ thể khác
cùng loại.
Nguyên lý Thay thế Liskov (The Liskov Substitution Principle)
Phát biểu
Lớp B chỉ nên kế thừa từ lớp A khi và chỉ khi với mọi hàm F thao tác trên các đối
tượng của A, cách cư xử (behaviors) của F không thay đổi khi ta thay thế (substitute) các
đối tượng của A bằng các đối tượng của B.
Nội dung
Kế thừa (inheritance) là một trong những tính chất cơ bản của lập trình hướng đối
tượng. Đó là khả năng định nghĩa một lớp đối tượng dựa trên các lớp đối tượng đã được
định nghĩa trước đó. Các đối tượng của lớp kế thừa có khả năng cư xử (behave) như các
đối tượng của lớp cơ sở. Điều này có nghĩa là các đối tượng của lớp kế thừa hoàn toàn có
thể thay thế các đối tượng của lớp cơ sở trong những hàm thao tác trên các đối tượng của
lớp cơ sở.
Chính vì tính chất này mà chúng ta không thể sử dụng kế thừa một cách tùy tiện.
Giả sử ta có lớp A và hàm F thao tác trên các đối tượng của A. Để nâng cấp, mở rộng
phần mềm, ta cần thêm vào lớp B kế thừa từ A. Nhưng việc thay thế các đối tượng của A
bằng các đối tượng của B lại làm cho F cư xử sai lệch so với trước khi thực hiện việc thay
thế. Lúc này, để F có thể cư xử không đổi so với trước, ta phải chỉnh sửa lại F. Điều này
làm cho F vi phạm nguyên lý Open-Closed.
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.
Nguyên lý Open-Closed (The Open-Closed Principle)
Phát biểu
Các thực thể phần mềm (hàm, đơn thể, đối tượng, …) nên được xây dựng theo
hướng mở cho việc mở rộng (be opened for extension) nhưng đóng đối với việc sửa đổi
(be closed for modification).
Nội dung
Các thực thể trong một phần mềm không đứng riêng lẻ mà có sự gắn kết chặt chẽ
với nhau. Chúng phối hợp hoạt động để cùng nhau thực hiện các chức năng của phần
mềm. Do đó, việc nâng cấp, mở rộng một thực thể nào đó sẽ ảnh hưởng đến những thực
thể liên quan. Điều này có thể dẫn đến việc phải nâng cấp, mở rộng cả những thực thể
liên quan đó. Và trong thời đại đầy biến động hiện nay, việc phải thường xuyên nâng cấp,
mở rộng các thực thể trong phần mềm là điều khó tránh khỏi.
Để làm cho quá trình bảo trì, nâng cấp, mở rộng phần mềm diễn ra dễ dàng và hiệu
quả hơn, các thực thể phần mềm nên được xây dựng tuân theo nguyên lý Open-Closed.
Điều này có nghĩa là các thực thể phần mềm nên được xây dựng sao cho việc nâng cấp,
mở rộng đồng nghĩa với việc thêm vào những cái mới chứ không phải là thay đổi những
cái hiện có, từ đó tránh được việc phải thay đổi các thực thể liên quan.
Xét ví dụ một đoạn chương trình vẽ đường thẳng và hình chữ nhật bằng C#.
public enum ShapeType
{
LINE,
RECTANGLE
}
public abstract class Shape
{
public abstract ShapeType getType();
}
public class Line: Shape
{
public override ShapeType getType()
{
return ShapeType.LINE;
}
public void drawLine()
{
// Draws the line...
}
}
public class Rectangle: Shape
{
public override ShapeType getType()
{
return ShapeType.RECTANGLE;
}
public void drawRectangle()
{
// Draws the rectangle...
}
}
public void draw(ArrayList shapeList)
{
Line line;
Rectangle rectangle;
foreach (Shape s in shapeList)
switch (s.getType())
{
case ShapeType.LINE:
line = (Line)s;
line.drawLine();
break;
case ShapeType.RECTANGLE:
rectangle = (Rectangle)s;
rectangle.drawRectangle();
break;
}
}
Đoạn chương trình trên hoạt động rất tốt cho đến khi có sự nâng cấp, mở rộng. Giả
sử chúng ta cần nâng cấp, mở rộng đoạn chương trình trên để nó có thể vẽ thêm được
hình tròn. Lúc bấy giờ ta phải chỉnh sửa lại hàm “draw”, thêm vào một trường hợp vẽ hình
tròn. Và trong nhiều tình huống, việc chỉnh sửa hàm “draw” sẽ dẫn đến việc chỉnh sửa
những hàm khác liên quan. Hàm “draw” được viết theo cách này được nói là không tuân
thủ nguyên lý Open-Closed.
Để đoạn chương trình trên tuân thủ nguyên lý Open-Closed, chúng ta sử dụng tính
đa hình của lập trình hướng đối tượng.
public abstract class Shape
{
public abstract void draw();
}
public class Line: Shape
{
public override void draw()
{
// Draws the line...
}
}
public class Rectangle: Shape
{
public override void draw()
{
// Draws the rectangle...
}
}
class Circle: Shape
{
public override void draw()
{
// Draws the circle...
}
}
public void draw(ArrayList shapeList)
{
foreach (Shape s in shapeList)
s.draw();
}
Với đoạn chương trình trên, khi thêm một hình mới vào, chúng ta chỉ việc thêm lớp
đối tượng cho hình đó (kế thừa từ Shape) mà không cần phải chỉnh sửa lại hàm “draw”.
Nó vẫn hoạt động tốt với những hình mới thêm vào.
Ghi chú
i) Không phải lúc nào tất cả các thực thể trong phần mềm đều có thể tuân thủ nguyên
lý Open-Closed. Nhưng mục tiêu của phân tích thiết kế hướng đối tượng là phải làm
sao cho số lượng các thực thể tuân thủ nguyên lý là lớn nhất, trong đó ưu tiên các
thực thể thường xuyên phải nâng cấp, mở rộng thỏa nguyên lý.
ii) Việc tuân thủ nguyên lý Open-Closed của một thực thể phần mềm chỉ mang tính
tương đối, phụ thuộc vào ngữ cảnh. Có thể trong ngữ cảnh này, thực thể thỏa
nguyên lý, nhưng trong một ngữ cảnh khác, thực thể này không còn tuân thủ
nguyên lý nữa. Mục tiêu của phân tích thiết kế hướng đối tượng là phải làm sao cho
có nhiều thực thể phần mềm nhất tuân thủ nguyên lý trong ngữ cảnh thường xảy
ra nhất của phần mềm, trong đó ưu tiên các thực thể thường xuyên phải nâng cấp,
mở rộng thỏa nguyên lý.
Ví dụ trường hợp hàm “draw” như trong đoạn chương trình vẽ hình trên.
public void draw(ArrayList shapeList)
{
foreach (Shape s in shapeList)
s.draw();
}
Hàm “draw” chỉ thỏa nguyên lý trong ngữ cảnh nâng cấp mở rộng là “thêm hình
mới”. Nếu chúng ta cần nâng cấp, mở rộng theo hướng thay đổi thứ tự vẽ các hình
thì hàm “draw” như trên là không thể đáp ứng được. Khi đó nó không còn tuân thủ
nguyên lý nữa.
iii) Một tính chất quan trọng trong lập trình hướng đối tượng giúp cho các thực thể
phần mềm tăng khả năng tuân thủ nguyên lý Open-Closed là tính đóng gói
(encapsulation). Đối tượng nắm giữ thông tin và chịu trách nhiệm trên thông tin
mình nắm giữ. Điều này giúp hạn chế sự kết dính (coupling) giữa các lớp đối tượng
với nhau. Trường hợp lý tưởng là tất cả thuộc tính của đối tượng được đặt tầm vực
private. việc thay đổi trên thuộc tính chỉ có thể được thực hiên thông qua những xử
lý của phương thức. Những phương thức của đối tượng khác, kể cả đối tượng kế
thừa không thể truy xuất được đến những thuộc tính này.
iv) Việc hạn chế sử dụng ép kiểu động (runtime type-casting) trong các thực thể phần
mềm cũng sẽ giúp làm tăng khả năng tuân thủ nguyên lý Open-Closed của chúng.
Vì bản chất của việc ép kiểu động là làm việc với một kiểu dữ liệu cụ thể. Khi muốn
nâng cấp, mở rộng thực thể để nó có thể làm việc với những kiểu dữ liệu khác,
đoạn chương trình sử dụng ép kiểu động phải được thay đổi để có thể làm việc
được với các kiểu dữ liệu khác này.
public void doSomething(Vehicle vehicle)
{
Car car = (Car)vehicle;
car.run();
car.stop();
}
Khi cần nâng cấp, mở rộng để đoạn chương trình trên có thể làm việc được với các
lớp đối tượng khác kế thừa từ “Vehicle”, chúng ta phải chỉnh sửa lại nó.
Ý nghĩa
Nguyên lý Open-Closed là nguyên lý cốt lõi 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 cho phần mềm dễ bảo trì,
nâng cấp và mở rộng.
Nguyên lý Nghịch đảo phụ thuộc (The Dependency Inversion Principle)
Phát biểu
Các thành phần trong phần mềm không nên phụ thuộc vào những cái riêng, cụ thể
(details) mà ngược lại nên phụ thuộc vào những cái chung, tổng quát (abstractions) của
những cái riêng, cụ thể đó.
Những cái chung, tổng quát (abstractions) không nên phụ vào những cái riêng, cụ
thể (details). Sự phụ thuộc này nên được đảo ngược lại.
Nội dung
Những cái chung, tổng quát là tập hợp của những đặc tính chung nhất từ những cái
riêng, cụ thể. Những cái riêng, cụ thể dù khác nhau thế nào đi nữa cũng đều tuân theo
các quy tắc chung mà cái chung, tổng quát của nó đã định nghĩa. Những cái chung, tổng
quát là những cái ít thay đổi và ít biến động. Trong khi đó, sự thay đổi lại thường xuyên
xảy ra ở những cái riêng, cụ thể. Việc phụ thuộc vào những cái chung, tổng quát sẽ giúp
cho các thành phần trong phần mềm trở nên linh động (flexible) và thích ứng tốt với sự
thay đổi thường xuyên diễn ra ở những cái riêng, cụ thể. Khi phụ thuộc vào những cái
chung, tổng quát, các thành phần trong phần mềm vẫn có thể hoạt động tốt mà không
cần phải sửa đổi một khi cái riêng, cụ thể được thay thế bằng một cái riêng, cụ thể khác
cùng loại.
Lấy ví dụ đoạn chương trình đọc dữ liệu từ bàn phím và xuất ra máy in.
public void copy()
{
Keyboard keyboard = new Keyboard();
Printer printer = new Printer();
char c;
while ((c = keyboard.read()) != ‘q’)
printer.write(c);
}
Khi nâng cấp, mở rộng đoạn chương trình trên để nó có thể xuất dữ liệu ra máy in
hoặc tập tin thì chúng ta phải chỉnh sửa lại đoạn chương trình trên như sau.
public void copy(OutputType type)
{
Keyboard keyboard = new Keyboard();
Printer printer = new Printer();
File file = new File();
char c;
while ((c = keyboard.read()) != ‘q’)
if (type == OutputType.PRINTER)
printer.write(c);
else if (type == OutputType.FILE)
file.write(c);
}
Rõ ràng hàm “copy” như trên đã vi phạm nguyên lý Open-Closed do khi mỗi lần cần
thêm một thiết bị đọc ghi mới vào, chúng ta phải chỉnh sửa lại nó. Nguyên nhân làm cho
hàm “copy” vi phạm nguyên lý Open-Closed là do nó làm việc với từng thiết bị đọc ghi cụ
thể. Khi thêm một thiết bị đọc ghi mới, chúng ta phải thêm vào hàm “copy” đoạn lệnh để
làm việc với thiết bị đọc ghi mới. Khi đó chúng ta nói hàm “copy” vi phạm nguyên lý
Nghịch đảo phụ thuộc.
Để đoạn chương trình trên tuân thủ Nguyên lý Nghịch đảo phụ thuộc, từ đó tuân thủ
Nguyên lý Open-Closed, chúng ta phải cho nó làm việc với thiết bị đọc ghi tổng quát.
public void copy(Reader reader, Writer writer)
{
char c;
while ((c = reader.read()) != ‘q’)
writer.write(c);
}
Hàm “copy” như trên có thể làm việc tốt với bất kỳ thiết bị đọc ghi nào tuân thủ
interface của Reader và Writer. Khi cần thêm thiết bị đọc ghi mới, chúng ta chỉ việc thêm
lớp đối tượng kế thừa từ Reader hoặc Writer mà không phải chỉnh sửa lại hàm “copy”.
Trích lời Allen Holub: “The more abstraction you add, the greater the flexibility. In
today’s business environment, where requirements regularly change as program
develops, this flexibility is essential.”.
Chú ý
i) Nguyên lý Nghịch đảo phụ thuộc có mối liên hệ mật thiết với nguyên lý Open-
Closed. Một khi nguyên lý Nghịch đảo phụ thuộc bị vi phạm, có nghĩa là những
thành phần trong phần mềm phụ thuộc vào những cái riêng, cụ thể, việc nâng cấp,
mở rộng ở những cái riêng, cụ thể (điều này rất thường xảy ra) buộc những thành
phần phụ thuộc vào nó bị thay đổi theo. Điều này dẫn đến vi phạm nguyên lý
Open-Closed.
ii) Sự nghịch đảo được đề cập đến ở đây nhằm nhấn mạnh đến việc cần phải thay đổi
quan điểm trong phân tích thiết kế phần mềm. Theo lối suy nghĩ “chia để trị” của
lập trình hướng cấu trúc, những công việc lớn, phức tạp, mang tính trừu tượng cao
thường được phân ra thành những công việc nhỏ, đơn giản và cụ thể hơn. Khi đó,
cấu trúc phần mềm có xu hướng theo dạng những thành phần lớn (trừu tượng) gọi
đến những thành phần nhỏ (cụ thể) hơn để yêu cầu chúng thực hiện công việc.
Điều này thường làm cho những thành phần trong phần mềm phụ thuộc vào những
cái riêng, cụ thể. Trong phân tích thiết kế hướng đối tượng, sự phụ thuộc này nên
được đảo ngược lại.
iii) Một thành phần trong phần mềm vi phạm nguyên lý Nghịch đảo phụ thuộc sẽ có
tính tái sử dụng (reusability) không cao. Việc mang những thành phần này sử dụng
vào một ngữ cảnh khác với những cái riêng, cụ thể khác là khó có thể thực hiện
được nếu như không thực hiện việc chỉnh sửa nào trên chúng.
iv) Một quy ước trong lập trình hướng đối tượng giúp cho các thành phần trong phần
mềm tăng khả năng tuân thủ nguyên lý Nghịch đảo phụ thuộc là thực hiện việc
truy xuất đến các đối tượng thông qua interface của chúng. Điều này sẽ làm cho
các thành phần bên trong phần mềm có tính linh động (flexibility) cao, không phải
sửa đổi khi thay thế các đối tượng được truy xuất đến bằng đối tượng khác cùng
loại.
public void doSomething(Car car)
{
car.run();
car.stop();
}
public void doSomething(Vehicle vehicle)
{
vehicle.run();
vehicle.stop();
}
Trong hai đoạn chương trình trên, đoạn chương trình thứ hai vẫn làm việc tốt khi
chúng ta thêm vào các đối tượng khác cùng loại với “Car” mà kế thừa từ “Vehicle”.
Ý nghĩa
Nguyên lý Nghịch đảo phụ thuộc 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 cho phần mềm có tính tái sử dụng cao, linh động và bền vững
(robustness) trước những sự thay đổi.
Nguyên lý Thay thế Liskov (The Liskov Substitution Principle)
Phát biểu
Lớp B chỉ nên kế thừa từ lớp A khi và chỉ khi với mọi hàm F thao tác trên các đối
tượng của A, cách cư xử (behaviors) của F không thay đổi khi ta thay thế (substitute) các
đối tượng của A bằng các đối tượng của B.
Nội dung
Kế thừa (inheritance) là một trong những tính chất cơ bản của lập trình hướng đối
tượng. Đó là khả năng định nghĩa một lớp đối tượng dựa trên các lớp đối tượng đã được
định nghĩa trước đó. Các đối tượng của lớp kế thừa có khả năng cư xử (behave) như các
đối tượng của lớp cơ sở. Điều này có nghĩa là các đối tượng của lớp kế thừa hoàn toàn có
thể thay thế các đối tượng của lớp cơ sở trong những hàm thao tác trên các đối tượng của
lớp cơ sở.
Chính vì tính chất này mà chúng ta không thể sử dụng kế thừa một cách tùy tiện.
Giả sử ta có lớp A và hàm F thao tác trên các đối tượng của A. Để nâng cấp, mở rộng
phần mềm, ta cần thêm vào lớp B kế thừa từ A. Nhưng việc thay thế các đối tượng của A
bằng các đối tượng của B lại làm cho F cư xử sai lệch so với trước khi thực hiện việc thay
thế. Lúc này, để F có thể cư xử không đổi so với trước, ta phải chỉnh sửa lại F. Điều này
làm cho F vi phạm nguyên lý Open-Closed.
Đoạn chương trình sau cho thấy việc kế thừa tùy tiện chỉ với mục đích tái sử dụng
nguy hiểm như thế nào.
public class Stack
{
private ArrayList data;
// More data members of stack.
public virtual void push(int n)
{
// Pushes n to stack...
}
public virtual int pop()
{
// Pops value from stack...
}
}
public class Queue: Stack
{
// Data members of Queue.
public override void push(int n)
{
// Pushes n to queue...
}
public override int pop()
{
// Pops value from queue...
}
}
public int func(Stack p)
{
p.push(5);
p.push(6);
p.push(7);
int a = p.pop();
int b = p.pop();
if (a == 7 && b == 6)
return a * b;
throw new ArgumentException();
}
Với mục đích tái sử dụng là một số thuộc tính và phương thức trong “Stack”, chúng
ta cho “Queue” kế thừa từ Stack. Xét hàm “func” thao tác trên đối tượng của “Stack”, do
“Queue” kế thừa từ “Stack” nên chúng ta hoàn toàn có thể truyền đối tượng của “Queue”
vào hàm này. Nhưng cách cư xử của hàm “func” khi thao tác trên các đối tượng của
“Stack” và “Queue” là khác nhau. Với các đối tượng của “Stack” hàm func luôn trả về
chính xác tích của hai số 7 và 6. Nhưng với các đối tượng của “Queue” hàm func lại luôn
gây ra một exception. Để hàm “func” có thể cư xử trên các đối tượng của “Stack” và
“Queue” như nhau, chúng ta phải viết lại nó. Điều này làm cho hàm “func” vi phạm
nguyên lý Open-Closed. Khi đó ta nói hàm “func” vi phạm nguyên lý Thay thế Liskov.
Chú ý
i) Nguyên lý Thay thế Liskov có mối liên hệ mật thiết với Nguyên lý Open-Closed. Sự
vi phạm nguyên lý Thay thế Liskov sẽ dẫn đến sự vi phạm nguyên lý Open-Closed.
Một thực thể phần mềm vi phạm nguyên lý Thay thế Liskov sẽ cư xử khác nhau
trên các đối tượng của lớp cơ sở và lớp kế thừa. Để thực thể phần mềm này vẫn có
thể làm việc tốt trên các đối tượng của cả lớp cơ sở và lớp kế thừa, chúng ta phải
chỉnh sửa lại nó. Điều này dẫn đến vi phạm nguyên lý Open-Closed.
ii) Không phải lúc nào tất cả các thực thể trong phần mềm đều có thể tuân thủ
nguyên lý Thay thế Liskov. Nhưng mục tiêu của phân tích thiết kế hướng đối tượng
là phải làm sao cho số lượng các thực thể tuân thủ nguyên lý là lớn nhất, trong đó
ưu tiên các thực thể thường xuyên phải nâng cấp, mở rộng thỏa nguyên lý.
iii) Việc tuân thủ nguyên lý Thay thế Liskov của một thực thể phần mềm chỉ mang
tính tương đối, phụ thuộc vào ngữ cảnh. Có thể trong ngữ cảnh này, thực thể thỏa
nguyên lý, nhưng trong một ngữ cảnh khác, thực thể này không còn tuân thủ
nguyên lý nữa. Mục tiêu của phân tích thiết kế hướng đối tượng là phải làm sao cho
có nhiều thực thể phần mềm nhất tuân thủ nguyên lý trong ngữ cảnh thường xảy
ra nhất của phần mềm, trong đó ưu tiên các thực thể thường xuyên phải nâng cấp,
mở rộng thỏa nguyên lý.
iv) Quan hệ “IS-A” thường được dù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.

Mais conteúdo relacionado

Destaque

Bài tập CTDL và GT 3
Bài tập CTDL và GT 3Bài tập CTDL và GT 3
Bài tập CTDL và GT 3Hồ Lợi
 
FPT Polytechnic Profile 2016
FPT Polytechnic Profile 2016FPT Polytechnic Profile 2016
FPT Polytechnic Profile 2016FPT Polytechnic
 
Danh Sach Lien Ket
Danh Sach Lien KetDanh Sach Lien Ket
Danh Sach Lien KetTony Nhân
 
Bài tập CTDL và GT 1
Bài tập CTDL và GT 1Bài tập CTDL và GT 1
Bài tập CTDL và GT 1Hồ Lợi
 
Bài tập CTDL và GT 12
Bài tập CTDL và GT 12Bài tập CTDL và GT 12
Bài tập CTDL và GT 12Hồ Lợi
 
bai tap cau truc du lieu ptit
bai tap cau truc du lieu ptitbai tap cau truc du lieu ptit
bai tap cau truc du lieu ptitMit Rin
 
BÀI 1: Làm quen với lập trình - Giáo trình FPT
BÀI 1: Làm quen với lập trình - Giáo trình FPTBÀI 1: Làm quen với lập trình - Giáo trình FPT
BÀI 1: Làm quen với lập trình - Giáo trình FPTMasterCode.vn
 
Bài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPT
Bài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPTBài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPT
Bài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPTMasterCode.vn
 
Bài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPT
Bài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPTBài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPT
Bài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPTMasterCode.vn
 
Bài tập mẫu C và C++ có giải
Bài tập mẫu C và C++ có giảiBài tập mẫu C và C++ có giải
Bài tập mẫu C và C++ có giảiTrung Thanh Nguyen
 
Lập trình C cơ bản cho vi điều khiển
Lập trình C cơ bản cho vi điều khiểnLập trình C cơ bản cho vi điều khiển
Lập trình C cơ bản cho vi điều khiểnMr Giap
 
Bài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPT
Bài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPTBài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPT
Bài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPTMasterCode.vn
 

Destaque (20)

Chuong6 (2)
Chuong6 (2)Chuong6 (2)
Chuong6 (2)
 
Bài tập CTDL và GT 3
Bài tập CTDL và GT 3Bài tập CTDL và GT 3
Bài tập CTDL và GT 3
 
FPT Polytechnic Profile 2016
FPT Polytechnic Profile 2016FPT Polytechnic Profile 2016
FPT Polytechnic Profile 2016
 
Chuong1
Chuong1Chuong1
Chuong1
 
Chuong8 (2)
Chuong8 (2)Chuong8 (2)
Chuong8 (2)
 
Chuong5 (2)
Chuong5 (2)Chuong5 (2)
Chuong5 (2)
 
Chuong4 (2)
Chuong4 (2)Chuong4 (2)
Chuong4 (2)
 
Chuong 1
Chuong 1Chuong 1
Chuong 1
 
Chuong2 c
Chuong2 c Chuong2 c
Chuong2 c
 
Danh Sach Lien Ket
Danh Sach Lien KetDanh Sach Lien Ket
Danh Sach Lien Ket
 
Bài tập CTDL và GT 1
Bài tập CTDL và GT 1Bài tập CTDL và GT 1
Bài tập CTDL và GT 1
 
Bài tập CTDL và GT 12
Bài tập CTDL và GT 12Bài tập CTDL và GT 12
Bài tập CTDL và GT 12
 
bai tap cau truc du lieu ptit
bai tap cau truc du lieu ptitbai tap cau truc du lieu ptit
bai tap cau truc du lieu ptit
 
BÀI 1: Làm quen với lập trình - Giáo trình FPT
BÀI 1: Làm quen với lập trình - Giáo trình FPTBÀI 1: Làm quen với lập trình - Giáo trình FPT
BÀI 1: Làm quen với lập trình - Giáo trình FPT
 
Web201 slide 1
Web201   slide 1Web201   slide 1
Web201 slide 1
 
Bài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPT
Bài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPTBài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPT
Bài 7: Danh sách liên kết (LINKED LIST) và tập hợp (SET) - Giáo trình FPT
 
Bài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPT
Bài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPTBài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPT
Bài 6: Các cấu trúc dữ liệu đặc biệt: ngăn xếp, hàng đợi, cây - Giáo trình FPT
 
Bài tập mẫu C và C++ có giải
Bài tập mẫu C và C++ có giảiBài tập mẫu C và C++ có giải
Bài tập mẫu C và C++ có giải
 
Lập trình C cơ bản cho vi điều khiển
Lập trình C cơ bản cho vi điều khiểnLập trình C cơ bản cho vi điều khiển
Lập trình C cơ bản cho vi điều khiển
 
Bài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPT
Bài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPTBài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPT
Bài 1: GIỚI THIỆU VỀ BẢO MẬT - Giáo trình FPT
 

Semelhante a Nguyen lyoop

Báo cáo thực tập chuyên nghành lập trình Android GPSGroup
Báo cáo thực tập chuyên nghành lập trình Android GPSGroupBáo cáo thực tập chuyên nghành lập trình Android GPSGroup
Báo cáo thực tập chuyên nghành lập trình Android GPSGroupTinh Ngo
 
lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...
lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...
lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...tPhan78
 
123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf
123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf
123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdfDuongDo35
 
Sử dụng API Leaflet chi tiết các chức năng
Sử dụng API Leaflet chi tiết các chức năngSử dụng API Leaflet chi tiết các chức năng
Sử dụng API Leaflet chi tiết các chức năngHao CT
 
BÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
BÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNGBÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
BÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNGTrường Phạm
 
07-ch14-OO design-sefdwaststratsrea8.ppt
07-ch14-OO design-sefdwaststratsrea8.ppt07-ch14-OO design-sefdwaststratsrea8.ppt
07-ch14-OO design-sefdwaststratsrea8.pptssuser7627f6
 
TT_MTKPM.pptx
TT_MTKPM.pptxTT_MTKPM.pptx
TT_MTKPM.pptxTriPhan74
 
Tài liệu Lập trình Zend Framework 2.x
Tài liệu Lập trình Zend Framework 2.xTài liệu Lập trình Zend Framework 2.x
Tài liệu Lập trình Zend Framework 2.xZendVN
 
Các phương pháp phân tích thiết kế phần.pptx
Các phương pháp phân tích thiết kế phần.pptxCác phương pháp phân tích thiết kế phần.pptx
Các phương pháp phân tích thiết kế phần.pptxdong92356
 
[Bao cao]tim hieu ve mo hinh lap trinh
[Bao cao]tim hieu ve mo hinh lap trinh[Bao cao]tim hieu ve mo hinh lap trinh
[Bao cao]tim hieu ve mo hinh lap trinhThùy Linh
 
2 introduction to oop
2 introduction to oop2 introduction to oop
2 introduction to oopHThu104
 
Vai trò của Jenkins trong mô hình phát triển phần mềm Agile
Vai trò của Jenkins trong mô hình phát triển phần mềm AgileVai trò của Jenkins trong mô hình phát triển phần mềm Agile
Vai trò của Jenkins trong mô hình phát triển phần mềm AgileMinh Tri Lam
 
Spring framework
Spring frameworkSpring framework
Spring frameworkAn Nguyen
 
Những thuật ngữ thường gặp trong Reverse Engineering .NET
Những thuật ngữ thường gặp trong Reverse Engineering .NETNhững thuật ngữ thường gặp trong Reverse Engineering .NET
Những thuật ngữ thường gặp trong Reverse Engineering .NETLevis Nickaster
 
Hướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hình
Hướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hìnhHướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hình
Hướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hìnhkey Pham
 
Elearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnh
Elearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnhElearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnh
Elearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnhThảo Uyên Trần
 

Semelhante a Nguyen lyoop (20)

Báo cáo thực tập chuyên nghành lập trình Android GPSGroup
Báo cáo thực tập chuyên nghành lập trình Android GPSGroupBáo cáo thực tập chuyên nghành lập trình Android GPSGroup
Báo cáo thực tập chuyên nghành lập trình Android GPSGroup
 
lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...
lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...
lap-trinh-huong-doi-tuong_nguyen-manh-son_lthdt_ptit - [cuuduongthancong.com]...
 
123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf
123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf
123doc-giai-ngan-hang-cong-nghe-phan-mem-ptit.pdf
 
Sử dụng API Leaflet chi tiết các chức năng
Sử dụng API Leaflet chi tiết các chức năngSử dụng API Leaflet chi tiết các chức năng
Sử dụng API Leaflet chi tiết các chức năng
 
Nhuong dieuhocc#phaibiet
Nhuong dieuhocc#phaibietNhuong dieuhocc#phaibiet
Nhuong dieuhocc#phaibiet
 
BÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
BÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNGBÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
BÀI GIẢNG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG
 
07-ch14-OO design-sefdwaststratsrea8.ppt
07-ch14-OO design-sefdwaststratsrea8.ppt07-ch14-OO design-sefdwaststratsrea8.ppt
07-ch14-OO design-sefdwaststratsrea8.ppt
 
TT_MTKPM.pptx
TT_MTKPM.pptxTT_MTKPM.pptx
TT_MTKPM.pptx
 
Tài liệu Lập trình Zend Framework 2.x
Tài liệu Lập trình Zend Framework 2.xTài liệu Lập trình Zend Framework 2.x
Tài liệu Lập trình Zend Framework 2.x
 
Các phương pháp phân tích thiết kế phần.pptx
Các phương pháp phân tích thiết kế phần.pptxCác phương pháp phân tích thiết kế phần.pptx
Các phương pháp phân tích thiết kế phần.pptx
 
Bai01 oop overview
Bai01 oop overviewBai01 oop overview
Bai01 oop overview
 
[Bao cao]tim hieu ve mo hinh lap trinh
[Bao cao]tim hieu ve mo hinh lap trinh[Bao cao]tim hieu ve mo hinh lap trinh
[Bao cao]tim hieu ve mo hinh lap trinh
 
Vb6 16 (10)
Vb6 16 (10)Vb6 16 (10)
Vb6 16 (10)
 
2 introduction to oop
2 introduction to oop2 introduction to oop
2 introduction to oop
 
Vai trò của Jenkins trong mô hình phát triển phần mềm Agile
Vai trò của Jenkins trong mô hình phát triển phần mềm AgileVai trò của Jenkins trong mô hình phát triển phần mềm Agile
Vai trò của Jenkins trong mô hình phát triển phần mềm Agile
 
Spring framework
Spring frameworkSpring framework
Spring framework
 
Luận văn: Nghiên cứu và ứng dụng mẫu thiết kế trong phương pháp hướng đối tượng
Luận văn: Nghiên cứu và ứng dụng mẫu thiết kế trong phương pháp hướng đối tượngLuận văn: Nghiên cứu và ứng dụng mẫu thiết kế trong phương pháp hướng đối tượng
Luận văn: Nghiên cứu và ứng dụng mẫu thiết kế trong phương pháp hướng đối tượng
 
Những thuật ngữ thường gặp trong Reverse Engineering .NET
Những thuật ngữ thường gặp trong Reverse Engineering .NETNhững thuật ngữ thường gặp trong Reverse Engineering .NET
Những thuật ngữ thường gặp trong Reverse Engineering .NET
 
Hướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hình
Hướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hìnhHướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hình
Hướng dẫn-cài-đặt-để-sữ-dụng-enterprise-architect-để-thiết-kế-các-mô-hình
 
Elearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnh
Elearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnhElearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnh
Elearning - Nhóm 06 - Chủ đề 3: Thiết kế một hệ e-Learning theo ngữ cảnh
 

Mais de Hồ Lợi

Lect04 functions
Lect04 functionsLect04 functions
Lect04 functionsHồ Lợi
 
Ky thuatkhudequy
Ky thuatkhudequyKy thuatkhudequy
Ky thuatkhudequyHồ Lợi
 
Itt epc assignment
Itt epc assignmentItt epc assignment
Itt epc assignmentHồ Lợi
 
Huong danontapc
Huong danontapcHuong danontapc
Huong danontapcHồ Lợi
 
H hai epc_baitap
H hai epc_baitapH hai epc_baitap
H hai epc_baitapHồ Lợi
 
Giaotrinhbaitapkythuatlaptrinh
GiaotrinhbaitapkythuatlaptrinhGiaotrinhbaitapkythuatlaptrinh
GiaotrinhbaitapkythuatlaptrinhHồ Lợi
 
Giao trinh ky thuat lap trinh 2
Giao trinh ky thuat lap trinh 2Giao trinh ky thuat lap trinh 2
Giao trinh ky thuat lap trinh 2Hồ Lợi
 
Giao trinh c c++
Giao trinh c c++Giao trinh c c++
Giao trinh c c++Hồ Lợi
 
Epc assignment
Epc assignmentEpc assignment
Epc assignmentHồ Lợi
 
Epc test practical
Epc test practicalEpc test practical
Epc test practicalHồ Lợi
 
De thic++ --th
De thic++ --thDe thic++ --th
De thic++ --thHồ Lợi
 

Mais de Hồ Lợi (20)

T4
T4T4
T4
 
Lect04 functions
Lect04 functionsLect04 functions
Lect04 functions
 
Ky thuatkhudequy
Ky thuatkhudequyKy thuatkhudequy
Ky thuatkhudequy
 
Itt epc assignment
Itt epc assignmentItt epc assignment
Itt epc assignment
 
Huong danontapc
Huong danontapcHuong danontapc
Huong danontapc
 
H hai epc_baitap
H hai epc_baitapH hai epc_baitap
H hai epc_baitap
 
Gtrinh oop
Gtrinh oopGtrinh oop
Gtrinh oop
 
Giaotrinhbaitapkythuatlaptrinh
GiaotrinhbaitapkythuatlaptrinhGiaotrinhbaitapkythuatlaptrinh
Giaotrinhbaitapkythuatlaptrinh
 
Giao trinh ky thuat lap trinh 2
Giao trinh ky thuat lap trinh 2Giao trinh ky thuat lap trinh 2
Giao trinh ky thuat lap trinh 2
 
Giao trinh c c++
Giao trinh c c++Giao trinh c c++
Giao trinh c c++
 
File trong c_
File trong c_File trong c_
File trong c_
 
Epc assignment
Epc assignmentEpc assignment
Epc assignment
 
Epc test practical
Epc test practicalEpc test practical
Epc test practical
 
De thic++ --th
De thic++ --thDe thic++ --th
De thic++ --th
 
Dethi c++ -lt
Dethi c++ -ltDethi c++ -lt
Dethi c++ -lt
 
Debug trong c
Debug trong cDebug trong c
Debug trong c
 
D05 stl
D05 stlD05 stl
D05 stl
 
Cpl test3
Cpl test3Cpl test3
Cpl test3
 
Cpl test2
Cpl test2Cpl test2
Cpl test2
 
Cpl test1
Cpl test1Cpl test1
Cpl test1
 

Nguyen lyoop

  • 1. 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) Nguyên lý Open-Closed (The Open-Closed Principle) Phát biểu Các thực thể phần mềm (hàm, đơn thể, đối tượng, …) nên được xây dựng theo hướng mở cho việc mở rộng (be opened for extension) nhưng đóng đối với việc sửa đổi (be closed for modification). Nội dung Các thực thể trong một phần mềm không đứng riêng lẻ mà có sự gắn kết chặt chẽ với nhau. Chúng phối hợp hoạt động để cùng nhau thực hiện các chức năng của phần mềm. Do đó, việc nâng cấp, mở rộng một thực thể nào đó sẽ ảnh hưởng đến những thực thể liên quan. Điều này có thể dẫn đến việc phải nâng cấp, mở rộng cả những thực thể liên quan đó. Và trong thời đại đầy biến động hiện nay, việc phải thường xuyên nâng cấp, mở rộng các thực thể trong phần mềm là điều khó tránh khỏi. Để làm cho quá trình bảo trì, nâng cấp, mở rộng phần mềm diễn ra dễ dàng và hiệu quả hơn, các thực thể phần mềm nên được xây dựng tuân theo nguyên lý Open-Closed. Điều này có nghĩa là các thực thể phần mềm nên được xây dựng sao cho việc nâng cấp, mở rộng đồng nghĩa với việc thêm vào những cái mới chứ không phải là thay đổi những cái hiện có, từ đó tránh được việc phải thay đổi các thực thể liên quan. Nguyên lý Nghịch đảo phụ thuộc (The Dependency Inversion Principle) Phát biểu Các thành phần trong phần mềm không nên phụ thuộc vào những cái riêng, cụ thể (details) mà ngược lại nên phụ thuộc vào những cái chung, tổng quát (abstractions) của những cái riêng, cụ thể đó. Những cái chung, tổng quát (abstractions) không nên phụ vào những cái riêng, cụ thể (details). Sự phụ thuộc này nên được đảo ngược lại. Nội dung Những cái chung, tổng quát là tập hợp của những đặc tính chung nhất từ những cái riêng, cụ thể. Những cái riêng, cụ thể dù khác nhau thế nào đi nữa cũng đều tuân theo các quy tắc chung mà cái chung, tổng quát của nó đã định nghĩa. Những cái chung, tổng quát là những cái ít thay đổi và ít biến động. Trong khi đó, sự thay đổi lại thường xuyên xảy ra ở những cái riêng, cụ thể. Việc phụ thuộc vào những cái chung, tổng quát sẽ giúp cho các thành phần trong phần mềm trở nên linh động (flexible) và thích ứng tốt với sự thay đổi thường xuyên diễn ra ở những cái riêng, cụ thể. Khi phụ thuộc vào những cái chung, tổng quát, các thành phần trong phần mềm vẫn có thể hoạt động tốt mà không cần phải sửa đổi một khi cái riêng, cụ thể được thay thế bằng một cái riêng, cụ thể khác cùng loại.
  • 2. Nguyên lý Thay thế Liskov (The Liskov Substitution Principle) Phát biểu Lớp B chỉ nên kế thừa từ lớp A khi và chỉ khi với mọi hàm F thao tác trên các đối tượng của A, cách cư xử (behaviors) của F không thay đổi khi ta thay thế (substitute) các đối tượng của A bằng các đối tượng của B. Nội dung Kế thừa (inheritance) là một trong những tính chất cơ bản của lập trình hướng đối tượng. Đó là khả năng định nghĩa một lớp đối tượng dựa trên các lớp đối tượng đã được định nghĩa trước đó. Các đối tượng của lớp kế thừa có khả năng cư xử (behave) như các đối tượng của lớp cơ sở. Điều này có nghĩa là các đối tượng của lớp kế thừa hoàn toàn có thể thay thế các đối tượng của lớp cơ sở trong những hàm thao tác trên các đối tượng của lớp cơ sở. Chính vì tính chất này mà chúng ta không thể sử dụng kế thừa một cách tùy tiện. Giả sử ta có lớp A và hàm F thao tác trên các đối tượng của A. Để nâng cấp, mở rộng phần mềm, ta cần thêm vào lớp B kế thừa từ A. Nhưng việc thay thế các đối tượng của A bằng các đối tượng của B lại làm cho F cư xử sai lệch so với trước khi thực hiện việc thay thế. Lúc này, để F có thể cư xử không đổi so với trước, ta phải chỉnh sửa lại F. Điều này làm cho F vi phạm nguyên lý Open-Closed. 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. Nguyên lý Open-Closed (The Open-Closed Principle) Phát biểu Các thực thể phần mềm (hàm, đơn thể, đối tượng, …) nên được xây dựng theo hướng mở cho việc mở rộng (be opened for extension) nhưng đóng đối với việc sửa đổi (be closed for modification). Nội dung Các thực thể trong một phần mềm không đứng riêng lẻ mà có sự gắn kết chặt chẽ với nhau. Chúng phối hợp hoạt động để cùng nhau thực hiện các chức năng của phần
  • 3. mềm. Do đó, việc nâng cấp, mở rộng một thực thể nào đó sẽ ảnh hưởng đến những thực thể liên quan. Điều này có thể dẫn đến việc phải nâng cấp, mở rộng cả những thực thể liên quan đó. Và trong thời đại đầy biến động hiện nay, việc phải thường xuyên nâng cấp, mở rộng các thực thể trong phần mềm là điều khó tránh khỏi. Để làm cho quá trình bảo trì, nâng cấp, mở rộng phần mềm diễn ra dễ dàng và hiệu quả hơn, các thực thể phần mềm nên được xây dựng tuân theo nguyên lý Open-Closed. Điều này có nghĩa là các thực thể phần mềm nên được xây dựng sao cho việc nâng cấp, mở rộng đồng nghĩa với việc thêm vào những cái mới chứ không phải là thay đổi những cái hiện có, từ đó tránh được việc phải thay đổi các thực thể liên quan. Xét ví dụ một đoạn chương trình vẽ đường thẳng và hình chữ nhật bằng C#. public enum ShapeType { LINE, RECTANGLE } public abstract class Shape { public abstract ShapeType getType(); } public class Line: Shape { public override ShapeType getType() { return ShapeType.LINE; } public void drawLine() { // Draws the line... } } public class Rectangle: Shape { public override ShapeType getType() { return ShapeType.RECTANGLE; } public void drawRectangle() { // Draws the rectangle... } } public void draw(ArrayList shapeList) { Line line; Rectangle rectangle; foreach (Shape s in shapeList) switch (s.getType()) { case ShapeType.LINE: line = (Line)s;
  • 4. line.drawLine(); break; case ShapeType.RECTANGLE: rectangle = (Rectangle)s; rectangle.drawRectangle(); break; } } Đoạn chương trình trên hoạt động rất tốt cho đến khi có sự nâng cấp, mở rộng. Giả sử chúng ta cần nâng cấp, mở rộng đoạn chương trình trên để nó có thể vẽ thêm được hình tròn. Lúc bấy giờ ta phải chỉnh sửa lại hàm “draw”, thêm vào một trường hợp vẽ hình tròn. Và trong nhiều tình huống, việc chỉnh sửa hàm “draw” sẽ dẫn đến việc chỉnh sửa những hàm khác liên quan. Hàm “draw” được viết theo cách này được nói là không tuân thủ nguyên lý Open-Closed. Để đoạn chương trình trên tuân thủ nguyên lý Open-Closed, chúng ta sử dụng tính đa hình của lập trình hướng đối tượng. public abstract class Shape { public abstract void draw(); } public class Line: Shape { public override void draw() { // Draws the line... } } public class Rectangle: Shape { public override void draw() { // Draws the rectangle... } } class Circle: Shape { public override void draw() { // Draws the circle... } } public void draw(ArrayList shapeList) { foreach (Shape s in shapeList) s.draw(); } Với đoạn chương trình trên, khi thêm một hình mới vào, chúng ta chỉ việc thêm lớp đối tượng cho hình đó (kế thừa từ Shape) mà không cần phải chỉnh sửa lại hàm “draw”.
  • 5. Nó vẫn hoạt động tốt với những hình mới thêm vào. Ghi chú i) Không phải lúc nào tất cả các thực thể trong phần mềm đều có thể tuân thủ nguyên lý Open-Closed. Nhưng mục tiêu của phân tích thiết kế hướng đối tượng là phải làm sao cho số lượng các thực thể tuân thủ nguyên lý là lớn nhất, trong đó ưu tiên các thực thể thường xuyên phải nâng cấp, mở rộng thỏa nguyên lý. ii) Việc tuân thủ nguyên lý Open-Closed của một thực thể phần mềm chỉ mang tính tương đối, phụ thuộc vào ngữ cảnh. Có thể trong ngữ cảnh này, thực thể thỏa nguyên lý, nhưng trong một ngữ cảnh khác, thực thể này không còn tuân thủ nguyên lý nữa. Mục tiêu của phân tích thiết kế hướng đối tượng là phải làm sao cho có nhiều thực thể phần mềm nhất tuân thủ nguyên lý trong ngữ cảnh thường xảy ra nhất của phần mềm, trong đó ưu tiên các thực thể thường xuyên phải nâng cấp, mở rộng thỏa nguyên lý. Ví dụ trường hợp hàm “draw” như trong đoạn chương trình vẽ hình trên. public void draw(ArrayList shapeList) { foreach (Shape s in shapeList) s.draw(); } Hàm “draw” chỉ thỏa nguyên lý trong ngữ cảnh nâng cấp mở rộng là “thêm hình mới”. Nếu chúng ta cần nâng cấp, mở rộng theo hướng thay đổi thứ tự vẽ các hình thì hàm “draw” như trên là không thể đáp ứng được. Khi đó nó không còn tuân thủ nguyên lý nữa. iii) Một tính chất quan trọng trong lập trình hướng đối tượng giúp cho các thực thể phần mềm tăng khả năng tuân thủ nguyên lý Open-Closed là tính đóng gói (encapsulation). Đối tượng nắm giữ thông tin và chịu trách nhiệm trên thông tin mình nắm giữ. Điều này giúp hạn chế sự kết dính (coupling) giữa các lớp đối tượng với nhau. Trường hợp lý tưởng là tất cả thuộc tính của đối tượng được đặt tầm vực private. việc thay đổi trên thuộc tính chỉ có thể được thực hiên thông qua những xử lý của phương thức. Những phương thức của đối tượng khác, kể cả đối tượng kế thừa không thể truy xuất được đến những thuộc tính này. iv) Việc hạn chế sử dụng ép kiểu động (runtime type-casting) trong các thực thể phần mềm cũng sẽ giúp làm tăng khả năng tuân thủ nguyên lý Open-Closed của chúng. Vì bản chất của việc ép kiểu động là làm việc với một kiểu dữ liệu cụ thể. Khi muốn nâng cấp, mở rộng thực thể để nó có thể làm việc với những kiểu dữ liệu khác, đoạn chương trình sử dụng ép kiểu động phải được thay đổi để có thể làm việc được với các kiểu dữ liệu khác này. public void doSomething(Vehicle vehicle) { Car car = (Car)vehicle; car.run(); car.stop(); } Khi cần nâng cấp, mở rộng để đoạn chương trình trên có thể làm việc được với các lớp đối tượng khác kế thừa từ “Vehicle”, chúng ta phải chỉnh sửa lại nó. Ý nghĩa Nguyên lý Open-Closed là nguyên lý cốt lõi 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 cho phần mềm dễ bảo trì, nâng cấp và mở rộng.
  • 6. Nguyên lý Nghịch đảo phụ thuộc (The Dependency Inversion Principle) Phát biểu Các thành phần trong phần mềm không nên phụ thuộc vào những cái riêng, cụ thể (details) mà ngược lại nên phụ thuộc vào những cái chung, tổng quát (abstractions) của những cái riêng, cụ thể đó. Những cái chung, tổng quát (abstractions) không nên phụ vào những cái riêng, cụ thể (details). Sự phụ thuộc này nên được đảo ngược lại. Nội dung Những cái chung, tổng quát là tập hợp của những đặc tính chung nhất từ những cái riêng, cụ thể. Những cái riêng, cụ thể dù khác nhau thế nào đi nữa cũng đều tuân theo các quy tắc chung mà cái chung, tổng quát của nó đã định nghĩa. Những cái chung, tổng quát là những cái ít thay đổi và ít biến động. Trong khi đó, sự thay đổi lại thường xuyên xảy ra ở những cái riêng, cụ thể. Việc phụ thuộc vào những cái chung, tổng quát sẽ giúp cho các thành phần trong phần mềm trở nên linh động (flexible) và thích ứng tốt với sự thay đổi thường xuyên diễn ra ở những cái riêng, cụ thể. Khi phụ thuộc vào những cái chung, tổng quát, các thành phần trong phần mềm vẫn có thể hoạt động tốt mà không cần phải sửa đổi một khi cái riêng, cụ thể được thay thế bằng một cái riêng, cụ thể khác cùng loại. Lấy ví dụ đoạn chương trình đọc dữ liệu từ bàn phím và xuất ra máy in. public void copy() { Keyboard keyboard = new Keyboard(); Printer printer = new Printer(); char c; while ((c = keyboard.read()) != ‘q’) printer.write(c); } Khi nâng cấp, mở rộng đoạn chương trình trên để nó có thể xuất dữ liệu ra máy in hoặc tập tin thì chúng ta phải chỉnh sửa lại đoạn chương trình trên như sau. public void copy(OutputType type) { Keyboard keyboard = new Keyboard(); Printer printer = new Printer(); File file = new File(); char c; while ((c = keyboard.read()) != ‘q’) if (type == OutputType.PRINTER) printer.write(c); else if (type == OutputType.FILE) file.write(c); } Rõ ràng hàm “copy” như trên đã vi phạm nguyên lý Open-Closed do khi mỗi lần cần thêm một thiết bị đọc ghi mới vào, chúng ta phải chỉnh sửa lại nó. Nguyên nhân làm cho hàm “copy” vi phạm nguyên lý Open-Closed là do nó làm việc với từng thiết bị đọc ghi cụ thể. Khi thêm một thiết bị đọc ghi mới, chúng ta phải thêm vào hàm “copy” đoạn lệnh để làm việc với thiết bị đọc ghi mới. Khi đó chúng ta nói hàm “copy” vi phạm nguyên lý Nghịch đảo phụ thuộc. Để đoạn chương trình trên tuân thủ Nguyên lý Nghịch đảo phụ thuộc, từ đó tuân thủ
  • 7. Nguyên lý Open-Closed, chúng ta phải cho nó làm việc với thiết bị đọc ghi tổng quát. public void copy(Reader reader, Writer writer) { char c; while ((c = reader.read()) != ‘q’) writer.write(c); } Hàm “copy” như trên có thể làm việc tốt với bất kỳ thiết bị đọc ghi nào tuân thủ interface của Reader và Writer. Khi cần thêm thiết bị đọc ghi mới, chúng ta chỉ việc thêm lớp đối tượng kế thừa từ Reader hoặc Writer mà không phải chỉnh sửa lại hàm “copy”. Trích lời Allen Holub: “The more abstraction you add, the greater the flexibility. In today’s business environment, where requirements regularly change as program develops, this flexibility is essential.”. Chú ý i) Nguyên lý Nghịch đảo phụ thuộc có mối liên hệ mật thiết với nguyên lý Open- Closed. Một khi nguyên lý Nghịch đảo phụ thuộc bị vi phạm, có nghĩa là những thành phần trong phần mềm phụ thuộc vào những cái riêng, cụ thể, việc nâng cấp, mở rộng ở những cái riêng, cụ thể (điều này rất thường xảy ra) buộc những thành phần phụ thuộc vào nó bị thay đổi theo. Điều này dẫn đến vi phạm nguyên lý Open-Closed. ii) Sự nghịch đảo được đề cập đến ở đây nhằm nhấn mạnh đến việc cần phải thay đổi quan điểm trong phân tích thiết kế phần mềm. Theo lối suy nghĩ “chia để trị” của lập trình hướng cấu trúc, những công việc lớn, phức tạp, mang tính trừu tượng cao thường được phân ra thành những công việc nhỏ, đơn giản và cụ thể hơn. Khi đó, cấu trúc phần mềm có xu hướng theo dạng những thành phần lớn (trừu tượng) gọi đến những thành phần nhỏ (cụ thể) hơn để yêu cầu chúng thực hiện công việc. Điều này thường làm cho những thành phần trong phần mềm phụ thuộc vào những cái riêng, cụ thể. Trong phân tích thiết kế hướng đối tượng, sự phụ thuộc này nên được đảo ngược lại. iii) Một thành phần trong phần mềm vi phạm nguyên lý Nghịch đảo phụ thuộc sẽ có tính tái sử dụng (reusability) không cao. Việc mang những thành phần này sử dụng vào một ngữ cảnh khác với những cái riêng, cụ thể khác là khó có thể thực hiện được nếu như không thực hiện việc chỉnh sửa nào trên chúng. iv) Một quy ước trong lập trình hướng đối tượng giúp cho các thành phần trong phần mềm tăng khả năng tuân thủ nguyên lý Nghịch đảo phụ thuộc là thực hiện việc truy xuất đến các đối tượng thông qua interface của chúng. Điều này sẽ làm cho các thành phần bên trong phần mềm có tính linh động (flexibility) cao, không phải sửa đổi khi thay thế các đối tượng được truy xuất đến bằng đối tượng khác cùng loại. public void doSomething(Car car) { car.run(); car.stop(); } public void doSomething(Vehicle vehicle) { vehicle.run(); vehicle.stop(); }
  • 8. Trong hai đoạn chương trình trên, đoạn chương trình thứ hai vẫn làm việc tốt khi chúng ta thêm vào các đối tượng khác cùng loại với “Car” mà kế thừa từ “Vehicle”. Ý nghĩa Nguyên lý Nghịch đảo phụ thuộc 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 cho phần mềm có tính tái sử dụng cao, linh động và bền vững (robustness) trước những sự thay đổi. Nguyên lý Thay thế Liskov (The Liskov Substitution Principle) Phát biểu Lớp B chỉ nên kế thừa từ lớp A khi và chỉ khi với mọi hàm F thao tác trên các đối tượng của A, cách cư xử (behaviors) của F không thay đổi khi ta thay thế (substitute) các đối tượng của A bằng các đối tượng của B. Nội dung Kế thừa (inheritance) là một trong những tính chất cơ bản của lập trình hướng đối tượng. Đó là khả năng định nghĩa một lớp đối tượng dựa trên các lớp đối tượng đã được định nghĩa trước đó. Các đối tượng của lớp kế thừa có khả năng cư xử (behave) như các đối tượng của lớp cơ sở. Điều này có nghĩa là các đối tượng của lớp kế thừa hoàn toàn có thể thay thế các đối tượng của lớp cơ sở trong những hàm thao tác trên các đối tượng của lớp cơ sở. Chính vì tính chất này mà chúng ta không thể sử dụng kế thừa một cách tùy tiện. Giả sử ta có lớp A và hàm F thao tác trên các đối tượng của A. Để nâng cấp, mở rộng phần mềm, ta cần thêm vào lớp B kế thừa từ A. Nhưng việc thay thế các đối tượng của A bằng các đối tượng của B lại làm cho F cư xử sai lệch so với trước khi thực hiện việc thay thế. Lúc này, để F có thể cư xử không đổi so với trước, ta phải chỉnh sửa lại F. Điều này làm cho F vi phạm nguyên lý Open-Closed. Đoạn chương trình sau cho thấy việc kế thừa tùy tiện chỉ với mục đích tái sử dụng nguy hiểm như thế nào. public class Stack { private ArrayList data; // More data members of stack. public virtual void push(int n) { // Pushes n to stack... } public virtual int pop() { // Pops value from stack... } } public class Queue: Stack { // Data members of Queue. public override void push(int n) { // Pushes n to queue... } public override int pop() {
  • 9. // Pops value from queue... } } public int func(Stack p) { p.push(5); p.push(6); p.push(7); int a = p.pop(); int b = p.pop(); if (a == 7 && b == 6) return a * b; throw new ArgumentException(); } Với mục đích tái sử dụng là một số thuộc tính và phương thức trong “Stack”, chúng ta cho “Queue” kế thừa từ Stack. Xét hàm “func” thao tác trên đối tượng của “Stack”, do “Queue” kế thừa từ “Stack” nên chúng ta hoàn toàn có thể truyền đối tượng của “Queue” vào hàm này. Nhưng cách cư xử của hàm “func” khi thao tác trên các đối tượng của “Stack” và “Queue” là khác nhau. Với các đối tượng của “Stack” hàm func luôn trả về chính xác tích của hai số 7 và 6. Nhưng với các đối tượng của “Queue” hàm func lại luôn gây ra một exception. Để hàm “func” có thể cư xử trên các đối tượng của “Stack” và “Queue” như nhau, chúng ta phải viết lại nó. Điều này làm cho hàm “func” vi phạm nguyên lý Open-Closed. Khi đó ta nói hàm “func” vi phạm nguyên lý Thay thế Liskov. Chú ý i) Nguyên lý Thay thế Liskov có mối liên hệ mật thiết với Nguyên lý Open-Closed. Sự vi phạm nguyên lý Thay thế Liskov sẽ dẫn đến sự vi phạm nguyên lý Open-Closed. Một thực thể phần mềm vi phạm nguyên lý Thay thế Liskov sẽ cư xử khác nhau trên các đối tượng của lớp cơ sở và lớp kế thừa. Để thực thể phần mềm này vẫn có thể làm việc tốt trên các đối tượng của cả lớp cơ sở và lớp kế thừa, chúng ta phải chỉnh sửa lại nó. Điều này dẫn đến vi phạm nguyên lý Open-Closed. ii) Không phải lúc nào tất cả các thực thể trong phần mềm đều có thể tuân thủ nguyên lý Thay thế Liskov. Nhưng mục tiêu của phân tích thiết kế hướng đối tượng là phải làm sao cho số lượng các thực thể tuân thủ nguyên lý là lớn nhất, trong đó ưu tiên các thực thể thường xuyên phải nâng cấp, mở rộng thỏa nguyên lý. iii) Việc tuân thủ nguyên lý Thay thế Liskov của một thực thể phần mềm chỉ mang tính tương đối, phụ thuộc vào ngữ cảnh. Có thể trong ngữ cảnh này, thực thể thỏa nguyên lý, nhưng trong một ngữ cảnh khác, thực thể này không còn tuân thủ nguyên lý nữa. Mục tiêu của phân tích thiết kế hướng đối tượng là phải làm sao cho có nhiều thực thể phần mềm nhất tuân thủ nguyên lý trong ngữ cảnh thường xảy ra nhất của phần mềm, trong đó ưu tiên các thực thể thường xuyên phải nâng cấp, mở rộng thỏa nguyên lý. iv) Quan hệ “IS-A” thường được dù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 {
  • 10. // 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.
  • 11. Ý 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).
  • 12. 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.