Các thực thể phần mềm nhƣ các lớp, module, hàm... nên đƣợc xây dựng theo hƣớng mở cho việc mở rộng nhƣng đóng đối với việc sửa đổi.
Trong một dự án phần mềm thƣờng bao gồm các thực thể khác nhau. Các thực thể này không tách biệt nhau mà có sự gắn kết, tƣơng tác với nhau theo những cách thức trong những tình huống nhất định. Một trong những yêu cầu trong thiết kế phần mềm là phải tính đến khả năng bảo trì, mở rộng của phần mềm sau này, đáp ứng các nhu cầu và điều kiện thay đổi. Tuy nhiên, một bất lợi cho sự gắn kết chặt chẽ giữa các thực thể phần mềm là, khi ta nâng cấp, mở rộng một thực thể nào đó, thì các thực thể liên quan cũng phải đƣợc nâng cấp, mở rộng cho phù hợp. Với sự phát triển nhanh chóng của việc ứng dụng công nghệ thông tin và các điều kiện thay đổi, việc nâng cấp, mở rộng phần mềm sẽ diễn ra là điều khó tránh khỏi.
Do đó để có thể bảo trì, nâng cấp phần mềm đƣợc dễ dàng, trong quá trình phân tích thiết kế, các kỹ sƣ phần mềm cần xây dựng theo nguyên lý đóng mở. Nghĩa là cần xây dựng lên một kiến trúc phần mềm sao cho nó có thể mở rộng, thay đổi hay thêm các chức năng cần thiết một cách dễ dàng, nhƣng sẽ hạn chế tối đa việc sửa đổi, đặc biệt là việc sửa đổi các thực thể khác liên quan. Đây là một điều rất quan trọng trong điều kiện phần mềm ngày càng lớn và phức tạp.
50 Ở đây có 2 vấn đề cần chú ý, đó là:
Mở cho việc mở rộng: nghĩa là việc thiết kế phần mềm, các thực thể phải đảm bảo cho khả năng mở rộng của nó. Ta có thể thêm vào các thực thể mới để thực hiện các chức năng theo yêu cầu. Các thực thể mới thêm vào đƣợc thừa kế từ các thực thể đã có hoặc có tƣơng tác với các thực thể đã tồn tại trên cơ sở coi các thực thể đã có là một đối tƣợng đã đƣợc đóng gói.
Đóng cho việc sửa đổi: ta có thể thêm vào các thực thể mới đáp ứng nhu cầu, tuy nhiên hạn chế tối đa việc thay đổi các thực thể đã tồn tại, nhất là việc sửa đổi mã nguồn của phần mềm trƣớc đó. Hay nói cách khác, không nên tác động vào mã nguồn của các thực thể đã tồn tại.
Để minh họa cho nguyên lý trên, ta có thể xét ví dụ đơn giản sau: giả sử chƣơng trình ban đầu thực hiện chức năng vẽ các đối tƣợng hình học là đƣờng thẳng và hình chữ nhật. Sử dụng phƣơng pháp lập trình cấu trúc, ta có thể mã hóa đoạn chƣơng trình nhƣ sau:
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(){
// Các lệnh vẽ đoạn thẳng }
}
public class Rectangle: Shape{
public override ShapeType getType(){ return ShapeType.RECTANGLE; }
public void drawRectangle(){
// Các lệnh vẽ hình chữ nhật }
}
public void draw(ArrayList shapeList){ Line objLine;
Rectangle objRectangle;
foreach (Shape s in shapeList) switch (s.getType()){ case ShapeType.LINE: objLine = (Line)s; objLine.drawLine(); break; case ShapeType.RECTANGLE: objRectangle = (Rectangle)s;
51
objRectangle.drawRectangle(); }
}
Đoạn chƣơng trình trên đã đáp ứng đƣợc yêu cầu là vẽ đƣờng thẳng và đƣờng tròn ra màn hình. Bây giờ giả sử cần nâng cấp, mở rộng chƣơng trình, giả sử ta thêm vào chƣơng trình đoạn mã vẽ đƣờng tròn. Khi đó ta phải chỉnh sửa lại hàm draw, bằng cách trong khối lệnh switch, ta thêm vào một trƣờng hợp của đƣờng tròn. Tuy nhiên trong nhiều trƣờng hợp, việc chỉnh sửa hàm draw này dẫn đến việc phải chỉnh sửa các hàm, thực thể khác liên quan. Do đó dẫn đến việc vi phạm nguyên lý đóng mở.
Để đoạn chƣơng trình trên có thể tuân thủ nguyên lý đóng mở, chúng ta sẽ sử dụng tính đa hình của lập trình hƣớng đối tƣợng. Và chƣơng trình trên sẽ đƣợc điều chỉnh lại nhƣ sau:
public abstract class Shape{
public abstract void draw(); }
public class Line: Shape{
public override void draw(){ // Các lệnh vẽ đoạn thẳng }
}
public class Rectangle: Shape{ public override void draw(){
// Các lệnh vẽ hình chữ nhật }
}
class Circle: Shape{
public override void draw(){ // Các lệnh vẽ hình tròn }
}
public void draw(ArrayList shapeList){ foreach (Shape s in shapeList)
s.draw(); }
Trong đoạn chƣơng trình trên, khi cần thêm một hình mới vào, ta chỉ cần thêm lớp đối tƣợng cho hình đó thừa kế từ lớp trừu tƣợng Shape mà không cần phải chỉnh sửa lại hàm draw.
Việc thiết kế phần mềm cần phải đƣợc tuân thủ nguyên lý đóng mở. Tuy nhiên trong đó, không phải lúc nào các thực thể phần mềm cũng tuân thủ nguyên lý. Mục tiêu của thiết kế là tối ƣu việc tuân thủ nguyên lý thiết kế, cân đối giữa việc tuân thủ nguyên lý và việc kết dính giữa các thực thể. Nghĩa là làm cho số các thực thể tuân thủ nguyên lý là lớn nhất, đặc biệt ƣu tiên cho các thực thể mà thƣờng xuyên hay khả năng mở rộng nhiều.
52
Việc áp dụng, tuân thủ nguyên lý cũng chỉ có tính tƣơng đối, tùy thuộc vào hoàn cảnh áp dụng. Trong tình huống này, thực thể tuân theo nguyên lý đóng mở, nhƣng trong những tình huống khác lại không còn tuân thủ nguyên lý nữa. Tuy nhiên mục tiêu của thiết kế phần mềm hƣớng đối tƣợng là làm sao cho số lƣợng thực thể tuân thủ nguyên là nhiều nhất, trong những tình huống thƣờng gặp, nhất là các thực thể thƣờng xuyên đƣợc nâng cấp, mở rộng.
Một đặc tính quan trọng của phân tích thiết kế hƣớng đối tƣợng mà giúp cho tăng khả năng tuân thủ nguyên lý đóng mở của các thực thể phần mềm, đó là tính đóng gói. Các đối tƣợng quản lý và chịu trách nhiệm trên các thông tin của mình. Do đó hạn chế khả năng kết dính giữa các đối tƣợng. Trong trƣờng hợp tối ƣu, các thuộc tính của đối tƣợng đƣợc đặt là private. Khi đó việc thay đổi các thuộc tính của đối tƣợng chỉ đƣợc thông qua các thao tác của chính các đối tƣợng đó mà các đối tƣợng khác không thể truy cấp đƣợc các thuộc tính này.