Interface như hạch

Bài viết này được lược dịch và phóng tác từ bài ‘Interface’ Considered Harmful của Uncle Bob — chuyên gia hàng đầu thế giới về OO design.

Này ông, cái interface ấy mà.

Ý ông là từ khóa interface trong Java, C# hả?

Chính nó! Theo ông thì interface là một tính năng xịn của Java, C# phải không?

Chuyện, còn phải hỏi!

Vậy hả? Thế interface là gì, nó là một dạng class chăng?

Đâu có, interface khác class chớ!

Khác khỉ gì?

Thì interface không được phép có cài đặt của method, vậy mà cũng không biết!

Nhưng tôi thích viết một abstract class không hề có cài đặt method cũng được vậy nè:

public abstract class MyInterface {
    public abstract void f();
}

Ây dà, cái ông viết là abstract class mà!

Thì sao?

Abstract class vẫn có thể có method được cài đặt.

Nhưng cái tôi viết đâu có cài đặt method nào đâu. Tại sao nó không là interface được?

Abstract class vẫn có thể có instance property, còn interface thì không.

Biết, nhưng cái tôi viết đâu có instance property nào đâu. Tại sao nó không là interface được?

Ờ thì vì nó không thể là interface.

Nói chuyện với ông như nói với đầu gối. Thôi được rồi, tôi lại hỏi ông: interface có gì hay ho hơn so với class?

Ông có thể kế thừa nhiều interface, nhưng lại chỉ kế thừa được một class mà thôi.

Sao vậy?

Vì compiler nó thế.

Vãi compiler! OK, vậy kế thừa nhiều class có gì xấu?

Ủa ông không biết hả? Đa kế thừa class nguy hiểm vồn, ông có nghe nói tới “Deadly Diamond of Death” chưa, ghê lắm đó!

Ừa nghe ghê ghê, nhưng nó là cái giống gì?

Ông có một class kế thừa 2 class khác, 2 class đó lại cùng kế thừa một class khác nữa.

Code như vầy đúng ko:

class B {}
class D1 extends B {}
class D2 extends B {}
class M extends D1, D2 {}

Chuẩn cơm mẹ nấu luôn, thấy ghê chưa?

Ghê gì?

Trong trường hợp class B có một instance property hoặc cài đặt method.

Ý ông là:

class B { public int i; }

Đúng rồi. Vậy trong class M sẽ có bao nhiêu i?

Ah, ra thế! Do cả D1 D2 đều có i, xong rồi M lại kế thừa cả D1 D2, vậy thì M sẽ có tới hai thằng i riêng biệt lận.

Chứ sao nữa! Nếu trong M không đả động gì tới i, hoặc nơi xài M không quất M.i, thì không sao. Nhưng hễ mà i bị truy cập là compiler bó chiếu, vì không hiểu M đang muốn lấy i nào, trong D1 hay D2?

Lộn xộn quá vậy!

Chứ sao!

Tóm lại là trong Java, C# ta không được phép kế thừa nhiều class vì có thể bị “Deadly Diamond of Death”?

Cái “có thể” này rất rất dễ xảy ra, vì tất cả class mặc định thì đều kế thừa từ class Object ông tổ mà.

Ặc, đúng rồi nhỉ! Và thế nên tốt nhất là cấm tiệt đa kế thừa?

Uhm.

Rồi, quay lại cái abstract class hồi nãy của tôi, tại sao nó không là interface được?

public abstract class MyInterface {
    public abstract void f();
}

Vì nó dùng từ khóa class và do đó compiler không cho phép ông đa kế thừa. Còn nếu ông dùng từ khoá interface thì đa kế thừa OK!

Hoá ra từ khóa interface được chế ra, đơn giản chỉ để đánh dấu một kiểu đặc biệt. Kiểu này bị compiler bắt buộc không được có instance property hay cài đặt method nào cả.

Và không bị compiler cho kế thừa class Object luôn. Chính vì thế interface chả có cái gì để cho con của nó gọi, như gọi i trong ví dụ ở trên. Nhờ đó sẽ luôn luôn tránh được “Deadly Diamond of Death”.

Ahhh, đó là lý do tại sao đa kế thừa interface thì lúc nào cũng an toàn.

Chính thế!

Giờ nói vụ từ khoá “kế thừa”, tại sao lúc thì extends lúc thì implements? Mỗi khi tôi chuyển class thành interface hoặc ngược lại tôi phải sửa code mệt pà cố!

Ôi dào, thì ông search and replace text thôi, IDE nó làm cái bụp. Ah mà chỉ Java mới vậy thôi, C# với C++ dùng dấu hai chấm hết á.

Java dở chỗ đó đó, như C# với C++ lại hay, dùng dấu hai chấm cho đơn giản gọn nhẹ.

Rồi rồi … Ah mà nè, sao ông phải dùng đa kế thừa class nhỉ, tôi chả dùng bao giờ!

Tại ông chưa đụng chuyện thôi, ví dụ:

public class Subject {
    private List<Observer> observers = new ArrayList<>();
    private void register(Observer o) {
        observers.add(o);
    }
    private void notify() {
        for (Observer o : observers)
            o.update();
    }
}

public class MyWidget {...}

public class MyObservableWidget extends MyWidget, Subject {
    ...
}

Ah há, Observer pattern nè.

Đúng rồi, ông cũng biết Observer pattern hả?

Ai chả biết, nhưng code của ông compile lỗi á!

Thì vậy, quả là bi kịch!

Bi kịch gì! Ông chỉ cần cho thằng MyWidget kế thừa Subject là xong.

Làm vậy thì thằng MyWidget lại dính dáng tới Subject. Tôi không muốn thế! Tôi chỉ muốn một vài con của MyWidget, nếu cần, thì mới làm Subject.

Vậy thì copy method register() với cả notify() vô trong MyObservableWidget luôn.

Vãi, nếu thế thì mai mốt tôi có thêm các observable tôi lại phải copy paste hả cha nội!

Ah hay là trong class MyObservableWidget ông khai báo một tham chiếu tới Subject, xong rồi trong register() với notify() của MyObservableWidget ông bàn giao công việc lại cho tham chiếu đó. Cái này gọi là aggregation đó ahihi.

Như vậy vẫn cứ là copy paste ah, dù ít hơn.

Thế thì thôi, ông lười copy paste vậy tôi thua rồi.

Tôi muốn đa kế thừa — một cách có kiểm soát, nhưng language ép tôi phải dùng từ khoá interface, nhưng vì interface không được phép có cài đặt method, tôi phải copy paste cái cài đặt đó quá trời. Ông thấy nỗi khổ của tôi chưa!

Uhm, tôi thấy rồi!

Ông thấy do cái gì của language mà tôi khổ vậy chưa?

Do từ khoá interface!

Tóm lại là sao ông nhỉ …

Interface như hạch.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s