Java - Xung đột tên phương thức trong triển khai giao diện


88

Nếu tôi có hai giao diện, cả hai đều hoàn toàn khác nhau về mục đích của chúng, nhưng có cùng một chữ ký phương thức, làm cách nào để tạo một lớp triển khai cả hai mà không bị buộc phải viết một phương thức phục vụ cho cả hai giao diện và viết một số logic phức tạp trong phương thức triển khai kiểm tra loại đối tượng nào mà cuộc gọi đang được thực hiện và gọi mã thích hợp?

Trong C #, điều này được khắc phục bằng cái được gọi là triển khai giao diện rõ ràng. Có cách nào tương đương trong Java không?


37
Khi một lớp phải triển khai hai phương thức với cùng một chữ ký thực hiện những việc khác nhau , thì lớp của bạn gần như chắc chắn đang làm quá nhiều thứ.
Joachim Sauer

15
Điều trên có thể không phải lúc nào IMO cũng đúng. Đôi khi, trong một lớp đơn lẻ, bạn cần các phương thức phải xác nhận với một hợp đồng bên ngoài (do đó hạn chế về chữ ký), nhưng có các cách triển khai khác nhau. Trên thực tế, đây là những yêu cầu chung khi thiết kế một lớp không tầm thường. Ghi đè và ghi đè nhất thiết là các cơ chế cho phép các phương thức thực hiện những việc khác nhau có thể không khác nhau về chữ ký hoặc chỉ khác một chút. Những gì tôi có ở đây chỉ là hạn chế hơn một chút là nó không cho phép phân lớp / và thậm chí không cho phép sự thay đổi nhỏ nhất về chữ ký.
Bhaskar

1
Tôi rất muốn biết những lớp và phương thức này là gì.
Uri

2
Tôi đã gặp trường hợp như vậy trong đó lớp "Địa chỉ" kế thừa đã triển khai giao diện Người và Công ty có phương thức getName () chỉ đơn giản là trả về một Chuỗi từ mô hình dữ liệu. Một yêu cầu nghiệp vụ mới đã chỉ định rằng Person.getName () trả về một Chuỗi được định dạng là "Họ, Tên đã đặt". Sau nhiều cuộc thảo luận, dữ liệu đã được định dạng lại trong cơ sở dữ liệu.
belwood

12
Chỉ nói rằng lớp học gần như chắc chắn đang làm quá nhiều thứ là KHÔNG ĐƯỢC HƯỚNG DẪN. Tôi đã gặp trường hợp này ngay bây giờ rằng lớp của tôi có xung đột tên mehod từ 2 giao diện khác nhau và lớp của tôi KHÔNG làm quá nhiều thứ. Các mục đích khá giống nhau, nhưng làm những điều hơi khác nhau. Đừng cố bảo vệ một ngôn ngữ lập trình có khuyết tật nghiêm trọng bằng cách buộc tội người đặt câu hỏi thực hiện thiết kế phần mềm tồi!
j00hi

Câu trả lời:


75

Không, không có cách nào để triển khai cùng một phương thức theo hai cách khác nhau trong một lớp trong Java.

Điều đó có thể dẫn đến nhiều tình huống khó hiểu, đó là lý do tại sao Java không cho phép nó.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

class Impl implements ISomething, ISomething2 {
   void doSomething() {} // There can only be one implementation of this method.
}

Những gì bạn có thể làm là soạn một lớp trong số hai lớp mà mỗi lớp triển khai một giao diện khác nhau. Sau đó, một lớp đó sẽ có hành vi của cả hai giao diện.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}

9
Nhưng theo cách này, tôi không thể chuyển một phiên bản của CompositeClass vào đâu đó một tham chiếu của các giao diện (ISomething hoặc ISomething2) được mong đợi? Tôi thậm chí không thể mong đợi mã máy khách có thể truyền phiên bản của tôi sang giao diện thích hợp, vì vậy tôi không đánh mất điều gì đó bởi hạn chế này? Cũng lưu ý rằng theo cách này, khi viết các lớp thực sự triển khai các giao diện tương ứng, chúng ta mất lợi ích của việc đưa mã vào một lớp duy nhất, đôi khi có thể là một trở ngại nghiêm trọng.
Bhaskar

9
@Bhaskar, bạn kiếm được điểm hợp lệ. Lời khuyên tốt nhất mà tôi có là thêm một ISomething1 CompositeClass.asInterface1();ISomething2 CompositeClass.asInterface2();phương thức vào lớp đó. Sau đó, bạn chỉ có thể lấy một hoặc khác từ lớp tổng hợp. Không có giải pháp tuyệt vời cho vấn đề này mặc dù.
jjnguy

1
Nói về những tình huống khó hiểu mà điều này có thể dẫn đến, bạn có thể cho một ví dụ không? Chúng ta có thể không nghĩ về tên giao diện được thêm vào tên phương thức như một độ phân giải phạm vi bổ sung mà sau đó có thể tránh va chạm / nhầm lẫn không?
Bhaskar

@Bhaskar Sẽ tốt hơn nếu các lớp của chúng ta tuân thủ nguyên tắc trách nhiệm duy nhất. Nếu tồn tại một lớp thực hiện hai giao diện rất khác nhau, tôi nghĩ rằng thiết kế nên được làm lại để phân chia các lớp để đảm nhận trách nhiệm duy nhất.
Anirudhan J

1
Thực sự sẽ khó hiểu đến mức nào khi cho phép một cái gì đó như public long getCountAsLong() implements interface2.getCount {...}[trong trường hợp giao diện yêu cầu longnhưng người dùng của lớp mong đợi int] hoặc private void AddStub(T newObj) implements coolectionInterface.Add[giả sử collectionInterfacecó một canAdd()phương thức và đối với tất cả các trường hợp của lớp này, nó trả về false]?
supercat

13

Không có cách thực sự nào để giải quyết vấn đề này trong Java. Bạn có thể sử dụng các lớp bên trong như một giải pháp thay thế:

interface Alfa { void m(); }
interface Beta { void m(); }
class AlfaBeta implements Alfa {
    private int value;
    public void m() { ++value; } // Alfa.m()
    public Beta asBeta() {
        return new Beta(){
            public void m() { --value; } // Beta.m()
        };
    }
}

Mặc dù nó không cho phép truyền từ AlfaBetađến Beta, nhưng các trường hợp bị hạ gục nói chung là xấu và nếu có thể mong đợi rằng một Alfaphiên bản thường có một Betakhía cạnh, và vì lý do nào đó (thường tối ưu hóa là lý do hợp lệ duy nhất) bạn muốn để chuyển đổi nó thành Beta, bạn có thể tạo một giao diện phụ Alfavới Beta asBeta()trong đó.


Ý bạn là lớp ẩn danh chứ không phải lớp bên trong?
Zaid Masud

2
@ZaidMasud Ý tôi là các lớp bên trong, vì chúng có thể truy cập trạng thái riêng tư của đối tượng bao quanh). Tất nhiên, các lớp bên trong này cũng có thể ẩn danh.
gustafc

11

Nếu bạn đang gặp phải sự cố này, rất có thể là do bạn đang sử dụng tính năng kế thừa , nơi bạn nên sử dụng ủy quyền . Nếu bạn cần cung cấp hai giao diện khác nhau, mặc dù giống nhau, cho cùng một mô hình dữ liệu cơ bản, thì bạn nên sử dụng một dạng xem để cung cấp quyền truy cập vào dữ liệu một cách rẻ tiền bằng một số giao diện khác.

Để đưa ra một ví dụ cụ thể cho trường hợp sau, giả sử bạn muốn triển khai cả CollectionMyCollection(không kế thừa từ Collectionvà có giao diện không tương thích). Bạn có thể cung cấp một Collection getCollectionView()và các MyCollection getMyCollectionView()hàm cung cấp triển khai nhẹ nhàng CollectionMyCollectionsử dụng cùng một dữ liệu cơ bản.

Đối với trường hợp trước đây ... giả sử bạn thực sự muốn một mảng số nguyên và một mảng chuỗi. Thay vì kế thừa từ cả hai List<Integer>List<String>, bạn nên có một thành viên cùng loại List<Integer>và một thành viên khác cùng loại List<String>, và tham chiếu đến các thành viên đó, thay vì cố gắng kế thừa từ cả hai. Ngay cả khi bạn chỉ cần một danh sách các số nguyên, tốt hơn nên sử dụng thành phần / ủy quyền thay vì kế thừa trong trường hợp này.


Tôi không nghĩ vậy. Bạn đang quên các thư viện yêu cầu bạn triển khai các giao diện khác nhau để tương thích với chúng. Bạn có thể gặp phải điều này bằng cách sử dụng nhiều thư viện xung đột thường xuyên hơn sau đó bạn gặp phải điều này trong mã của riêng bạn.
nightpool vào

1
@nightpool nếu bạn sử dụng nhiều thư viện mà mỗi thư viện yêu cầu các giao diện khác nhau, thì vẫn không cần một đối tượng duy nhất để triển khai cả hai giao diện; bạn có thể yêu cầu đối tượng có trình truy cập để trả về từng giao diện trong số hai giao diện khác nhau (và gọi trình truy cập thích hợp khi truyền đối tượng tới một trong các thư viện bên dưới).
Michael Aaron Safyan

1

Vấn đề Java "cổ điển" cũng ảnh hưởng đến sự phát triển Android của tôi ...
Lý do có vẻ rất đơn giản:
Bạn phải sử dụng nhiều framework / thư viện hơn, mọi thứ dễ dàng vượt quá tầm kiểm soát hơn ...

Trong trường hợp của tôi, tôi có một lớp BootStrapperApp kế thừa từ android.app.Application ,
trong khi cùng một lớp cũng nên triển khai giao diện Nền tảng của khung MVVM để được tích hợp.
Xung đột phương thức đã xảy ra trên một phương thức getString () , được thông báo bởi cả hai giao diện và phải có sự triển khai diffnet trong các ngữ cảnh khác nhau.
Giải pháp thay thế (xấu xí..IMO) là sử dụng một lớp bên trong để triển khai tất cả Nền tảngcác phương thức, chỉ vì một xung đột chữ ký phương thức nhỏ ... trong một số trường hợp, phương thức vay mượn như vậy thậm chí không được sử dụng (nhưng bị ảnh hưởng đến ngữ nghĩa thiết kế chính).
Tôi có xu hướng đồng ý rằng chỉ dẫn ngữ cảnh / không gian tên rõ ràng kiểu C # là hữu ích.


1
Tôi chưa bao giờ nhận ra C # đã chu đáo và giàu tính năng như thế nào cho đến khi tôi bắt đầu sử dụng Java để phát triển Android. Tôi đã coi các tính năng C # đó là đương nhiên. Java thiếu quá nhiều tính năng.
Rau chết tiệt

1

Giải pháp duy nhất nảy ra trong đầu tôi là sử dụng các đối tượng tham chiếu đến đối tượng mà bạn muốn đưa vào các giao diện đa dạng.

ví dụ: giả sử bạn có 2 giao diện để triển khai

public interface Framework1Interface {

    void method(Object o);
}

public interface Framework2Interface {
    void method(Object o);
}

bạn có thể đặt chúng vào hai đối tượng Facador:

public class Facador1 implements Framework1Interface {

    private final ObjectToUse reference;

    public static Framework1Interface Create(ObjectToUse ref) {
        return new Facador1(ref);
    }

    private Facador1(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework1Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork1(o);
    }
}

public class Facador2 implements Framework2Interface {

    private final ObjectToUse reference;

    public static Framework2Interface Create(ObjectToUse ref) {
        return new Facador2(ref);
    }

    private Facador2(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework2Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork2(o);
    }
}

Cuối cùng thì lớp học mà bạn muốn sẽ giống như

public class ObjectToUse {

    private Framework1Interface facFramework1Interface;
    private Framework2Interface facFramework2Interface;

    public ObjectToUse() {
    }

    public Framework1Interface getAsFramework1Interface() {
        if (facFramework1Interface == null) {
            facFramework1Interface = Facador1.Create(this);
        }
        return facFramework1Interface;
    }

    public Framework2Interface getAsFramework2Interface() {
        if (facFramework2Interface == null) {
            facFramework2Interface = Facador2.Create(this);
        }
        return facFramework2Interface;
    }

    public void methodForFrameWork1(Object o) {
    }

    public void methodForFrameWork2(Object o) {
    }
}

bây giờ bạn có thể sử dụng các phương thức getAs * để "phơi bày" lớp của bạn


0

Bạn có thể sử dụng mẫu Bộ điều hợp để làm cho chúng hoạt động. Tạo hai bộ điều hợp cho mỗi giao diện và sử dụng nó. Nó sẽ giải quyết vấn đề.


-1

Tất cả đều tốt và tốt khi bạn có toàn quyền kiểm soát tất cả các mã được đề cập và có thể triển khai trả trước này. Bây giờ hãy tưởng tượng bạn có một lớp công khai hiện có được sử dụng ở nhiều nơi với một phương thức

public class MyClass{

    private String name;

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Bây giờ bạn cần chuyển nó vào WizzBangProcessor có sẵn yêu cầu các lớp triển khai WBPInterface ... cũng có phương thức getName (), nhưng thay vì triển khai cụ thể của bạn, giao diện này yêu cầu phương thức trả về tên của một kiểu của Wizz Bang Chế.

Trong C # nó sẽ là một sự tầm thường

public class MyClass : WBPInterface{

    private String name;

    String WBPInterface.getName(){
        return "MyWizzBangProcessor";
    }

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Trong Java Tough, bạn sẽ phải xác định mọi điểm trong cơ sở mã được triển khai hiện có nơi bạn cần chuyển đổi từ giao diện này sang giao diện khác. Chắc chắn công ty WizzBangProcessor nên sử dụng getWizzBangProcessName (), nhưng họ cũng là nhà phát triển. Trong bối cảnh của họ, getName đã ổn. Trên thực tế, ngoài Java, hầu hết các ngôn ngữ dựa trên OO khác đều hỗ trợ điều này. Java hiếm khi buộc tất cả các giao diện được triển khai với cùng một phương thức NAME.

Hầu hết các ngôn ngữ khác có một trình biên dịch rất vui khi thực hiện một hướng dẫn để nói rằng "phương thức này trong lớp này khớp với chữ ký của phương thức này trong giao diện được triển khai này là nó đang thực hiện". Xét cho cùng, toàn bộ điểm của việc xác định giao diện là cho phép định nghĩa được trừu tượng hóa khỏi việc thực hiện. (Đừng khiến tôi bắt đầu với việc có các phương thức mặc định trong Giao diện trong Java, chứ đừng nói đến việc ghi đè mặc định .... bởi vì chắc chắn, mọi thành phần được thiết kế cho ô tô đường bộ đều có thể bị đâm vào một chiếc ô tô đang bay và chỉ hoạt động - này cả hai đều là ô tô ... Tôi chắc chắn rằng chức năng mặc định nói điều hướng sat của bạn sẽ không bị ảnh hưởng với đầu vào cao độ và cuộn mặc định, vì ô tô chỉ ngáp!

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.