Xây dựng trong một giao diện?


148

Tôi biết không thể định nghĩa một hàm tạo trong một giao diện. Nhưng tôi tự hỏi tại sao, bởi vì tôi nghĩ nó có thể rất hữu ích.

Vì vậy, bạn có thể chắc chắn rằng một số trường trong một lớp được xác định cho mỗi lần thực hiện giao diện này.

Ví dụ, hãy xem xét lớp thông báo sau:

public class MyMessage {

   public MyMessage(String receiver) {
      this.receiver = receiver;
   }

   private String receiver;

   public void send() {
      //some implementation for sending the mssage to the receiver
   }
}

Nếu một định nghĩa một giao diện cho lớp này để tôi có thể có nhiều lớp hơn thực hiện giao diện thông báo, tôi chỉ có thể định nghĩa phương thức gửi chứ không phải phương thức khởi tạo. Vậy làm thế nào tôi có thể đảm bảo rằng mọi triển khai của lớp này thực sự có một bộ thu? Nếu tôi sử dụng một phương thức như setReceiver(String receiver)tôi không thể chắc chắn rằng phương thức này thực sự được gọi. Trong constructor tôi có thể đảm bảo nó.



2
Bạn nói "Trong hàm tạo tôi có thể đảm bảo [mọi triển khai của lớp này thực sự có một bộ thu]." Nhưng không, bạn không thể làm điều đó. Miễn là có thể định nghĩa một hàm tạo như vậy, tham số sẽ chỉ là một gợi ý mạnh mẽ cho những người triển khai của bạn - nhưng họ có thể chọn cách đơn giản bỏ qua nó nếu họ muốn.
Julien Silland

3
@mattb Umm, đó là một ngôn ngữ khác.
yesennes

Câu trả lời:


129

Lấy một số điều bạn đã mô tả:

"Vì vậy, bạn có thể chắc chắn rằng một số trường trong một lớp được xác định cho mỗi lần thực hiện giao diện này."

"Nếu một định nghĩa một Giao diện cho lớp này để tôi có thể có nhiều lớp hơn thực hiện giao diện thông báo, tôi chỉ có thể xác định phương thức gửi chứ không phải là hàm tạo"

... những yêu cầu này chính xác là những gì các lớp trừu tượng dành cho.


nhưng lưu ý rằng ca sử dụng @Sebi mô tả (gọi các phương thức quá tải từ các hàm tạo cha mẹ) là một ý tưởng tồi như được giải thích trong câu trả lời của tôi.
rsp

44
Matt, điều đó rõ ràng đúng, nhưng các lớp trừu tượng phải chịu giới hạn thừa kế đơn, khiến mọi người nhìn vào các cách khác để xác định thứ bậc.
CPerkins

6
Điều này là đúng và có thể giải quyết vấn đề tức thời của Sebi. Nhưng một lý do để sử dụng các giao diện trong Java là vì bạn không thể có nhiều kế thừa. Trong trường hợp tôi không thể biến "thứ" của mình thành một lớp trừu tượng bởi vì tôi cần kế thừa từ thứ khác, vấn đề vẫn còn. Không phải là tôi tuyên bố có một giải pháp.
Jay

7
@CPerkins trong khi điều này là đúng, tôi không gợi ý rằng chỉ cần sử dụng một lớp trừu tượng sẽ giải quyết trường hợp sử dụng của Sebi. Nếu có bất cứ điều gì, tốt nhất là khai báo một Messagegiao diện xác định send()phương thức và nếu Sebi muốn cung cấp một lớp "cơ sở" để triển khai Messagegiao diện, thì cũng cung cấp một giao diện AbstractMessage. Các lớp trừu tượng không nên thay thế các giao diện, không bao giờ cố gắng đề xuất như vậy.
matt b

2
Hiểu rồi, Matt. Tôi đã không tranh cãi với bạn, chỉ ra rằng đó không phải là sự thay thế hoàn toàn cho những gì op muốn.
CPerkins

76

Một vấn đề mà bạn gặp phải khi bạn cho phép các nhà xây dựng trong các giao diện xuất phát từ khả năng thực hiện một số giao diện cùng một lúc. Khi một lớp thực hiện một số giao diện xác định các hàm tạo khác nhau, lớp sẽ phải thực hiện một số hàm tạo, mỗi lớp chỉ đáp ứng một giao diện chứ không phải các giao diện khác. Sẽ không thể xây dựng một đối tượng gọi từng hàm tạo này.

Hoặc trong mã:

interface Named { Named(String name); }
interface HasList { HasList(List list); }

class A implements Named, HasList {

  /** implements Named constructor.
   * This constructor should not be used from outside, 
   * because List parameter is missing
   */
  public A(String name)  { 
    ...
  }

  /** implements HasList constructor.
   * This constructor should not be used from outside, 
   * because String parameter is missing
   */
  public A(List list) {
    ...
  }

  /** This is the constructor that we would actually 
   * need to satisfy both interfaces at the same time
   */ 
  public A(String name, List list) {
    this(name);
    // the next line is illegal; you can only call one other super constructor
    this(list); 
  }
}

1
Không thể ngôn ngữ đã làm điều đó bằng cách cho phép những thứ nhưclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako

1
Ý nghĩa hữu ích nhất cho "hàm tạo trong giao diện", nếu được phép, sẽ là nếu new Set<Fnord>()có thể được hiểu là "Hãy cho tôi thứ gì đó tôi có thể sử dụng như một Set<Fnord>"; nếu tác giả của Set<T>dự định HashSet<T>thực hiện cho những thứ không có nhu cầu đặc biệt cho thứ khác, thì giao diện có thể được xác định new Set<Fnord>()có thể được coi là đồng nghĩa với new HashSet<Fnord>(). Đối với một lớp để thực hiện nhiều giao diện sẽ không gây ra bất kỳ vấn đề nào, vì new InterfaceName()đơn giản là sẽ xây dựng một lớp được chỉ định bởi giao diện .
supercat

Đối số đối lập: hàm tạo của bạn A(String,List)có thể là hàm tạo được chỉ định A(String)A(List)có thể là hàm thứ cấp gọi nó. Mã của bạn không phải là một ví dụ ngược lại, chỉ là một ví dụ kém.
Ben Leggiero

Tại sao bạn gọi tất cả các nhà xây dựng trong một triển khai?! Có, nếu nó triển khai thêm Giao diện với ctor, một với Chuỗi và một với int, nó cần hai bộ đệm - nhưng có thể hoặc có thể được sử dụng. Nếu điều đó không áp dụng, lớp chỉ đơn giản là không thực hiện cả Giao diện. Vậy thì sao!? (có những lý do khác để không có ctor trong Giao diện mặc dù).
kai

@kai Không, nó cần gọi cả hai hàm tạo giao diện khi xây dựng một thể hiện. Nói cách khác: Trong ví dụ của tôi, thể hiện có cả tên và danh sách, vì vậy mỗi trường hợp cần khởi tạo cả tên và danh sách.
daniel kullmann

13

Giao diện xác định hợp đồng cho API, đó là một tập hợp các phương thức mà cả người thực hiện và người dùng API đều đồng ý. Một giao diện không có triển khai thực hiện, do đó không có hàm tạo.

Trường hợp sử dụng mà bạn mô tả gần giống với một lớp trừu tượng trong đó hàm tạo gọi một phương thức của một phương thức trừu tượng được triển khai trong một lớp con.

Vấn đề cố hữu ở đây là trong khi hàm tạo cơ sở đang được thực thi, thì đối tượng con chưa được xây dựng và do đó trong một trạng thái không thể đoán trước.

Tóm lại: có phải nó đang gặp rắc rối khi bạn gọi các phương thức quá tải từ các hàm tạo cha mẹ, để trích dẫn mindprod :

Nói chung, bạn phải tránh gọi bất kỳ phương thức không cuối cùng nào trong hàm tạo. Vấn đề là các trình khởi tạo cá thể / khởi tạo biến trong lớp dẫn xuất được thực hiện sau hàm tạo của lớp cơ sở.


6

Một công việc xung quanh bạn có thể thử là xác định một getInstance()phương thức trong giao diện của bạn để người thực hiện nhận thức được những tham số nào cần được xử lý. Nó không vững chắc như một lớp trừu tượng, nhưng nó cho phép linh hoạt hơn như là một giao diện.

Tuy nhiên, cách giải quyết này yêu cầu bạn sử dụng getInstance()để khởi tạo tất cả các đối tượng của giao diện này.

Ví dụ

public interface Module {
    Module getInstance(Receiver receiver);
}

5

Chỉ có các trường tĩnh trong giao diện không cần khởi tạo trong khi tạo đối tượng trong lớp con và phương thức giao diện phải cung cấp triển khai thực tế trong lớp con. Vì vậy, không cần xây dựng trong giao diện.

Lý do thứ hai - trong quá trình tạo đối tượng của lớp con, hàm tạo cha mẹ được gọi. Nhưng nếu có nhiều giao diện được triển khai thì sẽ xảy ra xung đột trong khi gọi hàm tạo của giao diện mà hàm tạo của giao diện sẽ gọi trước


3

Nếu bạn muốn đảm bảo rằng mọi triển khai của giao diện đều chứa trường cụ thể, bạn chỉ cần thêm vào giao diện của mình trình getter cho trường đó :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • nó sẽ không phá vỡ đóng gói
  • nó sẽ thông báo cho mọi người sử dụng giao diện của bạn rằng Receiverđối tượng phải được chuyển đến lớp theo một cách nào đó (bằng cách xây dựng hoặc bằng setter)

2

Các phụ thuộc không được tham chiếu trong các phương thức giao diện nên được coi là chi tiết triển khai, không phải là thứ mà giao diện thực thi. Tất nhiên có thể có ngoại lệ, nhưng theo quy định, bạn nên xác định giao diện của mình là hành vi được mong đợi. Trạng thái bên trong của một triển khai nhất định không nên là mối quan tâm thiết kế của giao diện.


1

Xem câu hỏi này cho lý do tại sao (lấy từ các ý kiến).

Nếu bạn thực sự cần phải làm một cái gì đó như thế này, bạn có thể muốn một lớp cơ sở trừu tượng hơn là một giao diện.


1

Điều này là do các giao diện không cho phép định nghĩa thân phương thức trong nó. Nhưng chúng ta phải định nghĩa hàm tạo trong cùng lớp với các giao diện theo mặc định của bộ sửa đổi trừu tượng cho tất cả các phương thức để định nghĩa. Đó là lý do tại sao chúng ta không thể định nghĩa hàm tạo trong các giao diện.


0

Dưới đây là một ví dụ sử dụng Technic này. Trong ví dụ cụ thể này, mã đang thực hiện một cuộc gọi đến Firebase bằng cách sử dụng một giả MyCompletionListenercó giao diện được che dấu dưới dạng một lớp trừu tượng, một giao diện với hàm tạo

private interface Listener {
    void onComplete(databaseError, databaseReference);
}

public abstract class MyCompletionListener implements Listener{
    String id;
    String name;
    public MyCompletionListener(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

private void removeUserPresenceOnCurrentItem() {
    mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
        @Override
        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {

        }
    });
    }
}

@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
    CompletionListener cListener = new CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (var1 != null){
                        System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
                        var1.onComplete(databaseError, databaseReference);
                    }
                }
            };
    ref.removeValue(cListener);
}

Làm thế nào bạn có thể sử dụng privatesửa đổi truy cập với interface?
Rudra

0

Nói chung các hàm tạo là để khởi tạo các thành viên không tĩnh của lớp cụ thể đối với đối tượng.

Không có tạo đối tượng cho giao diện vì chỉ có các phương thức khai báo nhưng không xác định phương thức. Tại sao chúng ta không thể tạo đối tượng cho các phương thức được khai báo là - việc tạo đối tượng không là gì ngoài việc phân bổ một số bộ nhớ (trong bộ nhớ heap) cho các thành viên không tĩnh.

JVM sẽ tạo bộ nhớ cho các thành viên được phát triển đầy đủ và sẵn sàng sử dụng. Dựa trên các thành viên đó, JVM tính toán lượng bộ nhớ cần thiết cho họ và tạo bộ nhớ.

Trong trường hợp các phương thức được khai báo, JVM không thể tính được dung lượng bộ nhớ cần thiết cho các phương thức được khai báo này vì việc triển khai sẽ diễn ra trong tương lai mà không được thực hiện vào thời điểm này. vì vậy việc tạo đối tượng là không thể cho giao diện.

phần kết luận:

không có đối tượng tạo, sẽ không có cơ hội để khởi tạo các thành viên không tĩnh thông qua một hàm tạo. Đó là lý do tại sao hàm tạo không được phép bên trong một giao diện. (vì không sử dụng hàm tạo bên trong một giao diệ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.