Những dấu chân của nhân loại trên con đường đến với lập trình hướng đối tượng (phần 3)

images.jpg

Kế thừa và ủy quyền (Inheritance and Delegation)


Kế thừa

Tính chất cuối cùng mà Simula và C++ đem lại là Kế thừa. Kế thừa nghĩa là tạo ra một class mới vừa mang chức năng của class khác đã có sẵn, vừa mang chức năng mới của riêng nó.

Trước hết chúng ta thử suy nghĩ về mặt dữ liệu. Khi chúng ta muốn quản lí giáo viên và học sinh thì những gì mà cả hai đều phải có là tên, giới tính và tuổi. Học sinh thì có thêm chuyên nghành và khóa. Giáo viên thì có thêm thu nhập hàng tháng và dạy môn nào.

typedef struct {
    int age;
    int sex;
    char *name;
} Person;

typedef struct {
    People people;
    int grade;
    int study:
} Student;

typedef struct {
    People people;
    int field;
    int salary;
} Teacher;

Teacher t;
t.people.age = 10;

Bằng cách lồng một cấu trúc vào trong một cấu trúc như vậy, ta tạo ra được một cấu trúc dùng chung dữ liệu.

Nếu muốn thêm xử lí vào ta sẽ làm như sau.

char * person_get_name(Person *self) {
    return self->name;
}
char * teacher_get_name(Teacher *self){
    return person_get_name((People *)self);
}

char * teacher_get_name_2(Teacher *self){
    return person_get_name(&self.person);
}

Teacher *pt = teacher_alloc_init(30,MALE,"daichi hiroki",MATH,30);
teacher_get_name(pt);

Bằng xử lí upcasting, ta đã access được vào bên trong cấu trúc. Hoặc ta cũng có thể truyền nguyên vẹn cấu trúc bên trong để thực hiện việc dùng chung xử lí.

Tuy nhiên, làm theo cách này thì với mỗi lần muốn dùng chung xử lí ta sẽ phải viết code để phục vụ cho việc đó. Thứ giải quyết vấn đề đó chính là tính Kế thừa.

Bằng cách truy dấu theo tính Kế thừa, chúng ta có thể tìm ra các member function đang được public hoặc protected.

Do đó, chúng ta có thể viết lại code như sau.

Teacher *t = new Teacher;
t->get_name; //Tự tìm kiếm trong class people mà không cần phải khai báo là teacher

Mặt khác, khi ta có một hàm như sau:

string nameFormat(People *p)  {
    return sprintf("%s(%d) %s",p->get_name,p->get_age,(p->get_sex == MALE) ? "Male" :"Female");
}

thì chỉ cần là người (chính xác hơn là subclass) là có quyền sử dụng các xử lí chung. Trong Perl5, bên cạnh tính Kế thừa được sử dụng như một chức năng chính thức, còn có một chức năng nữa, đó là @ISA.

package Person;
sub get_name{"person"}

package Student;
//Các package được bless nhưng không có hàm số nào sẽ tìm kiếm trên package được gán @ISA
our @ISA = qw/Person/;

package Teacher;
our @ISA = qw/Person/;

Chỉ cần chỉ định việc tìm kiếm cần thực hiện ở đâu như vậy, nghĩa là tính Kế thừa đã được thể hiện. Nhân tiện, nếu viết bằng FQN thì cách thể hiện sẽ trở thành

@Teacher::ISA="Person", đồng nghĩa với việc mối quan hệ “teacher is a person” được thiết lập.

Việc tìm kiếm method trong trường hợp trên có thể diễn giải bằng đoạn code sau.

//Diễn giải dispatch động dưới dạng code
var PERSON_TABLE = {
    "getName" : function(self){return self.name}
};

var STUDENT_TABLE = {
    "getGrade" : function(self){return self.grade},
    "#is-a#"  : PERSON_TABLE
};

var object = {
    _vt_ : STUDENT_TABLE, // Khiến object hiểu vai trò của mình
    name : "daichi hiroki"
};

//Gọi method một cách động
function methodCall(object,methodName){

    var vt = object._vt_;
    //Tìm và chạy method một cách tuần tự theo is-a
    while(vt){
        var method = vt[methodName];
        if( method ) return method(object);
        vt = vt["#is-a#"];
    }
    throw Error;
}

methodCall(object,"getName");

Ủy quyền

Có những ngôn ngữ sử dụng một thủ pháp thay cho Kế thừa – đó là Ủy quyền.

Do ở trên tôi đã nói đến Dynamic displatch nên tôi sẽ giải thích đơn giản về Ủy quyền, khái niệm này không liên quan đến các hệ ngôn ngữ C++ và Simula cho lắm.

Tương phản với Class-based object oriented programming , khái niệm này còn hay được gọi là Prototype-based object oriented programming (hướng đối tượng dựa trên prototype). Một ví dụ điển hình của hình thức đó là JavaScript.

Chúng ta có thể dùng ví dụ đã nói ở trên để giải thích về sự khác nhau giữa Kế thừa và Ủy quyền, đơn giản chỉ là : struct đang được nhúng vào có phải là pointer hay không?

typedef struct {
    int age;
    int sex;
    char *name;
} Person;

typedef struct {
        Person* person;
    int grade;
    int study:
} Student;

typedef struct {
    Person* person;
    int field;
    int salary;
} Tea

Ủy quyền cho phép thực hiện việc rewrite động đối với search destination object.

t->person = new Person;

Ta có thể diễn giải bằng code như dưới đây.

var hogetaro = {
    getName : function(self){return self.name},
    name       : "hogetaro"
};

var object = {
    _prototype_ : hogetaro, // Decide the next search destination object
    name : "daichi hiroki"
};

// Call method dynamically
function methodCall(object,methodName){
    //Begin from data itself
    var pt = object;
    //Follow is-a to find and run method
    while(pt){
        var method = pt[methodName];
        if( method ) return method(object);
        pt = pt._prototype_;
    }
    throw Error;
}

methodCall(object,"getName");
object._prototype_ = { getName:function(){return "hello"}};
// Rewriting prototype is possible
methodCall(object,"getName");

Việc follow tuần tự theo các prototype để tìm method như trên trong Javascript được gọi với cái tên “Prototype chain”.

Trong ngôn ngữ lua có chức năng “metatable” cũng thực hiện việc tương tự.

Ngoài ra, việc tìm kiếm method bằng chức năng Ủy quyền còn được gọi là Kế thừa động.

Việc sử dụng cơ chế nào để tìm kiếm method động chính là một yếu tố quan trọng đối với ngôn ngữ lập trình hướng đối tượng.

Singleton method, singleton class, include, prepend hay module của Ruby là ví dụ.

(Xem phần cuối)


All Rights Reserved