Tại sao bắt buộc phải có từ khóa ghi đè trước các phương thức trừu tượng khi chúng ta triển khai chúng trong một lớp con?


9

Khi chúng ta tạo một lớp kế thừa từ một lớp trừu tượng và khi chúng ta triển khai lớp trừu tượng được kế thừa tại sao chúng ta phải sử dụng từ khóa ghi đè?

public abstract class Person
{
    public Person()
    {

    }

    protected virtual void Greet()
    {
        // code
    }

    protected abstract void SayHello();
}

public class Employee : Person
{
    protected override void SayHello() // Why is override keyword necessary here?
    {
        throw new NotImplementedException();
    }

    protected override void Greet()
    {
        base.Greet();
    }
}

Vì phương thức được khai báo là trừu tượng trong lớp cha của nó nên nó không có bất kỳ triển khai nào trong lớp cha, vậy tại sao từ khóa lại cần thiết ở đây?


2
Một phương thức trừu tượng hoàn toàn là một phương thức ảo, theo thông số kỹ thuật
— Pavel Anikhouski


"Công cụ sửa đổi ghi đè được yêu cầu mở rộng hoặc sửa đổi triển khai trừu tượng hoặc ảo của phương thức, thuộc tính, bộ chỉ mục hoặc sự kiện được kế thừa." docs.microsoft.com/en-us/dotnet/csharp/lingu-reference/ mẹo
— gunr2171

2
Bởi vì nếu bạn không đặt nó ở đó, bạn sẽ "ẩn" phương thức thay vì ghi đè lên nó. Đó là lý do tại sao bạn nhận được cảnh báo rằng "nếu đó là những gì bạn dự định, hãy sử dụng từ newkhóa` ... Bạn cũng sẽ nhận được lỗi" không có phương thức ghi đè phương thức lớp cơ sở ".
— Ron Beyer

@RonBeyer với một phương thức ảo có, nhưng với trừu tượng thì đơn giản là nó sẽ không biên dịch.
— Johnathan Barclay

Câu trả lời:


14

Khi chúng ta tạo một lớp kế thừa từ một lớp trừu tượng và khi chúng ta triển khai lớp trừu tượng được kế thừa tại sao chúng ta phải sử dụng từ khóa ghi đè?

"Tại sao?" những câu hỏi như thế này có thể khó trả lời vì chúng mơ hồ. Tôi sẽ giả định rằng câu hỏi của bạn là "những lập luận nào có thể được đưa ra trong quá trình thiết kế ngôn ngữ để tranh luận cho vị trí mà overridetừ khóa được yêu cầu ?"

Hãy bắt đầu bằng cách lùi lại một bước. Trong một số ngôn ngữ, giả sử, Java, các phương thức là ảo theo mặc định và được ghi đè tự động. Các nhà thiết kế của C # đã nhận thức được điều này và coi đó là một lỗ hổng nhỏ trong Java. C # không phải là "Java với những phần ngu ngốc bị loại bỏ" như một số người đã nói, nhưng các nhà thiết kế của C # rất muốn học hỏi từ các điểm thiết kế có vấn đề của C, C ++ và Java và không sao chép chúng trong C #.

Các nhà thiết kế C # coi việc ghi đè là một nguồn có thể có lỗi; xét cho cùng, đó là một cách để thay đổi hành vi của mã đã được thử nghiệm hiện có và điều đó thật nguy hiểm. Ghi đè không phải là điều nên được thực hiện ngẫu nhiên hoặc tình cờ; nó phải được thiết kế bởi một người nào đó suy nghĩ rất nhiều về nó . Đó là lý do tại sao các phương thức không ảo theo mặc định và tại sao bạn bắt buộc phải nói rằng bạn đang ghi đè một phương thức.

Đó là lý do cơ bản. Bây giờ chúng ta có thể đi vào một số lý luận nâng cao hơn.

Câu trả lời của StriplingWar Warrior đưa ra một cắt giảm tốt trong việc đưa ra một lập luận nâng cao hơn. Tác giả của lớp dẫn xuất có thể không hiểu rõ về lớp cơ sở, có thể có ý định tạo một phương thức mới và chúng ta không nên cho phép người dùng ghi đè do nhầm lẫn .

Mặc dù điểm này là hợp lý, có một số phản biện, chẳng hạn như:

  • Tác giả của một lớp dẫn xuất có trách nhiệm biết mọi thứ về lớp cơ sở! Họ đang sử dụng lại mã đó và họ cần phải thực hiện kỹ lưỡng để hiểu kỹ mã đó trước khi sử dụng lại mã đó.
  • Trong kịch bản cụ thể của bạn, phương thức ảo là trừu tượng; Sẽ là một lỗi nếu không ghi đè lên nó, và do đó không chắc là tác giả sẽ tạo ra một triển khai một cách tình cờ.

Sau đó, hãy đưa ra một lập luận thậm chí nâng cao hơn về điểm này. Trong hoàn cảnh nào, tác giả của một lớp dẫn xuất có thể được miễn vì không biết lớp cơ sở làm gì? Vâng, hãy xem xét kịch bản này:

  • Tác giả lớp cơ sở tạo ra một lớp cơ sở trừu tượng B.
  • Tác giả lớp dẫn xuất, trên một nhóm khác, tạo lớp D dẫn xuất với phương thức M.
  • Tác giả lớp cơ sở nhận ra rằng các nhóm mở rộng lớp cơ sở B sẽ luôn cần cung cấp một phương thức M, vì vậy tác giả của lớp cơ sở thêm phương thức trừu tượng M.
  • Khi lớp D được biên dịch lại, điều gì xảy ra?

Điều chúng tôi muốn xảy ra là tác giả của D được thông báo rằng một cái gì đó có liên quan đã thay đổi . Điều liên quan đã thay đổi là M bây giờ là một yêu cầu và việc thực hiện chúng phải bị quá tải. DM có thể cần thay đổi hành vi của nó một khi chúng ta biết rằng nó có thể được gọi từ lớp cơ sở. Điều đúng đắn cần làm là không âm thầm nói "oh, DM tồn tại và mở rộng BM". Điều chính xác để trình biên dịch thực hiện là thất bại và nói "hey, tác giả của D, hãy xem giả định này của bạn không còn hợp lệ và sửa mã của bạn nếu cần".

Trong ví dụ của bạn, giả sử overridelà tùy chọn trên SayHellovì nó đang ghi đè một phương thức trừu tượng. Có hai khả năng: (1) tác giả của mã dự định ghi đè một phương thức trừu tượng hoặc (2) phương thức ghi đè bị ghi đè do vô tình vì người khác đã thay đổi lớp cơ sở và mã hiện bị sai theo một cách tinh tế. Chúng tôi không thể phân biệt các khả năng này nếu overridelà tùy chọn .

Nhưng nếu overrideđược yêu cầu thì chúng ta có thể phân biệt ba kịch bản. Nếu có là một sai lầm có thể trong các mã sau đó overridelà mất tích . Nếu nó cố ý ghi đè thì overridecó mặt . Và nếu nó cố ý không ghi đè thì newcó mặt . Thiết kế của C # cho phép chúng tôi tạo ra những khác biệt tinh tế.

Ghi nhớ báo cáo trình biên dịch yêu cầu đọc suy nghĩ của nhà phát triển ; trình biên dịch phải suy ra từ mã sai mã chính xác mà tác giả có thể có trong tâm trí và đưa ra một lỗi chỉ ra chúng theo đúng hướng. Càng có nhiều manh mối, chúng tôi có thể khiến nhà phát triển để lại mã về những gì họ đã nghĩ, trình biên dịch có thể làm tốt hơn trong báo cáo lỗi và do đó bạn có thể tìm và sửa lỗi nhanh hơn.

Nhưng nói chung, C # được thiết kế cho một thế giới trong đó mã thay đổi . Thực tế, có rất nhiều tính năng của C # xuất hiện "kỳ quặc" vì chúng thông báo cho nhà phát triển khi một giả định được sử dụng là hợp lệ đã trở thành không hợp lệ vì một lớp cơ sở đã thay đổi. Lớp lỗi này được gọi là "lỗi lớp cơ sở dễ vỡ" và C # có một số giảm thiểu thú vị cho lớp lỗi này.


4
Cảm ơn đã xây dựng. Tôi luôn đánh giá cao câu trả lời của bạn, bởi vì thật tốt khi có một giọng nói có thẩm quyền từ ai đó "ở bên trong" và bởi vì bạn làm rất tốt việc giải thích mọi thứ một cách đơn giản và hoàn toàn.
— StriplingWar Warrior

Cảm ơn bạn đã giải thích sâu sắc! thực sự đánh giá cao nó.
— psj01

Điểm tuyệt vời! Bạn có thể liệt kê một số giảm nhẹ khác mà bạn đề cập?
— aksh1618

3

Đó là xác định xem bạn đang cố gắng ghi đè phương thức khác trong lớp cha hay tạo một triển khai mới duy nhất cho cấp phân cấp lớp này. Có thể hình dung rằng một lập trình viên có thể không nhận thức được sự tồn tại của một phương thức trong lớp cha có chữ ký chính xác giống như phương thức họ tạo trong lớp, điều này có thể dẫn đến một số bất ngờ khó chịu.

Mặc dù đúng là một phương thức trừu tượng phải được ghi đè trong một lớp con không trừu tượng, nhưng những người chế tạo C # có lẽ cảm thấy vẫn tốt hơn khi nói rõ hơn về những gì bạn đang cố gắng làm.


Đây là một khởi đầu tốt để hiểu lý do của nhóm thiết kế ngôn ngữ; Tôi đã thêm một câu trả lời cho thấy đội bắt đầu như thế nào với ý tưởng bạn đã bày tỏ ở đây nhưng đưa nó đi một bước xa hơn.
— Eric Lippert

1

Bởi vì abstractphương thức là một phương thức ảo không có triển khai, theo đặc tả ngôn ngữ C # , có nghĩa là phương thức trừu tượng đó hoàn toàn là một phương thức ảo. Và overrideđược sử dụng để mở rộng hoặc sửa đổi triển khai trừu tượng hoặc ảo, như bạn có thể thấy ở đây

Để định nghĩa lại một chút - bạn sử dụng các phương thức ảo để thực hiện một số loại ràng buộc muộn, trong khi các phương thức trừu tượng buộc các lớp con của loại phải có phương thức được ghi đè rõ ràng. Đó là điểm, khi phương thức là virtual, nó có thể bị ghi đè, khi nó là abstract- nó phải được ghi đè


1
Tôi nghĩ rằng câu hỏi không liên quan đến những gì nó làm, nhưng tại sao nó phải rõ ràng.
— Johnathan Barclay

0

Để thêm vào câu trả lời của @ StriplingWar Warrior, tôi nghĩ rằng nó cũng đã được thực hiện để có một cú pháp phù hợp với việc ghi đè một phương thức ảo trong lớp cơ sở.

public abstract class MyBase
{
    public virtual void MyVirtualMethod() { }

    public virtual void MyOtherVirtualMethod() { }

    public abstract void MyAbtractMethod();
}

public class MyDerived : MyBase
{
    // When overriding a virtual method in MyBase, we use the override keyword.
    public override void MyVirtualMethod() { }

    // If we want to hide the virtual method in MyBase, we use the new keyword.
    public new void MyOtherVirtualMethod() { }

    // Because MyAbtractMethod is abstract in MyBase, we have to override it: 
    // we can't hide it with new.
    // For consistency with overriding a virtual method, we also use the override keyword.
    public override void MyAbtractMethod() { }
}

Vì vậy, C # có thể đã được thiết kế để bạn không cần từ khóa ghi đè để ghi đè các phương thức trừu tượng, nhưng tôi nghĩ các nhà thiết kế đã quyết định rằng sẽ gây nhầm lẫn vì nó không phù hợp với việc ghi đè phương thức ảo.


Re: "các nhà thiết kế đã quyết định rằng sẽ gây nhầm lẫn vì nó sẽ không phù hợp với việc ghi đè một phương thức ảo" - vâng, nhưng nhiều hơn nữa. Giả sử bạn có một lớp cơ sở B với một phương thức ảo M và một lớp D dẫn xuất với một ghi đè. Bây giờ giả sử tác giả của B quyết định làm cho M trừu tượng. Đó là một sự thay đổi đột phá, nhưng có lẽ họ làm như vậy. Câu hỏi: có nên yêu cầu tác giả của D để xóa override? Tôi nghĩ rằng hầu hết mọi người sẽ đồng ý rằng thật vô lý khi buộc tác giả của D thực hiện thay đổi mã không cần thiết; lớp học của họ là tốt!
— Eric Lippert
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 cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.