PDA

View Full Version : Tiếp tục OOP nè!



xp2002_
30-09-2002, 14:38
:arrow: Bà con ới ời, tớ cũng muốn tham gia chủ đề này cùng các cậu nữa nhưng tại đang lười nên viết chưa xong lần sau tớ sẽ tiếp tực nhé! Giờ thì xem tạm nhiêu thôi.


Lập trình hướng đối tượng trong
Turbo Pascal

* * *
I. Từ thực tế bài toán quản lý:
Phát sinh từ yêu cầu việc quản lý một nhóm đối tượng có cùng một số tính chất tương tự nhau. Ví dụ, để quản lý sinh viên trong trường, LTV (lập trình viên) sẽ xây dựng một kiểu đối tượng chung là SV có các thuộc tính: HT, MSSV, DC,... và một số hàm để thao tác trên kiểu đối tượng này. Công việc này quá tầm thường với cấu trúc record trong TPascal. Nhưng sau đó lại phát sinh việc quản lý các cán bộ có cấu trúc tương tự nhưng có thêm thuộc tính LCB (lương cơ bản), LTV lại xây dựng thêm một kiểu đối tượng mới tương tự. Như vậy xảy ra việc trùng lặp dữ liệu (vô ích, có khi tạo thêm sự rối rắm trong việc định nghĩa tác vụ).
Để giải quyết vấn đề trên, TPascal hỗ trợ vấn đề lập trình hướng đối tượng, mở ra hướng đi mới cho dân lập trình. Nó cho phép định nghĩa một “lớp” chung bao gồm các thuộc tính và các tác vụ (hàm, thủ tục) thao tác trên thuộc tính đó. Trên cơ sớ đó, muốn tạo thêm lớp mới có thêm thuộc tính ta chỉ cần khai báo “thêm” mà thôi.

Rõ ràng vấn đề trên cấu trúc record (hoặc struct trong C++) không thể đáp ứng được yêu cầu. TPascal “phóng khoáng” cho ta thêm một từ khóa mới, đó là Object (tương ứng với C++ là từ khoá class). Object đã mở ra một hướng mới, giải quyết bế tắc cho những bài toán đòi hỏi mô hình dữ liệu hơn là tác vụ, đó chính là hướng “lập trình hướng đối tượng”. (Lưu ý trong C++ phân biệt chữ hoa và thường, do đó class là từ khoá, còn Class, CLASS sẽ không là từ khoá).

II. Khái niệm cơ bản:
Trước đây, khi vào lập trình, việc áp dụng thuật toán vào dữ liệu được đặt nặng, được xem là quan trọng, còn việc tổ chức dữ liệu bị xem nhẹ, bị xem là “con ghẻ”, cũng chính vì vậy đã đi vào “ngỏ cụt” với những dạng bài toán nêu trên.
Hiện nay trong kỷ nguyên mới, kỷ nguyên “lập trình hướng đối tượng”, công thức cổ điển:
Cấu trúc dữ liệu + thuật toán = chương trình
đã trở nên “xưa rồi Diễm”, thay vào đó là công thức mới, làm nổi bật tầm quan trọng của việc tổ chức dữ liệu:
Dữ liệu + hàm = đối tượng
Mục này ta sẽ làm quen với các khái niệm sau trước khi vào nội dung chính:
+ Lớp: tập hợp một số thuộc tính chung và các thao tác (hàm, thủ tục) đối với các thuộc tính đó.
+ Lớp cha (lớp trên, lớp tiền bối) : lớp mà được một lớp khác thừa hưởng thuộc tính của mình.
+ Lớp con (lớp dưới, lớp hậu bối): lớp thừa hưởng các thuộc tính từ một lớp cha nào đấy. Nó sẽ có thuộc tính của lớp cha lẫn các thuộc tính của riêng nó. Trường hợp lớp con có thêm lớp con sẽ sinh ra lớp … cháu (hoặc chắt, chít…).
+ Đối tượng: chính là “thể hiện” của một lớp. Hay nói cách khác đối tượng chính là một trong những sản phẩm được “đúc” từ cái khuôn là “lớp”.
+ Thừa kế: tính chất thừa hưởng những gì có từ một lớp cha của một lớp con nào đấy.
Ví dụ:
Trong H1, lớp Person là lớp tiền bối của lớp SV và lớp CB. Khi đó 2 lớp này sẽ là lớp con của lớp Person, nó có thêm các thuộc tính riêng của nó ngoài việc thừa hưởng HT, MS, DC, GT từ lớp Person. Khi ta khai báo Lan, Nam là SV thì Lan và Nam là hai đối tượng thể hiện của lớp SV, hoặc Khang là CB cũng là một đối tượng.

III. Giới thiệu lý thuyết:
1. Định nghĩa một lớp:
Đối với cấu trúc record (hoặc struct trong C++) khi định nghĩa các hàm tác động vào sẽ rời rạc, nằm “vất vưởng” nơi nào đấy, sẽ rất khó khi quản lý, sửa chữa. Còn khi làm việc với Object, nó cho phép nhóm chúng lại “một khối” tạo nên một lớp, tiện thể đặt cho nó một cái tên.
Ví dụ:
Type Person = Object
HT : string[20];
MS : string[7];
DC : string[40];
GT : boolean;
Procedure Print;
End; // khai báo lớp Person

Cú pháp khởi tạo một lớp nhìn chung (hay nhìn riêng) đều giống khai báo record, chỉ khác ở chổ thủ tục Print được bao chung trong Object (khỏi sợ rơi rớt). Sau đó muốn định nghĩa thủ tục Print sẽ làm gì ta có thể định nghĩa bên ngoài Object nhưng phải có tên lớp phía trước, ví dụ: Person.Print. Như vậy, nếu một lớp khác cũng có thủ tục Print thì nó sẽ khác đi, hay nói cách khác, mỗi chương trình con đều được “dán tem, đóng dấu” bởi tên lớp, khỏi lẫn lộn.
Ví dụ:
Procedure Person.Print;
Begin

// thao tác trên các thuộc tính (các trường) HT, MS, DC, GT…

End;
Sau này, muốn gọi Print của Person ta chỉ cần gọi: <tên đối tượng>.Print;
Ví dụ: A.Print; B.Print;
Muốn khai báo một lớp con của Person ta sử dụng cú pháp:
Type <tên lớp con> = Object(<tên lớp cha>);
… // những gì có thêm trong lớp con
End;
Ví dụ:
Type SV = Object(Person)
TCTL : word;
DTBTL : real;
Function HB(DTB : real) : boolean;
End;
Ở đây lớp SV thừa hưởng “hàm” (có thể là thủ tục) Print từ Person cho nên không in ra được TCTL và DTBTL, như vậy ta phải tạo một Print khác để thêm vào thôi. Khi đó trong lớp SV, ta khai báo Print (lưu ý: trùng tên với thủ tục trong lớp cha) rồi định nghĩa lại khác đi, và khi ta gọi X.Print (X là đối tượng SV) thì Print của SV sẽ được gọi. Ơ đây ta đã định nghĩa lại lệnh ở một lớp con bằng cách khai báo lệnh mới trùng tên lệnh củ trong lớp tiền bối, và lệnh “Person.Print” đã bị “SV.Print” che mất, cơ chế này được gọi là tái định nghĩa hàm trong C++.
Tóm lại:
+ Khai báo một lớp trong TPascal:
Type <tên lớp> = Object [<tên lớp thừa kế>]
[public]
// các dữ liệu, hàm (thủ tục) dùng chung
private
// các dữ liệu, hàm (thủ tục) dùng riêng
End;
+ Định nghĩa lệnh:
Procedure [Function] <tên lớp>.<lệnh của lớp>[<danh sách đối số>] [:<kiểu trả về của hàm>];
Begin
// định nghĩa tác vụ ở đây;
End;
+ Khai báo một lớp trong C++:
class <tên lớp> [:<tên các lớp thừa kế>]
{
[private:]
// các dữ liệu thành viên riêng
public:
// các dữ liệu thành viên chung
};
+ Định nghĩa lệnh trong C++:
<kiểu trả về của hàm> <tên lớp>::<tên hàm>[<danh sách đối số>]
{
// định nghĩa lệnh
};
Lưu ý: trong C++ không có thủ tục chỉ có hàm, tương ứng với thủ tục là hàm không trả về cái gì cả, khi đó kiểu trả về là “void”.

* Ý nghĩa Private - Public:
Hạn chế của cấu trúc record (hoặc struct) là các lệnh bên ngoài có thể truy cập một cách trực tiếp đến các trường dữ liệu, do đó có thể nó sẽ bị thay đổi, không có cách nào nghiêm cấm việc thay đổi dữ liệu trong cấu trúc loại này.
Trong khi đó, thiết kế một lớp, do có thêm các lệnh (hàm, thủ tục) riêng của nó dùng để xử lý dữ liệu riêng của nó, cho nên sẽ giải quyết triệt để vấn đề trên. Ta sẽ dùng một từ khoá mới (lại mới) là Private, có hiệu lực che dấu thông tin, và những dữ liệu hay hàm khai báo sau nó sẽ không truy xuất được từ bên ngoài lớp. Tương tụ ta có Public với ý nghĩa trái ngược, và mặc nhiên là Public (điều này ngược lại trong C++). Khi muốn cho “xem” dữ liệu ta chỉ cần viết một hàm trả về là xong, nên sẽ không có chuyện lệnh bên ngoài “xía” vào làm thay đổi nội bộ được.

Ví dụ: khai báo một ngăn xếp kiểu hướng đối tượng:
Type ElementType = …
Type Stack = Object
private // phần riêng
m : array[1..100] of ElementType;
top : integer;
public //phần chung
Procedure MakeNull;
Function Empty : boolean;
Procedure Push(x : ElementType);
Procedure Pop;

End;
Procedure Stack.MakeNull;
Begin
top := 0;
End;
Function Stack.Empty : boolean;
Begin
Empty := (top = 0);
End;
Procedure Stack.Push(x : ElemetnType);
Begin
If not Empty then
Begin
Inc(top);
m[top] := x;
End
Else writeln(‘Loi! Ngan xep day.’);
End;
Tương ứng trong C++: (lưu ý in hoa và thường)
enum BOOL {TRUE = 1, FALSE = 0};
// do trong C++ không có kiểu boolean nên ta phải định nghĩa kiểu BOOL
// lưu ý BOOL khác với Bool, bool
typedef ElementType …;
class Stack
{ // mặc định là phần riêng
ElementType m[100];
int top;
public: // phần chung
void MakeNull(); // hàm không trả về
enum BOOL Empty(); // hàm kiểm tra Stack rỗng
void Push(ElementType x);
void Pop();

};
void Stack::MakeNull()
{
top = 0;
};
enum BOOL Empty()
{
if (top == 0) return(TRUE);
else return(FALSE);
// hoặc return((top == 0) ? TRUE : FALSE);
};
void Push(Elementtype x)
{
if (!Empty()) m[top++] = x;
else cout <<”Loi! Ngan xep da day.”;
};

* Khai báo đối tượng:
Var s1, s2 : Stack;
Tương ứng với khai báo:
Stack s1, s2; trong C++

2. Thừa kế:
Tính thừa kế ra đời đã đưa ra một hướng đi mới cho bài toán quản lý nêu trên, tránh đi sự trùng lập dữ liệu và dễ dàng trong việc hiệu chỉnh nguồn khi cần thiết. Ý nghĩa của Thừa kế đã nằm gọn trong tên gọi của nó, nghĩa là một lớp hậu bối có tính thừa kế sẽ được “đặc ân” thừa hưởng tất cả những gì có trong lớp tiền bối và cộng thêm những thuộc tính mới của nó.
Riêng đối với C++ sự thừa hưởng sẽ bị hạn chế, chỉ được phép thừa hưởng dữ liệu chung, dữ liệu được bảo vệ…

danceswithwolves
30-09-2002, 14:45
xin chào, bạn có thể convert mã Pascal -> C-like form hay pseudo code được không ? Vì đây là box C/C++/C# mà.

Anyway, hoan nghênh tinh thần đóng góp của bạn.

xp2002_
30-09-2002, 21:46
Chết thật vội quá tớ quên mất cáo lỗi nhé. Lần sau tớ sẽ convert sang mã C++ cho, những lý thuyết cần thiết nhé. OK. :D