Là ghi đè phương pháp bê tông một mùi mã?


31

Có đúng là các phương pháp bê tông ghi đè là một mùi mã? Bởi vì tôi nghĩ rằng nếu bạn cần ghi đè các phương thức cụ thể:

public class A{
    public void a(){
    }
}

public class B extends A{
    @Override
    public void a(){
    }
}

nó có thể được viết lại như

public interface A{
    public void a();
}

public class ConcreteA implements A{
    public void a();
}

public class B implements A{
    public void a(){
    }
}

và nếu B muốn sử dụng lại a () trong A, nó có thể được viết lại thành:

public class B implements A{
    public ConcreteA concreteA;
    public void a(){
        concreteA.a();
    }
}

mà không yêu cầu kế thừa để ghi đè phương thức, điều đó có đúng không?



4
Bị từ chối vì (trong các bình luận bên dưới), Telastyn và Doc Brown đồng ý về việc ghi đè, nhưng đưa ra câu trả lời có / không đối với câu hỏi tiêu đề vì họ không đồng ý về ý nghĩa của "mùi mã". Vì vậy, một câu hỏi dành cho thiết kế mã đã được kết hợp chặt chẽ với một mẩu tiếng lóng. Và tôi nghĩ không cần thiết như vậy, vì câu hỏi cụ thể là về một kỹ thuật chống lại một kỹ thuật khác.
Steve Jessop

5
Câu hỏi không được gắn thẻ Java, nhưng mã dường như là Java. Trong C # (hoặc C ++), bạn phải sử dụng virtualtừ khóa để hỗ trợ ghi đè. Vì vậy, vấn đề được thực hiện nhiều hơn (hoặc ít hơn) mơ hồ bởi các chi tiết của ngôn ngữ.
Erik Eidt

1
Có vẻ như hầu hết mọi tính năng ngôn ngữ lập trình chắc chắn sẽ được phân loại là "mùi mã" sau đủ năm.
Brandon

2
Tôi cảm thấy như công cụ finalsửa đổi tồn tại chính xác cho các tình huống như thế này. Nếu hành vi của phương thức sao cho nó không được thiết kế để ghi đè, thì đánh dấu nó là final. Mặt khác, mong rằng các nhà phát triển có thể chọn ghi đè lên nó.
Martin Tuskevicius

Câu trả lời:


34

Không, nó không phải là một mùi mã.

  • Nếu một lớp không phải là cuối cùng, nó cho phép được phân lớp.
  • Nếu một phương thức không phải là cuối cùng, nó cho phép được ghi đè.

Nó nằm trong khả năng đáp ứng của từng lớp để xem xét cẩn thận nếu phân lớp là phù hợp và phương thức nào có thể được ghi đè .

Lớp có thể tự định nghĩa hoặc bất kỳ phương thức nào là cuối cùng hoặc có thể đặt các hạn chế (bộ sửa đổi mức độ hiển thị, các hàm tạo có sẵn) về cách thức và vị trí của nó được phân lớp.

Trường hợp thông thường cho các phương thức ghi đè là một triển khai mặc định trong lớp cơ sở có thể được tùy chỉnh hoặc tối ưu hóa trong lớp con (đặc biệt là trong Java 8 với sự xuất hiện của các phương thức mặc định trong các giao diện).

class A {
    public String getDescription(Element e) {
        // return default description for element
    }
}

class B extends A {
    public String getDescription(Element e) {
        // return customized description for element
    }
}

Một thay thế cho hành vi ghi đè là Mẫu chiến lược , trong đó hành vi được trừu tượng hóa như một giao diện và việc triển khai có thể được đặt trong lớp.

interface DescriptionProvider {
    String getDescription(Element e);
}

class A {
    private DescriptionProvider provider=new DefaultDescriptionProvider();

    public final String getDescription(Element e) {
       return provider.getDescription(e);
    }

    public final void setDescriptionProvider(@NotNull DescriptionProvider provider) {
        this.provider=provider;
    }
}

class B extends A {
    public B() {
        setDescriptionProvider(new CustomDescriptionProvider());
    }
}

Tôi hiểu rằng, trong thế giới thực, việc ghi đè một phương thức cụ thể có thể là mùi mã đối với một số người. Nhưng, tôi thích câu trả lời này vì nó có vẻ nhiều thông số hơn dựa vào tôi.
Darkwater23

Trong ví dụ cuối cùng của bạn, không nên Athực hiện DescriptionProvider?
Phát hiện

@ Spiated: A có thể triển khai DescriptionProvidervà do đó là nhà cung cấp mô tả mặc định. Nhưng ý tưởng ở đây là phân tách các mối quan tâm: Acần mô tả (một số lớp khác cũng có thể), trong khi các triển khai khác cung cấp chúng.
Peter Walser

2
Ok, âm thanh giống như mô hình chiến lược .
Phát hiện

@ Spiated: bạn nói đúng, ví dụ minh họa mẫu chiến lược, không phải mẫu trang trí (một người trang trí sẽ thêm hành vi cho một thành phần). Tôi sẽ sửa câu trả lời của tôi, cảm ơn.
Peter Walser

25

Có đúng là các phương pháp bê tông ghi đè là một mùi mã?

Vâng, nói chung, ghi đè các phương pháp cụ thể là một mùi mã.

Bởi vì phương thức cơ sở có một hành vi liên quan đến nó mà các nhà phát triển thường tôn trọng, việc thay đổi sẽ dẫn đến lỗi khi việc triển khai của bạn làm điều gì đó khác biệt. Tệ hơn, nếu họ thay đổi hành vi, việc thực hiện đúng trước đây của bạn có thể làm trầm trọng thêm vấn đề. Và nói chung, khá khó để tôn trọng Nguyên tắc thay thế Liskov cho các phương pháp cụ thể không tầm thường.

Bây giờ, có những trường hợp điều này có thể được thực hiện và là tốt. Các phương pháp bán tầm thường có thể được sử dụng một cách an toàn. Các phương thức cơ bản chỉ tồn tại dưới dạng một loại hành vi "mặc định lành mạnh" có nghĩa là quá mức có một số cách sử dụng tốt.

Do đó, điều này có vẻ như là một mùi đối với tôi - đôi khi tốt, thường là xấu, hãy xem để đảm bảo.


29
Giải thích của bạn là tốt, nhưng tôi đi đến kết luận ngược lại: "không, ghi đè các phương pháp cụ thể không phải là mùi mã nói chung" - nó chỉ là mùi mã khi người ta ghi đè lên các phương thức không được ghi đè. ;-)
Doc Brown

2
@DocBrown Chính xác! Tôi cũng thấy rằng lời giải thích (và đặc biệt là kết luận) phản đối tuyên bố ban đầu.
Tersizardos

1
@ Spiated: Ví dụ đơn giản nhất là một Numberlớp với một increment()phương thức đặt giá trị thành giá trị hợp lệ tiếp theo của nó. Nếu bạn phân lớp nó vào EvenNumbernơi increment()thêm hai thay vì một (và trình thiết lập thực thi tính đồng đều), đề xuất đầu tiên của OP yêu cầu triển khai trùng lặp mọi phương thức trong lớp và lần thứ hai của nó yêu cầu viết phương thức trình bao bọc để gọi các phương thức trong thành viên. Một phần của mục đích thừa kế là dành cho các lớp con để làm rõ chúng khác với cha mẹ bằng cách chỉ cung cấp các phương thức cần phải khác nhau.
Blrfl

5
@DocBrown: là một mùi mã không ngụ ý điều gì đó nhất thiết là xấu, chỉ có điều đó là có khả năng .
mikołak

3
@docbrown - Đối với tôi, điều này rơi cùng với "thành phần ưu tiên hơn thừa kế". Nếu bạn quá lạm dụng các phương pháp cụ thể, bạn đã ở vùng đất nhỏ bé để có quyền thừa kế. Tỷ lệ cược mà bạn đang vượt quá thứ gì đó bạn không nên là gì? Dựa trên kinh nghiệm của tôi, tôi nghĩ rằng nó có thể xảy ra. YMMV.
Telastyn

7

nếu bạn cần ghi đè các phương thức cụ thể [...] thì nó có thể được viết lại thành

Nếu nó có thể được viết lại theo cách đó có nghĩa là bạn có quyền kiểm soát cả hai lớp. Nhưng sau đó bạn biết liệu bạn đã thiết kế lớp A có nguồn gốc theo cách này hay không và nếu bạn đã làm, thì đó không phải là mùi mã.

Mặt khác, nếu bạn không có quyền kiểm soát lớp cơ sở, bạn không thể viết lại theo cách đó. Vì vậy, hoặc lớp được thiết kế để có nguồn gốc theo cách này, trong trường hợp chỉ đi trước, nó không phải là mùi mã, hoặc trong trường hợp đó bạn có hai lựa chọn: tìm kiếm một giải pháp khác hoàn toàn, hoặc đi trước , bởi vì làm việc trumps thiết kế tinh khiết.


Nếu lớp A sẽ được thiết kế để bắt nguồn, sẽ không có ý nghĩa hơn nếu có một phương thức trừu tượng để thể hiện rõ ràng ý định? Làm thế nào một người ghi đè phương thức có thể biết rằng phương thức được thiết kế theo cách này?
Phát hiện

@ Được phát hiện, có nhiều trường hợp cung cấp cam triển khai mặc định và mỗi chuyên môn chỉ cần ghi đè một số trong số chúng, để mở rộng chức năng hoặc cho hiệu quả. Ví dụ cực đoan là du khách. Người dùng biết làm thế nào các phương pháp được thiết kế từ tài liệu; nó phải ở đó để giải thích những gì được mong đợi của việc ghi đè theo bất kỳ cách nào.
Jan Hudec

Tôi hiểu quan điểm của bạn, nhưng tôi nghĩ thật nguy hiểm khi cách duy nhất mà nhà phát triển có, để biết một phương pháp có thể được ghi đè, bằng cách đọc tài liệu bởi vì: 1) nếu nó phải được ghi đè, tôi không đảm bảo rằng sẽ được thực hiện. 2) nếu không được ghi đè, ai đó sẽ làm điều đó vì sự thuận tiện của nó 3) không phải ai cũng đọc tài liệu. Ngoài ra, tôi không bao giờ phải đối mặt với trường hợp tôi cần ghi đè toàn bộ phương thức, mà chỉ các phần (và tôi đã kết thúc bằng cách sử dụng một mẫu mẫu).
Phát hiện

@ Spiated, 1) nếu nó phải được ghi đè, nó sẽ được abstract; nhưng có nhiều trường hợp không cần phải như vậy. 2) nếu nó không bị ghi đè, thì nó phải là final(không ảo). Nhưng vẫn còn nhiều trường hợp phương thức thể bị ghi đè. 3) nếu nhà phát triển xuôi dòng không đọc tài liệu, họ sẽ tự bắn vào chân mình, nhưng họ sẽ làm điều đó bất kể bạn áp dụng biện pháp nào.
Jan Hudec

Ok vì vậy quan điểm của chúng tôi chỉ phân kỳ trong 1 từ. Bạn nói rằng mọi phương thức nên trừu tượng hoặc cuối cùng trong khi tôi nói rằng mọi phương thức phải trừu tượng hoặc cuối cùng. :) Những trường hợp này khi ghi đè một phương thức là cần thiết (và an toàn) là gì?
Phát hiện

3

Trước tiên, hãy để tôi chỉ ra rằng câu hỏi này chỉ có thể áp dụng trong một số hệ thống gõ nhất định. Ví dụ, trong hệ thống gõ cấu trúc và hệ thống gõ Vịt - một lớp chỉ cần một phương thức có cùng tên và chữ ký sẽ có nghĩa là lớp đó tương thích với kiểu (ít nhất là đối với phương thức cụ thể đó, trong trường hợp gõ Vịt).

Điều này (rõ ràng) rất khác với lĩnh vực của các ngôn ngữ được nhập tĩnh , chẳng hạn như Java, C ++, v.v. Cũng như bản chất của gõ mạnh , như được sử dụng trong cả Java & C ++, cũng như C # và các ngôn ngữ khác.


Tôi đoán bạn đến từ nền Java, nơi các phương thức phải được đánh dấu rõ ràng là cuối cùng nếu người thiết kế hợp đồng dự định chúng không bị ghi đè. Tuy nhiên, trong các ngôn ngữ như C #, ngược lại là mặc định. Các phương thức (không có bất kỳ trang trí, từ khóa đặc biệt nào) "được niêm phong " (phiên bản của C # final) và phải được khai báo rõ ràng như virtualthể chúng được phép ghi đè.

Nếu một API hoặc thư viện đã được thiết kế trong các ngôn ngữ với một phương pháp cụ thể mà Overridable, sau đó nó phải (ít nhất là trong một số trường hợp (s)) có ý nghĩa cho nó sẽ được ghi đè. Do đó, nó không phải là một mùi mã để ghi đè lên một finalphương thức cụ thể / ảo / không cụ thể.     (Tất nhiên, điều này giả định rằng các nhà thiết kế API đang tuân theo các quy ước và đánh dấu là virtual(C #) hoặc đánh dấu là final(Java) các phương thức thích hợp cho những gì họ đang thiết kế.)


Xem thêm


Trong kiểu gõ vịt, hai lớp có cùng chữ ký phương thức có đủ để chúng tương thích với kiểu không, hoặc cũng cần các phương thức có cùng ngữ nghĩa, sao cho chúng có thể thay thế liskov cho một số lớp cơ sở ngụ ý?
Wayne Conrad

2

Có nó là bởi vì nó có thể dẫn đến siêu gọi chống mẫu. Nó cũng có thể làm mất mục đích ban đầu của phương pháp, đưa ra các tác dụng phụ (=> lỗi) và làm cho các thử nghiệm thất bại. Bạn sẽ sử dụng một lớp mà các bài kiểm tra đơn vị có màu đỏ (điền)? Tôi sẽ không.

Tôi sẽ đi xa hơn nữa bằng cách nói, mã mùi ban đầu là khi một phương pháp là không abstracthay final. Bởi vì nó cho phép thay đổi (bằng cách ghi đè) hành vi của một thực thể được xây dựng (hành vi này có thể bị mất hoặc thay đổi). Với abstractfinalý định là không rõ ràng:

  • Tóm tắt: bạn phải cung cấp một hành vi
  • Cuối cùng: bạn không được phép sửa đổi hành vi

Cách an toàn nhất để thêm hành vi vào một lớp hiện có là những gì ví dụ cuối cùng của bạn cho thấy: sử dụng mẫu trang trí.

public interface A{
    void a();
}

public final class ConcreteA implements A{
    public void a() {
        ...
    }
}

public final class B implements A{
    private final ConcreteA concreteA;
    public void a(){
        //Additionnal behaviour
        concreteA.a();
    }
}

9
Cách tiếp cận đen trắng này thực sự không hữu ích lắm. Hãy suy nghĩ về Object.toString()Java ... Nếu điều này là abstract, nhiều thứ mặc định sẽ không hoạt động và mã thậm chí sẽ không biên dịch khi ai đó không ghi đè toString()phương thức! Nếu đúng như vậy final, mọi thứ sẽ còn tồi tệ hơn - không có khả năng cho phép các đối tượng được chuyển đổi thành chuỗi, ngoại trừ cách Java. Như câu trả lời của @Peter Walser nói về, việc triển khai mặc định (như Object.toString()) rất quan trọng. Đặc biệt là trong các phương thức mặc định của Java 8.
Tersizardos

@Tersizardos Bạn nói đúng, nếu toString()abstracthoặc finalđó sẽ là một nỗi đau. Ý kiến ​​cá nhân của tôi là phương pháp này đã bị hỏng và tốt hơn hết là không nên sử dụng nó (như bằng và hashCode ). Mục đích ban đầu của các mặc định Java là cho phép tiến hóa API với tính tương thích retro.
Phát hiện

Từ "tương thích retro" có rất nhiều âm tiết.
Craig

@ Phát hiện Tôi rất thất vọng về sự chấp nhận chung về kế thừa cụ thể trên trang web này, tôi mong đợi tốt hơn: / Câu trả lời của bạn sẽ có nhiều lượt nâng cấp hơn
TheCatWhisperer

1
@TheCatWhisperer Tôi đồng ý, nhưng mặt khác tôi đã mất rất lâu để tìm ra những lợi thế của việc sử dụng trang trí trên thừa kế cụ thể (thực tế đã trở nên rõ ràng khi tôi bắt đầu viết bài kiểm tra trước) rằng bạn không thể đổ lỗi cho ai về điều đó . Điều tốt nhất để làm là cố gắng giáo dục những người khác cho đến khi họ khám phá ra Sự thật (trò đùa). :)
Phát hiện
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.