2011-12-28 18 views
5

Wikipedia định nghĩa virtual methods như:Các phương thức trong mô hình hướng đối tượng có thể bị ghi đè bởi các phương thức có cùng chữ ký trong các lớp kế thừa. Tuy nhiên, các biến không thể. Tại sao?

Trong lập trình hướng đối tượng, một hàm ảo hoặc một phương pháp ảo là một chức năng hoặc các phương pháp mà hành vi có thể được ghi đè trong một lớp kế thừa của một hàm với cùng một chữ ký [để cung cấp một hành vi đa hình].

Theo định nghĩa, mọi phương pháp không tĩnh trong Java là theo mặc định ảo mặc định trừ phương pháp cuối cùng và riêng tư. Phương thức không thể kế thừa cho hành vi đa hình là không phải là một phương pháp ảo.

Phương pháp tĩnh trong Java không bao giờ bị ghi đè; do đó, nó là vô nghĩa để khai báo một phương thức tĩnh là cuối cùng trong Java bởi vì các phương thức tĩnh tự hành xử giống như các phương thức cuối cùng. Chúng có thể chỉ đơn giản là ẩn trong các lớp con theo các phương thức có cùng chữ ký. Rõ ràng là vì các phương thức tĩnh không bao giờ có hành vi đa hình: một phương thức được ghi đè phải đạt được tính đa hình, mà không phải là trường hợp với các phương thức tĩnh.

Từ đoạn trên, một kết luận quan trọng có thể được thúc đẩy. Tất cả các phương thức trong C++ là mặc định tĩnh vì không có phương thức nào trong C++ có thể hoạt động đa hình cho đến khi và trừ khi chúng được khai báo một cách rõ ràng trong lớp siêu. Ngược lại, tất cả các phương thức trong Java ngoại trừ các phương thức cuối cùng, tĩnh và riêng là mặc định ảo vì chúng có hành vi đa hình theo mặc định (không cần khai báo một cách rõ ràng các phương thức như ảo trong Java và, do đó, Java không có từ khóa nào như "ảo").

Bây giờ, hãy chứng minh rằng các biến mẫu (tĩnh quá) không thể hoạt động đa hình từ ví dụ đơn giản sau đây trong Java.

class Super 
{ 
    public int a=5; 
    public int show() 
    { 
     System.out.print("Super method called a = "); 
     return a; 
    } 
} 

final class Child extends Super 
{ 
    public int a=6; 

    @Override 
    public int show() 
    { 
     System.out.print("Child method called a = "); 
     return a; 
    } 
} 

final public class Main 
{ 
    public static void main(String...args) 
    { 
     Super s = new Child(); 
     Child c = new Child(); 

     System.out.println("s.a = "+s.a); 
     System.out.println("c.a = "+c.a);   

     System.out.println(s.show()); 
     System.out.println(c.show()); 
    } 
} 

Sản lượng sản xuất bởi các đoạn mã trên là như sau.

 
s.a = 5 
c.a = 6 
Child method called a = 6 
Child method called a = 6 

Trong ví dụ này, cả hai gọi s.show()c.show() thực hiện cho các phương pháp show() thông qua các biến kiểu Super và kiểu Child, tương ứng, gọi phương thức show() trong lớp Child. Điều này có nghĩa là phương thức show() trong lớp Child ghi đè phương thức show() trong lớp Super vì cả hai đều có chữ ký giống hệt nhau.

Điều này, tuy nhiên, không thể áp dụng cho biến mẫu a được khai báo trong cả hai lớp.Trong trường hợp này, s.a sẽ tham khảo a trong lớp Super và hiển thị 5c.a sẽ tham khảo a trong lớp Child và hiển thị 6 nghĩa là a trong Child lớp chỉ da (và không ghi đè như đã xảy ra để không tĩnh phương pháp) a trong lớp Super.

Sau cuộc thảo luận dài này, chỉ có một câu hỏi. Tại sao các biến mẫu (và các biến còn lại) không bị ghi đè? Những lý do đặc biệt để thực hiện một cơ chế như vậy là gì? Liệu có bất kỳ ưu điểm hay nhược điểm nào, nếu chúng bị ghi đè?

+1

C++ phương pháp không ảo không tĩnh. chúng là các phương thức thể hiện không thể bị ghi đè. Chúng tương tự như các phương thức cuối cùng trong Java: các phương thức mẫu không thể bị ghi đè. –

+0

'Theo đoạn trên, một kết luận quan trọng có thể được thúc đẩy. Tất cả các phương thức trong C++ (mặc định trong C) là mặc định tĩnh vì không có phương thức nào trong C++ có thể hoạt động đa hình cho đến khi và trừ khi chúng được khai báo một cách rõ ràng trong cơ sở ': một phương thức tĩnh không thể truy cập đối tượng [no' this' cho các phương thức này ] trong khi một chức năng [mặc định] không ảo trong C++ có thể – amit

+1

Tôi điều từ "ghi đè" không được áp dụng cho các biến. Bạn ghi đè hành vi, nhưng không phải là trạng thái. Các biến có thể được truy cập từ lớp con, vì vậy không cần phải "ghi đè lên" (bạn có thể thay đổi giá trị của nó và nó có cùng tên). Ngoài ra còn có một cái gì đó gọi là shadowing, vì vậy nếu bạn redeclare một biến với cùng tên nó là một biến khác nhau trong thời gian chạy. –

Trả lời

3

Tôi nghĩ rằng mục đích ghi đè đang thay đổi chức năng mà không thay đổi chữ ký. Điều này chỉ phù hợp với các phương thức: phương thức có thể có cùng chữ ký nhưng có hành vi khác nhau. Các trường chỉ có "chữ ký" cũng được giới hạn ở loại và tên. Họ không có hành vi, vì vậy không có gì có thể bị ghi đè.

Lý do khác để không có khả năng "ghi đè" trường là các trường thường là riêng tư (hoặc phải là riêng tư), vì vậy chúng thực sự là chi tiết đẫm máu của việc triển khai lớp. Tuy nhiên, các phương thức đại diện cho giao diện bên ngoài của lớp. Giao diện bên ngoài này sẽ hỗ trợ đa hình để làm cho nó dễ dàng hơn để thay đổi chức năng của mô-đun mà không thay đổi mối quan hệ giữa các mô-đun.

+0

Theo Nguyên tắc thay thế Liskov, chữ ký không nhất thiết phải giống nhau: kiểu trả về phải là biến thể (cùng kiểu hoặc kiểu bắt nguồn) và đối số nên là contravariant (cùng loại hoặc kiểu cơ bản) trong chữ ký của các phương thức ghi đè. Tương tự như vậy, loại của một trường có cùng tên trong một hậu duệ nên được so khớp, do đó, nó có thể có ý nghĩa để "ghi đè" các lĩnh vực. – outis

+0

@outis: Kiểu của một trường tương tự * cả * với kiểu trả về của getter ('Số x = this.x' này tương tự với' Số x = this.getX() ') * và * thành tham số của bộ setter -type ('this.x = (Số) x' tương tự với' this.setX ((Số) x) '). Cho nên nó phải là bất biến. (Và điều này đúng ngay cả khi trường là 'final', vì hàm tạo superclass là cái sẽ được đặt trong trường hợp đó.) – ruakh

2

Có một cách sử dụng thuật ngữ "tĩnh" khi đề cập đến độ phân giải tên: độ phân giải tĩnh là khi loại khai báo của biến được sử dụng để phân giải tên, động khi loại dữ liệu được lưu trữ trong biến được sử dụng. Độ phân giải tĩnh có thể được thực hiện tại thời gian biên dịch, trong khi độ phân giải động phải được thực hiện tại thời gian chạy. Giải quyết các thành viên ảo (đó là một hình thức giải quyết năng động) đòi hỏi một mức độ thêm của indirection, một tra cứu bảng. Cho phép các trường "ảo" sẽ phải chịu chi phí thời gian chạy.

Khi độ phân giải tĩnh cho tất cả các thành viên được thực hiện, thì có rất ít điểm trong việc có các biến được nhập tĩnh. Tại thời điểm đó, bạn cũng có thể có một ngôn ngữ được gõ động, chẳng hạn như Python và vô số các biến thể Lisp. Khi bạn loại bỏ các kiểu tĩnh, sẽ không còn ý nghĩa khi cố gắng nói về các trường con không ghi đè các trường mẹ (hoặc ghi đè), bởi vì không còn một kiểu biến tĩnh để đề xuất cách khác.

class Super(object): 
    a=5 
    def show(self): 
     print("Super method called a = ", end='') 
     return self.a 

class Child(Super): 
    a=6 
    def show(self): 
     print("Child method called a = ", end='') 
     return self.a 

# there's no indication that 's' is supposed to be a Super... 
s = Child(); 
c = Child(); 

# so it's no surprise that s.a is Child.a 
print("s.a =", s.a) 
# result: "s.a = 6" 
print("c.a =", c.a) 
# result: "c.a = 6" 
print(s.show()) 
# result: "Child method called a = 6" 
print(c.show()) 
# result: "Child method called a = 6" 

Lis chung, cần lưu ý, có cả hai loại khai báo và độ phân giải động.

(defgeneric show (o)) 

(defclass super() 
    ((a :accessor super-a 
    :initform 5 
    :initarg :a)) 
) 

(defmethod show ((o super)) 
    (list "Super method called a = " 
     (slot-value o 'a)) 
) 

(defclass child (super) 
    ((a :accessor child-a 
    :initform 6 
    :initarg :a)) 
) 

(defmethod show ((o child)) 
    (list "Child method called a = " 
     (slot-value o 'a)) 
) 

(defun test (s c) 
    (declare (type super s)) 
    (declare (type child c)) 
    (list (list "s.a =" (slot-value s 'a)) 
     (list "c.a =" (slot-value c 'a)) 
     (show s) 
     (show c) 
     ) 
) 

(test (make-instance 'child) (make-instance 'child)) 
;; result: '(("s.a =" 6) ("c.a =" 6) ("Child method called a = " 6) ("Child method called a = " 6)) 

Trong OOP thuần túy (theo một số), đối tượng không được có trường công khai, chỉ thành viên công khai, do đó trường được giải quyết tĩnh không phải là vấn đề.

Tất cả các phương pháp trong C++ (cũng trong C) theo mặc định tĩnh [...]

Không, bởi vì static nghĩa hơn "không thể được ghi đè" trong Java, và don 't có nghĩa là ở tất cả trong C + +. Trong cả hai, thuộc tính cơ bản của các phương thức tĩnh là chúng là các phương thức lớp, được truy cập thông qua một lớp, chứ không phải là một cá thể (mặc dù Java cho phép chúng được truy cập bằng một trong hai).

2

Nhìn vào thế giới C#. Việc sử dụng các biến cá thể công khai không được khuyến khích - xem StyleCop. Cách duy nhất được khuyến nghị là sử dụng các thuộc tính có thể được ghi đè lên.

Tôi nghĩ đây là phương pháp phù hợp. Chỉ cần không sử dụng các biến cá thể công khai và sử dụng các phép so sánh với các thuộc tính.

Về lý do tại sao nó như vậy trong Java, tôi nghĩ rằng một số loại C++ giống như phong cách đã được áp dụng tại thời điểm đó khi Java được thiết kế như một cách để dễ dàng chuyển đổi.

+0

C# giống như Java. các lĩnh vực công cộng cũng được coi là một quảng cáo thực tế trong Java. C# chỉ có cú pháp đường để truy cập các thuộc tính bằng cách sử dụng cú pháp truy cập trường, và Java sử dụng getters và setters, nhưng chúng giống nhau. –

+0

Điều chắc chắn.Thành thật mà nói, tôi mong đợi một cú pháp tốt hơn cho getters và setters trong Java trong phiên bản tiếp theo. –

+0

@Jiri Pik Cú pháp hiện tại là tốt. Nếu bạn không thích, hãy đặt trường ở chế độ công khai. Tôi nghĩ cú pháp C# cho các thuộc tính là khủng khiếp. –

1

Có rất ít việc ghi đè trong trường thể hiện. Bạn chỉ phải thay thế nó bằng cùng một thứ: cùng tên, cùng loại. Bạn có thể thay thế một mảng bằng một chiều dài khác nhau, nhưng bạn cần xem xét kỹ tất cả các lớp siêu để đảm bảo chúng không đưa ra giả định về độ dài.

Các trường không phải là riêng tư, như được ghi nhận bởi những người khác, thường là điều cần tránh. Đôi khi một lớp học có nhiều cấu trúc C, với các trường công khai và không có phương thức nào. Đây là một cách thực hành hoàn hảo, nhưng vẫn không có điểm nào trong việc ghi đè bất cứ điều gì.

Ghi đè thực sự chỉ là về các phương pháp, không liên quan đến ngôn ngữ. Java không có các thuộc tính như C#, các lớp (có giới hạn) ở bên phải của riêng chúng với các phương thức sẽ được thay đổi. Các trường có thể nhìn và hành động như các thuộc tính, nhưng chúng không giống nhau.

Các vấn đề liên quan