1. Trang chủ
  2. » Công Nghệ Thông Tin

Tài liệu công nghệ thông tin - Các nguyên lý cơ bản trong thiết kế HĐT pptx

19 618 1
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 19
Dung lượng 300,5 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

2.Các nguyên lý thiết kế hướng đối tượng - Nguyên lý ‘đóng mở’: một moudle cần “mở” đối với việc phát triển thêm tính năng nhưng phải “đóng” đối với việc sửa đổi mã nguồn - Nguyên lý tha

Trang 1

Các nguyên lý cơ bản trong thiết kế HĐT

Các nguyên lý cơ bản trong thiết kế HĐT (basic object-oriented principles)

1.Vai trò của thiết kế

Thiết kế là 1 công đoạn quan trọng trong qui trình phát triển phần mềm

Thiết kế là bước chuyển tiếp của giai đoạn phân tích và là bước chuẩn bị trước khi chúng ta tiến hành xây dựng phần mềm

Thiết kế là tiến trình mà ở đó xuất hiện mô hình các kiểu mẫu của phần mềm Các mô hình này chính là những nét phác thảo nên phần mềm Nó cho chúng ta biết phần mềm chúng ta đang xây dựng là gì, đã có, đang có và sẽ có những gì

Thiết kế là nơi mà ta có thể trả lời câu hỏi “Liệu phần mềm này có thể chạy được không?” ,

“Phần mềm có thể đáp ứng được các yêu cầu của khách hàng hay không?” mà không cần đợi đến công đoạn phát triển

2.Các nguyên lý thiết kế hướng đối tượng

- Nguyên lý ‘đóng mở’: một moudle cần “mở” đối với việc phát triển thêm tính năng nhưng phải

“đóng” đối với việc sửa đổi mã nguồn

- Nguyên lý thay thế Liskov: Các chức năng của hệ thống vẫn thực hiện đúng đắn nếu ta htay bất

kì một lớp đối tượng nào bằng đối tượng kế thừa

- Nguyên lý nghịch đảo phụ thuộc: phụ thuộc vào mức trừu tượng, không phụ thuộc vào mức chi tiết

- Nguyên lý phân tách giao diện: nên có nhiều giao diện đặc thù với bên ngoài hơn là chỉ có một giao diện dùng chung cho một mục đích

Theo tác giả thì mọi nguyên lý trong lập trình hướng đối tượng đều quy vào một nguyên lý duy

Trang 2

nhất là nguyên lý đóng mở (Open-Closed Principle) Do đó đầu tiên sẽ giới thiệu với các bạn về nguyên lý đóng mở Các nguyên lý sau sẽ làm rõ hơn làm cách nào để đạt được yêu cầu như nguyên lý đóng mở đề ra

Phát biểu nguyên lý Đóng - Mở:

“Các thực thể phần mềm (lớp, đơn thể, hàm, …) nên (được xây dựng theo hướng) mở cho việc mở rộng và đóng cho việc sửa đổi”.

Nguyên văn tiếng Anh:

“SOFTWARE ENTITIES(CLASSES,MODULES,FUNCTIONS,ETC.)SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.”

Điểm mấu chốt nhất khi xây dựng phần mềm là “yêu cầu (chức năng) của phần mềm luôn luôn thay đổi” Sự thay đổi này có thể là khách quan (vd: do nhu cầu công việc cần bổ sung thêm chức năng mới) hoặc chủ quan (vd: chuyên viên lấy yêu cầu hoặc khách hàng không mô tả yêu cầu phần mềm rõ ràng) Như vậy vấn đề đặt ra là làm thế nào viết một phần mềm thay đổi “dễ” chứ không phải là viết một phần mềm mà không hề thay đổi Nguyên lý đóng mở được đưa ra nhằm phục vụ cho mục đích này

Có hai vế trong nguyên lý này:

1 “Mở cho việc mở rộng”: có nghĩa rằng hoạt động của thực thể phần mềm (lớp, đơn thể, hàm,

…) có thể được mở rộng Chúng ta có thể tạo ra thực thể hoạt động theo những cách mới và khác hẳn khi yêu cầu của ứng dụng thay đổi hoặc để thỏa mãn nhu cầu của ứng dụng mới

2 “Đóng cho việc sửa đổi”: có nghĩa rằng đoạn mã (code) của thực thể này không bị xâm phạm Không ai được phép thay đổi đoạn mã của thực thể

Có vẻ như hai vế này mâu thuẫn lẫn nhau Bởi vì khi chúng ta muốn thêm hoặc sửa đổi yêu cầu thì có vẻ “chắc chắn” chúng ta phải sửa đổi đoạn mã cũ

Để làm rõ hơn hai vế trên, chúng ta xét ví dụ chương trình Draw Chương trình Draw là một chương trình tương tự như Paint trong Windows Nó cho phép chúng ta vẽ các đối tượng hình học ra màn hình Bỏ qua các vấn đề liên quan đến giao diện người dùng, chúng ta chỉ tập trung đến thao tác vẽ (draw) của các đối tượng

Giả sử yêu cầu ban đầu của chương trình Draw là chỉ thao tác trên hai loại đối tượng là hình tròn

và hình vuông Sử dụng phương pháp lập trình cấu trúc (structured programming) (hay còn gọi phương pháp lập trình hướng thủ tục – procedural programming), chương trình phác thảo sơ lược

sẽ có dạng như sau (sử dụng ngôn ngữ C++):

PHP Code:

enum ShapeType { circle , square };

struct Shape

{

ShapeType itsType ;

Trang 3

struct Circle

{

ShapeType itsType ;

double itsRadius ;

Point itsCenter ;

};

struct Square

{

ShapeType itsType ;

double itsSide ;

Point itsTopLeft ;

};

// không cần quan tâm chi tiết đến cài đặt hai hàm này

void DrawSquare ( struct Square *);

void DrawCircle ( struct Circle *);

typedef struct Shape * ShapePointer ;

void DrawAllShapes ( ShapePointer list[], int n )

{

int i ;

for ( i 0 ; i n ; i ++)

{

struct Shape * s = list[ i ];

switch ( s -> itsType )

{

case square :

DrawSquare (( struct Square *) s );

break;

case circle :

DrawCircle (( struct Circle *) s );

break;

}

}

}

Đoạn mã ở trên có thể dễ dàng đọc hiểu Trong đó hàm DrawAllShapes có nhiệm vụ vẽ các đối tượng hình học (hình tròn, hình vuông) ra màn hình Tham số của hàm DrawAllShapes là một mảng các con trỏ chứa địa chỉ của các đối tượng hình học cần được vẽ Để đạt được điều đó, hàm DrawAllShapes đến phiên nó lại cần sự trợ giúp của hai hàm vẽ cụ thể cho hai loại đối tượng hình học là hàm DrawCircle và DrawSquare Để gọi được hai hàm này, hàm DrawAllShapes cần phải xác định đối tượng hiện tại đang thao tác là đối tượng nào thông qua biến thành viên itsType của từng đối tượng Có vẻ chương trình Draw đã được hoàn thành và đúng với yêu cầu đề ra Vấn đề sẽ xuất hiện khi chúng ta muốn vẽ thêm một đối tượng khác, như hình tam giác chẳng hạn Lúc này, hàm DrawAllShapes cần phải xử lý thêm một trường hợp nữa là hình tam giác Đoạn mã thêm vào và sửa đối sẽ như sau:

PHP Code:

enum ShapeType { circle , square , triangle };//kiểu dữ liệu liệu kê

struct Triangle

{

Trang 4

ShapeType itsType ;

Point itsVertices [ ];

};

// không cần quan tâm chi tiết đến cài đặt hàm này

void DrawTriangle ( struct Triangle *);

void DrawAllShapes ( ShapePointer list[], int n )

{

int i ;

for ( i 0 ; i n ; i ++)

{

struct Shape * s = list[ i ];

switch ( s -> itsType )

{

case square :

DrawSquare (( struct Square *) s );

break;

case circle :

DrawCircle (( struct Circle *) s );

break;

// thêm vào

case triangle :

DrawTriangle (( struct Triangle *) s );

break;

}

}

}

Để ý trong trường hợp này, khi một yêu cầu mới phát sinh (vẽ hình tam giác), thì đoạn mã của hàm DrawAllShapes đã bị thay đổi Bản thiết kế chương trình Draw của chúng ta đã vi phạm nguyên lý đóng mở

Vậy bản thiết kế chương trình Draw nên như thế nào?

Hai kỹ thuật chính để đạt được nguyên lý Đóng - Mở là sự trừu tượng (abstraction) và tính đa hình (đa xạ : polymorphism) Các bạn có thể tự tìm hiểu hai kỹ thuật trừu tượng hóa và đa

hình, vốn là hai kỹ thuật mà bất cứ một ngôn ngữ lập trình hướng đối tượng, bao gồm C++, phải

hỗ trợ Tôi không trình bày chi tiết hai kỹ thuật trên mà chỉ trình bày sơ lược theo ví dụ thiết kế Draw mà chúng ta đang hướng đến

Chương trình Draw ở trên có thể mô hình như sau:

Trang 5

Nghĩa là hàm DrawAllShapes sử dụng trực tiếp (được thể hiện bằng đoạn thẳng có dấu mũi tên mảnh) hai đối tượng (hai lớp) Circle và Square, tương ứng là hình tròn và hình vuông

Chúng ta sẽ trừu tượng hóa quan hệ này bằng cách tạo ra một đối tượng gọi là hình (Shape) Một cách cảm tính chúng ta có thể thấy một đối tượng hình tròn hoặc hình vuông hoặc hình tam giác đều là một đối tượng hình Hàm DrawAllShapes thay vì thao tác trực tiếp trên các đối tượng hình tròn và hình vuông sẽ thao tác trên các đối tượng hình chung chung mà chúng ta đã trừu tượng hóa Mô hình chương trình Draw sẽ trở thành như sau:

Các lớp Circle, Square sẽ được kế thừa (được thể hiện bằng đoạn thẳng có dấu mũi tên đậm) từ lớp Shape Đoạn mã chương trình Draw cho mô hình thiết kế mới sẽ như sau:

PHP Code:

class Shape

{

public:

// hàm thuần ảo (pure virtual)

virtual void Draw () const= 0

};

class Square : public Shape

{

protected:

double itsSide ;

Point itsTopLeft ;

public:

Trang 6

// không cần quan tâm chi tiết cài đặt hàm này

virtual void Draw () const;

};

class Circle : public Shape

{

protected:

double itsRadius ;

Point itsCenter ;

public:

// không cần quan tâm chi tiết cài đặt hàm này

virtual void Draw () const;

};

void DrawAllShape ( set < Shape *>& list)

{

for( iterator < Shape *> i (list); i ; i ++)

(* i ) -> Draw ();

};

Qua đoạn mã chương trình Draw mới, có thể thấy hàm DrawAllShapes không quan tâm chi tiết đến từng đối tượng hình cụ thể như là hình tròn hay hình vuông (không có câu lệnh if), mà nó chỉ quan tâm đến sự trừu tượng của các đối tượng hình này – Shape Nhờ cơ chế đa hình (đa xạ) mà hàm Draw của lớp Shape sẽ được liên kết với hàm Draw của lớp Circle hoặc Square tùy thuộc vào đối tượng hiện tại thuộc lớp Circle hay Square Trong đoạn chương trình trên cũng xuất hiện một khái niệm mà các bạn ít quen thuộc là iterator và set, các bạn có thể tự tìm hiểu thêm trong thư viện STL đi kèm với C++

Quay trở lại với chương trình Draw của chúng ta, nếu muốn chương trình vẽ thêm đối tượng tam giác thì chúng ta chỉ việc thêm vào lớp Triangle, được dẫn xuất (thừa kế) từ lớp Shape

PHP Code:

class Shape

{

public:

// hàm thuần ảo (pure virtual)

virtual void Draw () const= 0

};

class Square : public Shape

{

protected:

double itsSide ;

Point itsTopLeft ;

public:

// không cần quan tâm chi tiết cài đặt hàm này

Trang 7

virtual void Draw () const;

};

class Circle : public Shape

{

protected:

double itsRadius ;

Point itsCenter ;

public:

// không cần quan tâm chi tiết cài đặt hàm này

virtual void Draw () const;

};

class Triangle : public Shape

{

protected:

Point vertices [ ];

public:

// không cần quan tâm chi tiết cài đặt hàm này

virtual void Draw () const;

};

void DrawAllShape ( set < Shape *>& list)

{

for( iterator < Shape *> i (list); i ; i ++)

(* i ) -> Draw ();

};

Có thể thấy, chúng ta chỉ cần thêm mới vào lớp Triangle, hàm DrawAllShapes, cũng như tất cả các thành phần đoạn mã đã có của chương trình Draw, không hề thay đổi Bản thiết kế mới của chương trình Draw thỏa mãn nguyên lý Đóng – Mở

Open-closed là nguyên li trung tâm, rất quan trọng trong thiết kế hướng đối tượng vì chính nguyên lí này làm cho lập trình hướng đối tượng có tính tái sử dụng (reusability) và dễ bảo trì (maintainability)

Tham khảo thêm ở đây và ở đây

Như đã đề cập ở trên hai kỹ thuật quan trọng để đạt được nguyên lý đóng mở là trừu tượng hóa

và tính đa hình Trong C++, tính đa hình được thể hiện thông qua sự thừa kế (inheritance) Vậy khi nào thì một lớp A nào đó nên được thừa kế từ lớp B đã có?

Nguyên lý thay thế Liskov

Nếu xem nguyên lý Mở - Đóng là nguyên lý cơ sở quan trọng nhất của lập trình và thiết kết theo

hướng đối tượng, thì nguyên lý Thay thế Liskov là một phương tiện để chúng ta kiểm tra xem

chương trình hoặc bản thiết kế của chúng ta có thoả nguyên lý Mở - Đóng hay không.

Nếu nguyên lí này bị vi phạm, function có sử dụng reference hay pointer tới object của lớp cha phải kiểm tra kiểu của object để đảm bảo chương trình có thể chạy đúng, và việc này vi phạm nguyên lí open-closed nhắc đến ở trên

Tham khảo thêm ở đây và ở đây

Trước khi đi vào nguyên lý chúng ta xét một chương trình ví dụ, cũng liên quan đến các đối tượng hình vẽ như chúng ta đã đề cập trong chương trình Draw, nhưng được giản lược đi nhiều

Trang 8

chỉ để đủ cho việc minh họa nguyên lý Thay thế Liskov Cụ thể chúng ta xét lớp Rectangle mô tả đối tượng hình chữ nhật và một hàm f thao tác trên đối tượng lớp Rectangle

PHP Code:

class Rectangle

{

public:

void SetWidth ( double w ) { itsWidth = ;}

void SetHeight ( double h ) { itsHeight = ;}

double GetHeight () const {return itsHeight ;}

double GetWidth () const {return itsWidth ;}

private:

double itsWidth ;

double itsHeight ;

};

// hàm thao tác trên đối tượng Rectangle&

void f ( Rectangle & r

{

r SetWidth ( 32 );

}

Đoạn mã đã giải thích rõ ràng công dụng của lớp Rectangle, cũng như của hàm f

Phát biểu nguyên lý:

Các hàm mà sử dụng con trỏ hoặc tham chiếu đến các (đối tượng) lớp cơ sở cũng phải có thể sử dụng các đối tượng của các lớp dẫn xuất mà không cần biết chúng

Nguyên văn tiếng Anh:

FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT

Để hiểu nguyên lý này chúng ta thử xét những ví dụ vi phạm Giả sử chương trình Draw mở rộng thao tác trên những đối tượng hình vuông, lớp Square Theo bạn, chúng ta nên tạo mới lớp hình vuông hay kế thừa từ lớp Rectangle Để trả lời câu hỏi này thì theo như phần lớn các bạn đã được học, các bạn cần trả lời câu hỏi là “Một đối tượng hình vuông có phải là một (IS A) đối tượng hình chữ nhật hay không?” Nếu câu trả lời là có, thì lớp hình vuông là lớp kế thừa từ lớp hình chữ nhật và ngược lại Trong trường hợp này, dĩ nhiên câu trả lời là có Vậy chúng ta sẽ cho lớp hình vuông kế thừa từ lớp hình chữ nhật và xem điều gì sẽ xảy ra Đoạn mã của lớp Square có dạng như sau:

PHP Code:

class Square : public Rectangle

{

public:

void Square :: SetWidth ( double w )

{

Rectangle :: SetWidth ( );

Rectangle :: SetHeight ( );

}

void Square :: SetHeight ( double h )

{

Rectangle :: SetHeight ( );

Trang 9

Rectangle :: SetWidth ( );

}

};

Lớp Square được kế thừa từ lớp Rectangle, và do đặc điểm của hình vuông là hai cạnh bằng nhau, nên khi đặt chiều rộng thì chúng ta cũng phải đặt chiều dài và ngược lại Bỏ qua lý do tốn

bộ nhớ (do phải lưu cả chiều dài và chiều rộng), chúng ta xét về logic thực hiện chương trình Chúng ta thử tạo ra hai đối tượng hình vuông và hình chữ nhật và gọi hàm f thao tác trên hai đối tượng này theo hàm main như sau:

PHP Code:

int main ()

{

Rectangle r ;

Square s ;

f r ); // thực hiện đúng

f s ); // thực hiện sai vì hàm SetWidth là hàm của hình chữ nhật

return 0

}

Nếu chúng ta truyền vào một đối tượng Rectangle (r) thì hàm f thực hiện đúng như mong đợi Nhưng nếu chúng ta truyền vào một đối tượng Square (s) thì hàm f thực hiện sai vì câu lệnh r.SetWidth(32) sẽ gọi hàm SetWidth của lớp Rectangle và do đó gây ra vi phạm ràng buộc là chiều dài và chiều rộng của đối tượng s phải bằng nhau Trong trường hợp này, hàm f đã vi phạm nguyên lý Thay thế Liskov Nó họat động tốt trên đối tượng truyền vào thuộc lớp cơ sở (lớp Rectanlge) nhưng không họat động tốt trên đối tượng truyền vào thuộc lớp dẫn xuất (lớp

Square)

Giải pháp khắc phục rất đơn giản chúng ta sẽ thay đổi hai hàm thuộc lớp Rectangle thành hàm ảo (virtual function) và sử dụng cơ chế đa xạ Để ý rằng, khi chương trình Draw vi phạm nguyên lý Thay thế Liskov thì nó cũng vi phạm nguyên lý Mở - Đóng (vì phải chỉnh sửa đoạn mã các thực thể đã có)

PHP Code:

class Rectangle {

public:

// đổi thành hàm ảo (virtual)

virtual void SetWidth ( double w )

{ itsWidth = ;}

// đổi thành hàm ảo (virtual)

virtual void SetHeight ( double h )

{ itsHeight = ;}

double GetHeight () const

{return itsHeight ;}

double GetWidth () const

{return itsWidth ;}

private:

double itsHeight ;

double itsWidth ;

};

class Square : public Rectangle

{

Trang 10

void Square :: SetWidth ( double w )

{

Rectangle :: SetWidth ( );

Rectangle :: SetHeight ( );

}

void Square :: SetHeight ( double h )

{

Rectangle :: SetHeight ( );

Rectangle :: SetWidth ( );

}

};

Vấn đề đã được giải quyết xong, hàm f bây giờ có thể hoạt động tốt cho cả đối tượng truyền vào thuộc lớp Rectangle lẫn đối tượng truyền vào thuộc lớp Square Chương trình bây giờ thỏa mãn nguyên lý Thay thế Liskov

Để minh họa tiếp tục, chúng ta xét hàm g như sau Hàm g có vai trò như một hàm test bảo đảm rằng thao tác thực hiện hai hàm SetWidth và SetHeight của lớp Rectangle phải hoàn toàn đúng đắn

PHP Code:

void g ( Rectangle & r

{

r SetWidth ( );

r SetHeight ( );

assert ( GetWidth ()* r GetHeight ())== 20 );

}

Dễ thấy rằng, hàm g hoạt động tốt nếu chúng ta truyền vào một đối tượng Rectangle (r), assert thành công, nhưng sẽ không hoạt động tốt nếu chúng ta truyền vào một đối tượng Square (s), assert không thành công (Các bạn tham khảo thêm hàm assert, nó được dùng chủ yếu cho mục đích debug và test chương trình) Trong trường hợp này, chúng ta kết luận chương trình không thỏa mãn nguyên lý Thay thế Liskov Vì hàm g hoạt động tốt trên các đối tượng lớp cơ sở (Rectangle) nhưng không hoạt động tốt trên các đối tượng lớp dẫn xuất (Square).

Vậy nguyên nhân là do đâu? Lý do chính ở đây, là lớp Square không nên kế thừa từ lớp Rectangle Và

việc trả lời cho câu hỏi: “Đối tượng hình vuông có phải là một đối tượng hình chữ nhật hay không?”

cho đáp án là “có” chỉ là điều kiện cần cho việc quyết định lớp Square (hình vuông) có nên kế thừa từ lớp Rectangle (hình chữ nhật) hay không Điều kiện đủ cần phải xét là nó có thỏa nguyên lý Liskov hay không

Lưu ý rằng việc bảo đảm nguyên lý Thay thế Liskov cho mọi hàm, mọi thực thể trong phần mềm là rất khó Tuy nhiên việc cố gắng thực hiện đúng theo nguyên lý Thay thế Liskov sẽ giúp ích cho việc mở rộng

và bảo trì phần mềm Bởi vì nếu vi phạm nguyên lý Thay thế Liskov thì tất yếu sẽ vi phạm nguyên lý

Mở - Đóng (cụ thể là tính Đóng).

Nguyên lý đảo phụ thuộc (Dependency Inversion Principle)

Phát bi ể u nguyên lý :

A Các đơn thể cấp cao không nên phụ thuộc vào các đơn thể cấp thấp Cả hai nên phụ thuộc vào

Ngày đăng: 11/07/2014, 12:20

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w