Java - ชื่อเมธอดชนกันในการใช้งานอินเตอร์เฟส


88

ถ้าฉันมีสองอินเทอร์เฟซทั้งสองอย่างแตกต่างกันในวัตถุประสงค์ของพวกเขา แต่มีลายเซ็นวิธีเดียวกันฉันจะทำให้คลาสใช้ทั้งสองอย่างโดยไม่ถูกบังคับให้เขียนเมธอดเดียวที่ทำหน้าที่ทั้งอินเทอร์เฟซและการเขียนตรรกะที่ซับซ้อนในเมธอด การใช้งานที่ตรวจสอบว่ามีการเรียกใช้วัตถุประเภทใดและเรียกใช้รหัสที่เหมาะสม?

ใน C # สิ่งนี้ถูกเอาชนะโดยสิ่งที่เรียกว่าการใช้งานอินเทอร์เฟซที่ชัดเจน มีวิธีที่เทียบเท่าใน Java หรือไม่?


37
เมื่อชั้นเรียนหนึ่งต้องใช้สองวิธีที่มีลายเซ็นเดียวกันซึ่งทำสิ่งต่างกันชั้นเรียนของคุณเกือบจะทำหลายอย่างมากเกินไป
Joachim Sauer

15
ข้างต้นอาจไม่เป็นความจริงเสมอไป IMO บางครั้งในคลาสเดียวคุณต้องใช้วิธีการที่ต้องยืนยันกับสัญญาภายนอก (จึงเป็นการ จำกัด ลายเซ็น) แต่มีการใช้งานที่แตกต่างกัน ในความเป็นจริงสิ่งเหล่านี้เป็นข้อกำหนดทั่วไปเมื่อออกแบบคลาสที่ไม่สำคัญ การโอเวอร์โหลดและการลบล้างเป็นกลไกที่จำเป็นในการอนุญาตให้ใช้วิธีการที่ทำสิ่งที่แตกต่างกันซึ่งอาจไม่แตกต่างกันในลายเซ็นหรือแตกต่างกันเล็กน้อยสิ่งที่ฉันมีต่อไปนี้มีข้อ จำกัด มากกว่าเล็กน้อยในเรื่องที่ไม่อนุญาตให้มีการแบ่งย่อย / และไม่อนุญาต ลายเซ็นที่เปลี่ยนแปลงน้อยที่สุด
Bhaskar

1
ฉันรู้สึกทึ่งที่รู้ว่าคลาสและวิธีการเหล่านี้คืออะไร
Uri

2
ฉันพบกรณีเช่นนี้ที่คลาส "ที่อยู่" แบบเดิมใช้อินเทอร์เฟซบุคคลและ บริษัท ที่มีเมธอด getName () เพียงแค่ส่งคืนสตริงจากโมเดลข้อมูล ข้อกำหนดทางธุรกิจใหม่ระบุว่า Person.getName () ส่งคืนสตริงที่จัดรูปแบบเป็น "นามสกุลชื่อที่ได้รับ" หลังจากการสนทนากันมากข้อมูลก็ถูกจัดรูปแบบใหม่ในฐานข้อมูลแทน
belwood

12
เพียงแค่ระบุว่าชั้นเรียนเกือบจะทำสิ่งต่างๆมากเกินไปก็ไม่ได้เป็นสิ่งก่อสร้าง ตอนนี้ฉันมีกรณีนี้ที่คลาสของฉันมีการชนกันของชื่อ mehod จาก 2 อินเทอร์เฟซที่แตกต่างกันและคลาสของฉันไม่ได้ทำหลายอย่างเกินไป วัตถุประสงค์ค่อนข้างคล้ายกัน แต่ทำสิ่งที่แตกต่างกันเล็กน้อย อย่าพยายามปกป้องภาษาโปรแกรมสำหรับคนพิการอย่างเห็นได้ชัดโดยกล่าวหาว่าผู้ถามใช้การออกแบบซอฟต์แวร์ที่ไม่ดี!
j00hi

คำตอบ:


75

ไม่ไม่มีวิธีใดที่จะใช้วิธีเดียวกันในสองวิธีที่แตกต่างกันในคลาสเดียวใน Java

นั่นอาจนำไปสู่สถานการณ์ที่สับสนมากมายซึ่งเป็นสาเหตุที่ Java ไม่อนุญาต

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

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

สิ่งที่คุณทำได้คือสร้างคลาสจากสองคลาสที่แต่ละคลาสใช้อินเทอร์เฟซที่แตกต่างกัน จากนั้นคลาสหนึ่งจะมีลักษณะการทำงานของอินเทอร์เฟซทั้งสอง

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

9
แต่ด้วยวิธีนี้ฉันไม่สามารถส่งอินสแตนซ์ของ CompositeClass ไปที่ใดก็ได้ที่คาดว่าจะมีการอ้างอิงของอินเทอร์เฟซ (ISomething หรือ ISomething2)? ฉันไม่สามารถคาดหวังว่ารหัสไคลเอ็นต์จะสามารถส่งอินสแตนซ์ของฉันไปยังอินเทอร์เฟซที่เหมาะสมได้ดังนั้นฉันจึงไม่สูญเสียบางสิ่งจากข้อ จำกัด นี้หรือ โปรดทราบว่าด้วยวิธีนี้เมื่อเขียนคลาสที่ใช้อินเทอร์เฟซตามลำดับจริง ๆ เราจะสูญเสียประโยชน์ของการมีโค้ดลงในคลาสเดียวซึ่งอาจเป็นอุปสรรคที่ร้ายแรงในบางครั้ง
Bhaskar

9
@Bhaskar คุณทำคะแนนถูกต้อง คำแนะนำที่ดีที่สุดที่ฉันมีคือเพิ่ม a ISomething1 CompositeClass.asInterface1();และISomething2 CompositeClass.asInterface2();method ในคลาสนั้น จากนั้นคุณจะได้รับหนึ่งหรืออีกชั้นหนึ่งจากคลาสผสม แม้ว่าจะไม่มีวิธีแก้ปัญหาที่ดีเยี่ยมสำหรับปัญหานี้
jjnguy

1
เมื่อพูดถึงสถานการณ์ที่สับสนซึ่งอาจนำไปสู่คุณสามารถยกตัวอย่างได้หรือไม่? เราไม่สามารถคิดว่าชื่ออินเทอร์เฟซที่เพิ่มเข้าไปในชื่อเมธอดเป็นความละเอียดขอบเขตพิเศษซึ่งสามารถหลีกเลี่ยงการชน / ความสับสนได้หรือไม่
Bhaskar

@Bhaskar จะดีกว่าถ้าชั้นเรียนของเรายึดมั่นในหลักการความรับผิดชอบเดียว หากมีคลาสที่ใช้อินเทอร์เฟซสองอินเทอร์เฟซที่แตกต่างกันมากฉันคิดว่าการออกแบบควรได้รับการปรับปรุงใหม่เพื่อแบ่งคลาสเพื่อดูแลความรับผิดชอบเดียว
Anirudhan J

1
มันจะสับสนแค่ไหนที่ยอมให้บางอย่างเช่นpublic long getCountAsLong() implements interface2.getCount {...}[ในกรณีที่อินเทอร์เฟซต้องการlongแต่ผู้ใช้ของคลาสคาดหวังint] หรือprivate void AddStub(T newObj) implements coolectionInterface.Add[สมมติว่าcollectionInterfaceมีcanAdd()วิธีการและสำหรับอินสแตนซ์ทั้งหมดของคลาสนี้จะส่งคืนfalse]
supercat

13

ไม่มีวิธีจริงในการแก้ปัญหานี้ใน Java คุณสามารถใช้คลาสภายในเป็นวิธีแก้ปัญหาได้:

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()
        };
    }
}

แม้ว่ามันจะไม่อนุญาตให้ทำการร่ายจากAlfaBetaถึงBetaแต่การดาวน์คาสต์โดยทั่วไปก็เป็นสิ่งที่ชั่วร้ายและหากเป็นไปได้ว่าAlfaอินสแตนซ์มักจะมีBetaแง่มุมด้วยเช่นกันและด้วยเหตุผลบางประการ (โดยปกติการเพิ่มประสิทธิภาพเป็นเหตุผลที่ถูกต้องเท่านั้น) ที่คุณต้องการ ในการแปลงเป็นBetaคุณสามารถสร้างอินเทอร์เฟซย่อยAlfaด้วยBeta asBeta()ได้


คุณหมายถึงคลาสนิรนามมากกว่าชั้นในหรือเปล่า?
Zaid Masud

2
@ZaidMasud ฉันหมายถึงคลาสภายในเนื่องจากสามารถเข้าถึงสถานะส่วนตัวของวัตถุที่ปิดล้อม) ชั้นเรียนภายในเหล่านี้สามารถไม่ระบุชื่อได้เช่นกัน
gustafc

11

หากคุณกำลังประสบปัญหานี้มันเป็นไปได้มากที่สุดเพราะคุณกำลังใช้มรดกที่คุณควรจะใช้คณะผู้แทน หากคุณต้องการให้สองอินเทอร์เฟซที่แตกต่างกันแม้ว่าจะคล้ายกันสำหรับโมเดลข้อมูลต้นแบบเดียวกันคุณควรใช้มุมมองเพื่อให้การเข้าถึงข้อมูลในราคาถูกโดยใช้อินเทอร์เฟซอื่น

เพื่อให้เป็นตัวอย่างที่ชัดเจนสำหรับกรณีหลังสมมติว่าคุณต้องการใช้ทั้งสองอย่างCollectionและMyCollection(ซึ่งไม่ได้รับมาจากCollectionและมีอินเทอร์เฟซที่เข้ากันไม่ได้) คุณสามารถจัดเตรียม a Collection getCollectionView()and MyCollection getMyCollectionView()ฟังก์ชันที่ให้การใช้งานที่มีน้ำหนักเบาCollectionและMyCollectionโดยใช้ข้อมูลพื้นฐานเดียวกัน

สำหรับกรณีเดิม ... สมมติว่าคุณต้องการอาร์เรย์ของจำนวนเต็มและอาร์เรย์ของสตริง แทนที่จะรับช่วงจากทั้งสองList<Integer>และList<String>คุณควรมีสมาชิกประเภทหนึ่งList<Integer>และสมาชิกประเภทList<String>อื่นและอ้างถึงสมาชิกเหล่านั้นแทนที่จะพยายามสืบทอดจากทั้งสองอย่าง แม้ว่าคุณจะต้องการเพียงแค่รายการจำนวนเต็ม แต่ก็ควรใช้องค์ประกอบ / การมอบหมายมากกว่าการสืบทอดในกรณีนี้


ฉันไม่คิดอย่างนั้น คุณกำลังลืมไลบรารีที่กำหนดให้คุณต้องใช้อินเทอร์เฟซต่างๆเพื่อให้เข้ากันได้ คุณสามารถพบสิ่งนี้ได้โดยใช้ไลบรารีที่ขัดแย้งกันหลาย ๆ ไลบรารีบ่อยขึ้นจากนั้นคุณจะพบสิ่งนี้ในโค้ดของคุณเอง
ไนท์พูล

1
@nightpool หากคุณใช้ไลบรารีหลายไลบรารีซึ่งแต่ละแห่งต้องการอินเทอร์เฟซที่แตกต่างกันก็ยังไม่จำเป็นสำหรับอ็อบเจ็กต์เดียวที่จะใช้อินเทอร์เฟซทั้งสอง คุณสามารถให้อ็อบเจ็กต์มีตัวเข้าถึงสำหรับส่งคืนอินเทอร์เฟซที่แตกต่างกันสองอินเทอร์เฟซที่แตกต่างกัน (และเรียกตัวเข้าถึงที่เหมาะสมเมื่อส่งต่ออ็อบเจ็กต์ไปยังไลบรารีพื้นฐาน)
Michael Aaron Safyan

1

ปัญหา Java "คลาสสิก" ยังส่งผลต่อการพัฒนา Android ของฉัน ...
เหตุผลดูเหมือนจะง่าย:
เฟรมเวิร์ก / ไลบรารีที่คุณต้องใช้มากขึ้นสิ่งต่าง ๆ ที่ควบคุมไม่ได้ง่ายขึ้น ...

ในกรณีของฉันฉันมีคลาสBootStrapperAppสืบทอดมาจากandroid.app.Application ,
ในขณะที่ชั้นเดียวกันก็ควรที่จะใช้แพลตฟอร์มอินเตอร์เฟซของกรอบ MVVM เพื่อให้ได้รับแบบบูรณาการ
เมธอดชนกันเกิดขึ้นบนเมธอดgetString ()ซึ่งประกาศโดยทั้งสองอินเทอร์เฟซและควรมีการใช้งานที่แตกต่างกันในบริบทที่แตกต่างกัน
วิธีแก้ปัญหา (น่าเกลียด .. IMO) กำลังใช้คลาสภายในเพื่อใช้งานแพลตฟอร์มทั้งหมดวิธีการเพียงเพราะความขัดแย้งของลายเซ็นของวิธีการเล็กน้อย ... ในบางกรณีวิธีการยืมดังกล่าวไม่ได้ใช้เลย (แต่ส่งผลต่อความหมายของการออกแบบที่สำคัญ)
ฉันมักจะเห็นด้วย C # - สไตล์การบ่งชี้บริบท / เนมสเปซที่ชัดเจนเป็นประโยชน์


1
ฉันไม่เคยรู้เลยว่า C # มีความรอบคอบและมีคุณลักษณะมากมายอย่างไรจนกระทั่งฉันเริ่มใช้ Java สำหรับการพัฒนา Android ฉันใช้คุณสมบัติ C # เหล่านั้นเพื่อรับสิทธิ์ Java ขาดคุณสมบัติมากเกินไป
ผักเหี้ย

1

วิธีแก้ปัญหาเดียวที่อยู่ในใจของฉันคือการใช้วัตถุอ้างอิงกับสิ่งที่คุณต้องการใช้อินเทอร์เฟซที่หลากหลาย

เช่นสมมติว่าคุณมี 2 อินเทอร์เฟซที่จะใช้

public interface Framework1Interface {

    void method(Object o);
}

และ

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

คุณสามารถใส่ไว้ในวัตถุ 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);
    }
}

ในที่สุดชั้นเรียนที่คุณต้องการควรเป็นอย่างไร

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) {
    }
}

ตอนนี้คุณสามารถใช้เมธอด getAs * เพื่อ "เปิดเผย" ชั้นเรียนของคุณได้


0

คุณสามารถใช้รูปแบบอะแดปเตอร์เพื่อทำงานเหล่านี้ได้ สร้างอะแด็ปเตอร์สองตัวสำหรับแต่ละอินเทอร์เฟซและใช้สิ่งนั้น มันควรจะแก้ปัญหาได้


-1

ทั้งหมดนี้ดีและดีเมื่อคุณสามารถควบคุมโค้ดทั้งหมดที่เป็นปัญหาได้ทั้งหมดและสามารถนำไปใช้ล่วงหน้าได้ ตอนนี้ลองนึกภาพคุณมีคลาสสาธารณะที่ใช้อยู่ในหลาย ๆ ที่ด้วยวิธีการ

public class MyClass{

    private String name;

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

    public String getName(){
        return name;
    }
}

ตอนนี้คุณต้องส่งผ่านไปยัง WizzBangProcessor ซึ่งต้องใช้คลาสในการใช้ WBPInterface ... ซึ่งมีเมธอด getName () ด้วย แต่แทนที่จะใช้งานอย่างเป็นรูปธรรมอินเทอร์เฟซนี้คาดว่าเมธอดจะส่งคืนชื่อของประเภท ของ Wizz Bang Processing

ใน C # มันจะเป็นสามเท่า

public class MyClass : WBPInterface{

    private String name;

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

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

    public String getName(){
        return name;
    }
}

ใน Java Tough คุณจะต้องระบุทุกจุดในฐานรหัสที่ปรับใช้ที่มีอยู่ซึ่งคุณต้องแปลงจากอินเทอร์เฟซหนึ่งไปยังอีกอินเทอร์เฟซ แน่นอนว่า บริษัท WizzBangProcessor ควรใช้ getWizzBangProcessName () แต่พวกเขาก็เป็นนักพัฒนาเช่นกัน ในบริบท getName ก็ดี จริงๆแล้วนอก Java ภาษาที่ใช้ OO อื่น ๆ ส่วนใหญ่รองรับสิ่งนี้ Java มีน้อยมากในการบังคับให้ใช้อินเทอร์เฟซทั้งหมดด้วยวิธีการเดียวกัน NAME

ภาษาอื่น ๆ ส่วนใหญ่มีคอมไพเลอร์ที่ยินดีรับคำสั่งมากกว่าที่จะบอกว่า "วิธีนี้ในคลาสนี้ซึ่งตรงกับลายเซ็นของวิธีนี้ในอินเทอร์เฟซที่ใช้งานนี้คือการนำไปใช้งาน" หลังจากจุดรวมทั้งหมดของการกำหนดอินเทอร์เฟซคือการอนุญาตให้นิยามที่เป็นนามธรรมจากการนำไปใช้งาน (อย่าเพิ่งให้ฉันเริ่มใช้วิธีการเริ่มต้นในอินเทอร์เฟซใน Java นับประสาการลบล้างค่าเริ่มต้น .... เพราะแน่นอนว่าทุกส่วนประกอบที่ออกแบบมาสำหรับรถใช้งานบนท้องถนนควรจะกระแทกเข้ากับรถที่บินได้และใช้งานได้ - เฮ้ มันเป็นรถทั้งคู่ ... ฉันแน่ใจว่าฟังก์ชันการทำงานเริ่มต้นของ say sat nav ของคุณจะไม่ได้รับผลกระทบกับอินพุต pitch และ roll ที่เป็นค่าเริ่มต้นเพราะรถยนต์เท่านั้นที่หันเห!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.