Tại sao Java không có cơ chế để Override thuộc tính của instance

Chúng ta có hai class như sau

class Foo {
    String value = "foo";

    public String getValue() {
        return value;
    }
}

class Bar extends Foo {
    String value = "bar";

    @Override
    public String getValue() {
        return value;
    }
}

Hãy quan sát sự khác nhau giữa hai lời gọi truy cập sau đây

Foo foo = new Bar();
System.out.println(foo.getValue()); // bar
System.out.println(foo.value); // foo

Chúng ta có hiểu biết thường thức rằng phương thức getValue() của class con sẽ ghi đè phương thức tương ứng của class cha, và chúng ta sẽ cho rằng điều tương tự sẽ xảy ra với thuộc tính của instance. Nhưng sự thực đã không như thế.

Chúng ta nhận được giá trị thuộc tính dựa theo kiểu của tham chiếu, không phải theo kiểu của instance. Nhưng tại sao?

Đa hình chỉ áp dựng cho phương thức, không áp dụng cho thuộc tính

Tại thời điểm compile, bytecode của các lời gọi phương thức sẽ là bytecode của class của tham chiếu. Tới thời điểm thực thi chương trình tất cả các bytecode đó sẽ được JVM thay thế bằng bytecode của class của instance. Chúng ta gọi cơ chế này là đa hình tại thời điểm thực thi (runtime).

Tương tự như vậy, tại thời điểm compile tất cả các lời gọi truy cập thuộc tính đều sẽ là bytecode của kiểu tham chiếu. Vấn đề là tại runtime… không có thêm điều gì xảy ra với các bytecode đó cả. Chúng ta gọi cơ chế này là đa hình chỉ áp dụng cho phương thức, không áp dụng cho thuộc tính.

Nhưng tại sao lại thế? Hay rõ hơn, tại sao các nhà thiết kế Java lại định hình như thế?

  • Thứ nhất, việc tồn tại cơ chế ghi đè bytecode truy cập thuộc tính sẽ khiến các phương thức mà class con được kế thừa từ class cha bị “hỏng”, chẳng hạn trong trường hợp class con khai báo lại thuộc tính thành một kiểu dữ liệu khác.
  • Thứ hai, ghi đè thuộc tính sẽ phế bỏ Nguyên tắc thay thế Liskov.
  • Thứ ba, việc thiếu vắng khả năng ghi đè thuộc tính còn giúp chúng ta tạo ra các thiết kế phần mềm tốt hơn so với khi không có.

Kết luận

Thông thường không ai lại đi khai báo lại thuộc tính với cùng một tên. Ít khi có nhu cầu thực tế nào bắt buộc chúng ta phải làm như thế khi thiết kế cây kế thừa. Việc khiến thuộc tính bị trùng tên cũng sẽ dễ gây nhầm lẫn. Và cuối cùng, kể cả khi diều đó có xảy ra, chúng ta cũng sẽ không bao giờ gặp phải kết quả khó lường như trong ví dụ ở đầu bài viết này nếu chúng ta tận dụng triệt để tính chất bao gói. Cụ thể, giấu tất cả các thuộc tính ra khỏi khả năng truy cập trực tiếp từ bên ngoài và thay vào đó sử dụng các getter/setter sẽ giúp chúng ta viết ra phần mềm chạy giống như nó được viết hơn, dễ debug hơn, thiết kế đẹp hơn và “khỏe” hơn.


Tham khảo:

Loading

2 Replies to “Tại sao Java không có cơ chế để Override thuộc tính của instance”

  1. Thưa thầy, em là 1 học sinh cũ của thầy, em cám ơn thầy vì bài viết rất hữu ích ạ.
    Trong bài thì em có thấy thầy nhắc về tính đa hình, em có 1 thắc mắc: Rất nhiều trang web nói rằng trong java thì tính đa hình thể hiện qua overloading method (static polymorphism) và Overriding method ( dynamic Polymorphism). Nhưng số khác lại nói rằng overloading thì không liên quan vì signature của 2 phương thức overload là khác nhau. Trên 1 số diễn đàn thì nói rằng tùy thuộc vào cách định nghĩa về đa hình. Trên doc của oracle thì em không thấy nhắc về overloading method. Vậy thầy có thể cho em xin quan điểm của thầy về vấn đề này được không ạ?

  2. Không có định nghĩa này hay định nghĩa kia về đa hình, chỉ có một định nghĩa duy nhất: “chúng ta muốn có cùng một thứ gì đó, hoạt động khác nhau, trong những điều kiện khác nhau”. Trong lập trình, “một thứ gì đó có khả năng ‘hoạt động’ ” được gọi là hàm. Vậy, một hàm, hoạt động khác nhau trong những điều kiện khác nhau, là sự thể hiện của tính đa hình.

    Hai phiên bản khác nhau của một hàm ‘f’, cho dù là static polymorphism hay dynamic polymorphism, thì vẫn tên là hàm ‘f’. Trong trường hợp static polymorphism, chúng ta cố ý đặt tên chúng đều là ‘f’, là đã ngụ ý rằng chúng ta muốn chúng là những phiên bản khác nhau của cùng một thứ (nếu không, ta đã đặt tên chúng khác đi) – tức là, ta muốn có đa hình ở đấy. Điều này, ngay ở tên gọi của hai cách, đều có chữ polymorphism, là đã thể hiện rồi.

    Việc Java tồn tại cả hai khái niệm overloading method và overriding method thật ra là do bản chất của Java – định kiểu mạnh, và định kiểu tĩnh, dẫn tới bắt buộc, để đáp ứng đầy đủ các khía cạnh của đa hình, thì phải phân ra định vị method động hay tĩnh. Với những ngôn ngữ định kiểu động sẽ không còn hai khái niệm đó nữa, mà cả hai đều được gom vào cùng một chuyện là triển khai đa hình mà thôi.

Leave a Reply

Your email address will not be published. Required fields are marked *