Tại sao các phương thức tĩnh không thể có thể được ghi đè?


13

Trong câu trả lời cho câu hỏi này , sự đồng thuận chung là các phương thức tĩnh không có nghĩa là bị ghi đè (và do đó, các hàm tĩnh trong C # không thể là ảo hoặc trừu tượng). Tuy nhiên, đây không chỉ là trường hợp trong C #; Java cũng cấm điều này và C ++ dường như cũng không thích điều đó. Tuy nhiên, tôi có thể nghĩ ra nhiều ví dụ về các hàm tĩnh mà tôi muốn ghi đè trong một lớp con (ví dụ: các phương thức xuất xưởng). Trong khi về lý thuyết, có nhiều cách để khắc phục chúng, không ai trong số chúng sạch sẽ hay đơn giản.

Tại sao các hàm tĩnh không được ghi đè?


4
Delphi hỗ trợ các phương thức lớp , khá giống với các phương thức tĩnh và có thể bị ghi đè. Họ được thông qua một selfcon trỏ trỏ đến lớp chứ không phải là một thể hiện của lớp.
CodeInChaos

Một định nghĩa của tĩnh là: thiếu thay đổi. Tôi tò mò tại sao bạn tạo một hàm tĩnh mà bạn muốn ghi đè.
JeffO

4
@JeffO: Định nghĩa tiếng Anh về "tĩnh" hoàn toàn không liên quan gì đến ngữ nghĩa duy nhất của các hàm thành viên tĩnh.
Cuộc đua nhẹ nhàng với Monica

5
Câu hỏi của bạn là "tại sao các phương thức tĩnh nên sử dụng công văn được nhập tĩnh thay vì gửi đơn ảo ?" Khi bạn diễn đạt câu hỏi theo cách đó tôi nghĩ nó tự trả lời.
Eric Lippert

1
@EricLippert Câu hỏi có thể được đặt ra là "Tại sao C # cung cấp phương thức tĩnh thay vì phương thức lớp?", Nhưng đây vẫn là một câu hỏi hợp lệ.
CodeInChaos

Câu trả lời:


13

Với các phương thức tĩnh, không có đối tượng để cung cấp kiểm soát thích hợp của cơ chế ghi đè.

Cơ chế phương thức ảo lớp / thể hiện bình thường cho phép kiểm soát các phần ghi đè được tinh chỉnh như sau: mỗi đối tượng thực là một thể hiện của chính xác một lớp. Lớp đó xác định hành vi của phần ghi đè; nó luôn có được vết nứt đầu tiên tại các phương thức ảo. Sau đó, nó có thể chọn gọi phương thức cha mẹ vào đúng thời điểm để thực hiện. Mỗi phương thức cha mẹ sau đó cũng lần lượt gọi phương thức cha của nó. Điều này dẫn đến một loạt các yêu cầu cha mẹ, thực hiện một trong những khái niệm về việc sử dụng lại mã mà hướng đối tượng được biết đến. (Ở đây, mã cơ sở / siêu lớp đang được sử dụng lại theo cách tương đối phức tạp; một khái niệm trực giao khác về tái sử dụng mã trong OOP chỉ đơn giản là có nhiều đối tượng của cùng một lớp.)

Các lớp cơ sở có thể được sử dụng lại bởi các lớp con khác nhau và mỗi lớp có thể cùng tồn tại thoải mái. Mỗi lớp được sử dụng để khởi tạo các đối tượng ra lệnh cho hành vi của chính nó, hòa bình và đồng thời cùng tồn tại với các đối tượng khác. Máy khách có quyền kiểm soát hành vi nào nó muốn và khi nào bằng cách chọn lớp nào sẽ sử dụng để khởi tạo một đối tượng và truyền cho người khác theo ý muốn.

(Đây không phải là một cơ chế hoàn hảo, vì người ta luôn có thể xác định các khả năng không được hỗ trợ, tất nhiên, đó là lý do tại sao các mẫu như phương pháp nhà máy và tiêm phụ thuộc được đặt lên hàng đầu.)

Vì vậy, nếu chúng ta tạo ra khả năng ghi đè cho thống kê mà không thay đổi bất cứ điều gì khác, chúng ta sẽ gặp khó khăn khi đặt hàng ghi đè. Thật khó để xác định bối cảnh giới hạn cho khả năng áp dụng của ghi đè, vì vậy bạn sẽ có được ghi đè trên toàn cầu thay vì cục bộ hơn như với các đối tượng. Không có đối tượng thể hiện để chuyển đổi hành vi. Vì vậy, nếu ai đó đã gọi phương thức tĩnh đã bị ghi đè bởi một lớp khác, liệu ghi đè có được kiểm soát hay không? Nếu có nhiều phần ghi đè như vậy, ai sẽ kiểm soát trước? thứ hai? Với các đối tượng ghi đè, tất cả các câu hỏi này đều có câu trả lời có ý nghĩa và có lý do, nhưng với số liệu thống kê thì không.

Ghi đè cho thống kê sẽ rất hỗn loạn, và, những việc như thế này đã được thực hiện trước đây.

Ví dụ, Mac OS System 7 và trước đó đã sử dụng cơ chế vá bẫy để mở rộng hệ thống bằng cách kiểm soát các cuộc gọi hệ thống do ứng dụng tạo ra trước hệ điều hành. Bạn có thể nghĩ bảng vá gọi hệ thống như một mảng các con trỏ hàm, giống như một vtable cho các đối tượng thể hiện, ngoại trừ việc nó là một bảng toàn cục duy nhất.

Điều này gây ra sự đau buồn không thể kể xiết cho các lập trình viên vì tính chất không có thứ tự của việc vá bẫy. Bất cứ ai phải vá cái bẫy cuối cùng về cơ bản là chiến thắng, ngay cả khi họ không muốn. Mỗi miếng vá của bẫy sẽ nắm bắt giá trị bẫy trước đó cho một loại khả năng gọi cha mẹ, cực kỳ dễ vỡ. Xóa một bản vá bẫy, giả sử khi bạn không còn cần biết về một cuộc gọi hệ thống bị coi là hình thức xấu vì bạn không thực sự có thông tin cần thiết để xóa bản vá của mình (nếu bạn đã thực hiện, bạn cũng sẽ hủy bỏ mọi bản vá khác đã theo dõi bạn).

Điều này không có nghĩa là không thể tạo ra một cơ chế để ghi đè các số liệu thống kê, nhưng điều tôi có thể muốn làm thay vào đó là biến các trường tĩnh và phương thức tĩnh thành trường đối tượng và phương thức cá thể của siêu dữ liệu, sao cho đối tượng bình thường kỹ thuật định hướng sau đó sẽ áp dụng. Lưu ý rằng có những hệ thống cũng làm điều này: CSE 341: Các lớp và siêu dữ liệu nhỏ ; Xem thêm: Smalltalk tương đương với tĩnh của Java là gì?


Tôi đang cố gắng nói rằng bạn sẽ phải thực hiện một số thiết kế tính năng ngôn ngữ nghiêm túc để làm cho nó hoạt động tốt hơn nữa. Đối với một ví dụ, một cách tiếp cận ngây thơ đã được thực hiện, đi khập khiễng, nhưng rất có vấn đề, và được cho là (tôi sẽ tranh luận) về mặt kiến ​​trúc bị thiếu sót bằng cách cung cấp một sự trừu tượng không đầy đủ và khó sử dụng.

Vào thời điểm bạn hoàn thành thiết kế tính năng ghi đè tĩnh để hoạt động độc đáo, bạn có thể đã phát minh ra một số dạng siêu dữ liệu, đây là phần mở rộng tự nhiên của OOP thành / cho các phương thức dựa trên lớp. Vì vậy, không có lý do gì để không làm điều này - và một số ngôn ngữ thực sự làm. Có lẽ đó chỉ là một chút yêu cầu bên lề mà một số ngôn ngữ chọn không làm.


Nếu tôi hiểu chính xác: đó không phải là vấn đề về mặt lý thuyết bạn sẽ muốn hay muốn làm một cái gì đó như thế này, nhưng hơn thế nữa, việc thực hiện nó sẽ khó khăn và không nhất thiết phải hữu ích?
PixelArtDragon

@Garan, Xem phụ lục của tôi.
Erik Eidt

1
Tôi không mua đối số đặt hàng; bạn nhận được phương thức được cung cấp bởi lớp mà bạn đã gọi phương thức trên (hoặc siêu lớp đầu tiên cung cấp phương thức theo tuyến tính hóa đã chọn của ngôn ngữ của bạn).
hobbs

1
@PierreArlaud: Nhưng this.instanceMethod()có độ phân giải động, vì vậy nếu self.staticMethod()có cùng độ phân giải, cụ thể là động, nó sẽ không còn là phương thức tĩnh nữa.
Jörg W Mittag

1
ghi đè ngữ nghĩa cho các phương thức tĩnh cần được thêm vào. Một siêu dữ liệu Java + giả thuyết không cần thêm bất cứ thứ gì! Bạn chỉ cần tạo các đối tượng lớp và các phương thức tĩnh sau đó trở thành các phương thức cá thể thông thường. Bạn không phải thêm một cái gì đó vào ngôn ngữ, bởi vì bạn chỉ sử dụng các khái niệm đã có sẵn: các đối tượng, các lớp và các phương thức cá thể. Như một phần thưởng, bạn thực sự có được để loại bỏ các phương thức tĩnh khỏi ngôn ngữ! Bạn có thể thêm một tính năng bằng cách loại bỏ một khái niệm và do đó phức tạp khỏi ngôn ngữ!
Jörg W Mittag

9

Ghi đè phụ thuộc vào công văn ảo: bạn sử dụng loại thời gian chạy của thistham số để quyết định phương thức nào sẽ gọi. Một phương thức tĩnh không có thistham số, vì vậy không có gì để gửi đi.

Một số ngôn ngữ, đặc biệt là Delphi và Python, có phạm vi "ở giữa" cho phép thực hiện điều này: các phương thức lớp. Một phương thức lớp không phải là một phương thức cá thể thông thường, nhưng nó cũng không tĩnh; nó nhận được một selftham số (thú vị là cả hai ngôn ngữ đều gọi thistham số self) đó là một tham chiếu đến chính loại đối tượng, chứ không phải là một thể hiện của loại đó. Với giá trị đó, bây giờ bạn có một loại có sẵn để thực hiện công văn ảo.

Thật không may, cả JVM và CLR đều không có gì có thể so sánh được.


Đây có phải là ngôn ngữ sử dụng selfcác lớp như các đối tượng thực tế, tức thời với các phương thức có thể ghi đè - Tất nhiên là Smalltalk, Ruby, Dart, Objective C, Self, Python? So với các ngôn ngữ đi sau C ++, trong đó các lớp không phải là đối tượng của lớp đầu tiên, ngay cả khi có một số truy cập phản chiếu?
Jerry101

Nói rõ hơn, bạn đang nói rằng trong Python hay Delphi, bạn có thể ghi đè các phương thức lớp không?
Erik Eidt

@ErikEidt Trong Python, hầu hết mọi thứ đều bị quá tải. Trong Delphi, một phương thức lớp có thể được khai báo rõ ràng virtualvà sau đó được ghi đè trong một lớp con cháu, giống như các phương thức cá thể.
Mason Wheeler

Vì vậy, những ngôn ngữ này đang cung cấp một số loại khái niệm về siêu dữ liệu, phải không?
Erik Eidt

1
@ErikEidt Python có siêu dữ liệu "thật". Trong Delphi, một tham chiếu lớp là một kiểu dữ liệu đặc biệt, không phải là một lớp trong chính nó.
Mason Wheeler

3

Bạn hỏi

Tại sao các hàm tĩnh không được ghi đè?

Tôi hỏi

Tại sao các chức năng bạn muốn ghi đè là tĩnh?

Một số ngôn ngữ nhất định buộc bạn phải bắt đầu chương trình theo phương thức tĩnh. Nhưng sau đó, bạn thực sự có thể giải quyết rất nhiều vấn đề mà không cần bất kỳ phương pháp tĩnh nào nữa.

Một số người thích sử dụng các phương thức tĩnh bất cứ lúc nào không có sự phụ thuộc vào trạng thái trong một đối tượng. Một số người thích sử dụng các phương thức tĩnh để xây dựng các đối tượng khác. Một số người thích tránh các phương thức tĩnh càng nhiều càng tốt.

Không ai trong số những người này là sai.

Nếu bạn cần nó quá mức, chỉ cần dừng ghi nhãn tĩnh. Nothings sẽ phá vỡ vì bạn có một vật thể phi trạng thái bay xung quanh.


Nếu ngôn ngữ hỗ trợ các cấu trúc, thì một loại đơn vị có thể là một cấu trúc có kích thước bằng không, được truyền cho phương thức nhập logic. Việc tạo ra đối tượng đó là một chi tiết thực hiện. Và nếu chúng ta bắt đầu nói về chi tiết triển khai là sự thật thực tế, thì tôi nghĩ bạn cũng cần xem xét rằng trong triển khai trong thế giới thực, loại không có kích thước bằng không thực sự phải được khởi tạo (vì không cần hướng dẫn để tải nó hoặc lưu trữ nó).
Theodoros Chatzigiannakis

Và nếu bạn đang nói nhiều hơn "đằng sau hậu trường" (ví dụ ở cấp độ thực thi), thì các đối số môi trường có thể được định nghĩa là một loại trong ngôn ngữ và điểm vào "thực" của chương trình có thể là một phương thức ví dụ của kiểu đó, hoạt động trên bất kỳ trường hợp nào đã được chuyển đến chương trình bởi trình tải hệ điều hành.
Theodoros Chatzigiannakis

Tôi nghĩ rằng nó phụ thuộc vào cách bạn nhìn vào nó. Ví dụ, khi một chương trình được khởi động, HĐH sẽ tải nó vào bộ nhớ và sau đó đi đến địa chỉ nơi thực hiện riêng lẻ chương trình của điểm nhập nhị phân (ví dụ: _start()ký hiệu trên Linux). Trong ví dụ đó, tệp thực thi là đối tượng (nó có "bảng điều phối" riêng và mọi thứ) và điểm vào là hoạt động được gửi động. Do đó, điểm vào của chương trình vốn là ảo khi được nhìn từ bên ngoài và nó có thể dễ dàng được tạo thành trông ảo khi nhìn từ bên trong.
Theodoros Chatzigiannakis

1
"Nothings sẽ phá vỡ vì bạn có một vật thể không quốc tịch bay xung quanh." ++++++
RubberDuck

@TheodorosChatzigiannakis bạn lập luận mạnh mẽ. nếu không có gì khác bạn đã thuyết phục tôi thì dòng không truyền cảm hứng cho dòng suy nghĩ tôi dự định. Tôi đã thực sự cố gắng cho phép mọi người nhún nhường một số thực hành theo thói quen hơn mà họ liên kết một cách mù quáng với các phương pháp tĩnh. Vì vậy, tôi đã cập nhật. Suy nghĩ?
candied_orange

2

Tại sao các phương thức tĩnh không thể có thể được ghi đè?

Đó không phải là một câu hỏi "nên".

"Ghi đè" có nghĩa là "gửi động ". "Phương thức tĩnh" có nghĩa là "gửi tĩnh". Nếu một cái gì đó là tĩnh, nó không thể bị ghi đè. Nếu một cái gì đó có thể được ghi đè, nó không tĩnh.

Câu hỏi của bạn khá giống với câu hỏi: "Tại sao xe ba bánh không thể có bốn bánh?" Các định nghĩa của "ba bánh" là có ba bánh xe. Nếu là xe ba bánh, nó không thể có bốn bánh, nếu có bốn bánh, nó không thể là xe ba bánh. Tương tự như vậy, định nghĩa của "phương thức tĩnh" là nó được gửi tĩnh. Nếu đó là một phương thức tĩnh, nó không thể được gửi đi một cách linh hoạt, nếu nó có thể được gửi đi một cách linh hoạt, nó không thể là một phương thức tĩnh.

Tất nhiên, hoàn toàn có thể có các phương thức lớp có thể được ghi đè. Hoặc, bạn có thể có một ngôn ngữ như Ruby, trong đó các lớp là các đối tượng giống như bất kỳ đối tượng nào khác và do đó có thể có các phương thức cá thể, loại bỏ hoàn toàn nhu cầu về các phương thức lớp hoàn toàn. (Ruby chỉ có một loại phương thức: phương thức cá thể. Nó không có phương thức lớp, phương thức tĩnh, hàm tạo, hàm hoặc thủ tục.)


1
"Theo định nghĩa" Vâng đặt.
candied_orange

1

do đó, các hàm tĩnh trong C # không thể là ảo hoặc trừu tượng

Trong C #, bạn luôn gọi các thành viên tĩnh bằng cách sử dụng lớp, ví dụ BaseClass.StaticMethod(), không baseObject.StaticMethod(). Vì vậy, cuối cùng, nếu bạn đã ChildClasskế thừa từ BaseClasschildObjectmột thể hiện của ChildClass, bạn sẽ không thể gọi phương thức tĩnh của bạn từ đó childObject. Bạn sẽ luôn cần phải sử dụng rõ ràng lớp thực, vì vậy static virtualchỉ là không có ý nghĩa.

Những gì bạn có thể làm là xác định lại staticphương thức tương tự trong lớp con của bạn và sử dụng newtừ khóa.

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

Nếu có thể gọi baseObject.StaticMethod(), thì câu hỏi của bạn sẽ có ý nghĩa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.