ตัวสร้างในอินเทอร์เฟซ


148

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

ดังนั้นคุณสามารถมั่นใจได้ว่ามีการกำหนดบางฟิลด์ในคลาสสำหรับทุกการใช้อินเทอร์เฟซนี้

ตัวอย่างเช่นพิจารณาคลาสข้อความต่อไปนี้:

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

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



2
คุณพูดว่า "ในตัวสร้างฉันสามารถมั่นใจได้ว่า [ทุกการใช้งานของคลาสนี้มีชุดตัวรับ]" แต่ไม่คุณไม่สามารถทำได้ หากมีความเป็นไปได้ที่จะกำหนดคอนสตรัคเตอร์ดังกล่าวพารามิเตอร์จะเป็นเพียงคำแนะนำที่ชัดเจนสำหรับผู้พัฒนาของคุณ - แต่พวกเขาสามารถเลือกที่จะเพิกเฉยได้หากพวกเขาต้องการ
Julien Silland

3
@mattb อืมนั่นเป็นภาษาอื่น
ใช่

คำตอบ:


129

รับบางสิ่งที่คุณอธิบายไว้:

"ดังนั้นคุณสามารถมั่นใจได้ว่ามีการกำหนดบางฟิลด์ในคลาสสำหรับการใช้อินเทอร์เฟซนี้ทุกครั้ง"

"หากการกำหนดส่วนต่อประสานสำหรับคลาสนี้เพื่อให้ฉันสามารถมีคลาสเพิ่มเติมซึ่งใช้ส่วนต่อประสานข้อความฉันสามารถกำหนดวิธีการส่งไม่ใช่ตัวสร้างเท่านั้น"

... ข้อกำหนดเหล่านี้เป็นสิ่งที่เป็นนามธรรมสำหรับชั้นเรียน


แต่โปรดทราบว่ากรณีที่ใช้ @Sebi อธิบาย (เรียกวิธีการโอเวอร์โหลดจากผู้สร้างหลัก) เป็นความคิดที่ไม่ดีตามที่อธิบายไว้ในคำตอบของฉัน
rsp

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

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

7
@ CPerkins ในขณะนี้เป็นจริงฉันไม่แนะนำให้ใช้คลาสนามธรรมจะแก้กรณีการใช้งานของ Sebi หากมีสิ่งใดที่ดีที่สุดคือการประกาศMessageอินเตอร์เฟสที่กำหนดsend()วิธีการและหาก Sebi ประสงค์ที่จะให้คลาส "ฐาน" สำหรับการใช้งานMessageอินเทอร์เฟซให้ระบุAbstractMessageเช่นกัน คลาสนามธรรมไม่ควรแทนที่อินเทอร์เฟซ แต่ไม่เคยพยายามที่จะแนะนำ
matt b

2
เข้าใจแมตต์ ฉันไม่ได้โต้เถียงกับคุณมากขึ้นชี้ให้เห็นว่ามันไม่ใช่สิ่งทดแทนที่สมบูรณ์สำหรับสิ่งที่ op ต้องการ
CPerkins

76

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

หรือในรหัส:

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
ภาษาไม่สามารถทำได้โดยให้สิ่งต่าง ๆ เช่นclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako

1
ความหมายที่มีประโยชน์ที่สุดสำหรับ "ตัวสร้างในอินเทอร์เฟซ" หากได้รับอนุญาตจะเป็นถ้าnew Set<Fnord>()สามารถตีความได้ว่าหมายถึง "ให้บางสิ่งบางอย่างที่ฉันสามารถใช้เป็นSet<Fnord>"; ถ้าผู้เขียนSet<T>ตั้งใจHashSet<T>ที่จะไปสู่การดำเนินการในสิ่งที่ไม่ได้มีความจำเป็นโดยเฉพาะอย่างยิ่งสำหรับสิ่งอื่นอินเตอร์เฟซที่แล้วสามารถกำหนดอาจจะถือว่าตรงกันกับnew Set<Fnord>() new HashSet<Fnord>()สำหรับการเรียนที่จะใช้การเชื่อมต่อหลายจะไม่ก่อให้เกิดปัญหาใด ๆ ตั้งแต่new InterfaceName()ก็จะสร้างชั้นเรียนที่กำหนดโดยอินเตอร์เฟซ
supercat

การโต้แย้ง: ตัวA(String,List)สร้างของคุณอาจเป็นตัวสร้างที่กำหนดและA(String)และA(List)อาจเป็นตัวที่สองที่เรียกมัน รหัสของคุณไม่ใช่ตัวอย่างเคาน์เตอร์ แต่เป็นรหัสที่ไม่ดี
Ben Leggiero

ทำไมคุณถึงเรียกช่างก่อสร้างทั้งหมดในการติดตั้ง! ใช่ถ้าใช้อินเตอร์เฟซที่มี ctor มากกว่าหนึ่งอันที่มีสตริงและอีกอันที่มีอินเทอร์เซ็ทนั้นก็ต้องใช้สองเตอร์ - แต่สามารถใช้อย่างใดอย่างหนึ่งหรือ ถ้าไม่เหมาะสมคลาสก็จะไม่ใช้ทั้งสองอินเตอร์เฟส แล้วอะไรล่ะ! (มีเหตุผลอื่นที่ไม่มี ctor ในส่วนต่อประสาน)
ไก่

@kai ไม่มันจำเป็นต้องเรียกใช้ตัวสร้างทั้งสองอินเตอร์เฟสเมื่อสร้างอินสแตนซ์ กล่าวอีกนัยหนึ่ง: ในตัวอย่างของฉันอินสแตนซ์มีทั้งชื่อและรายการดังนั้นแต่ละอินสแตนซ์จำเป็นต้องสร้างอินสแตนซ์ทั้งชื่อและรายการ
daniel kullmann

13

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

กรณีการใช้งานที่คุณอธิบายนั้นคล้ายกับคลาสนามธรรมซึ่งนวกรรมิกเรียกใช้เมธอด abstract ซึ่งถูกนำไปใช้ในคลาสย่อย

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

เพื่อสรุป: มันคือการถามปัญหาเมื่อคุณเรียกใช้วิธีการมากเกินไปจากผู้สร้างผู้สร้างเพื่ออ้างmindprod :

โดยทั่วไปคุณต้องหลีกเลี่ยงการเรียกใช้วิธีการที่ไม่เป็นขั้นสุดท้ายในตัวสร้าง ปัญหาคืออินสแตนซ์ initialisers / ตัวแปร initialisation ในคลาสที่ได้รับจะดำเนินการ หลังจากตัวสร้างของคลาสฐาน


6

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

อย่างไรก็ตามวิธีแก้ปัญหานี้ต้องการให้คุณใช้getInstance()อินสแตนซ์ของวัตถุทั้งหมดของอินเทอร์เฟซนี้

เช่น

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

5

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

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


3

หากคุณต้องการตรวจสอบให้แน่ใจว่าการใช้งานอินเทอร์เฟซทั้งหมดมีฟิลด์เฉพาะคุณเพียงแค่เพิ่มอินเตอร์เฟสของคุณใน getter สำหรับฟิลด์นั้น :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • มันจะไม่ทำลาย encapsulation
  • มันจะบอกให้ทุกคนที่ใช้อินเทอร์เฟซของคุณทราบว่าReceiverวัตถุนั้นจะต้องถูกส่งผ่านไปยังคลาสในบางวิธี (ไม่ว่าจะเป็นคอนสตรัคเตอร์หรือเซทเทอร์)

2

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


1

ดูคำถามนี้เพื่อหาสาเหตุ (นำมาจากความคิดเห็น)

หากคุณต้องการทำสิ่งนี้จริง ๆ คุณอาจต้องการคลาสฐานนามธรรมแทนอินเทอร์เฟซ


1

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


0

นี่คือตัวอย่างการใช้เทคนิคนี้ ในตัวอย่าง specifik นี้รหัสจะทำการเรียกไปยัง Firebase โดยใช้การเยาะเย้ยMyCompletionListenerที่เป็นอินเตอร์เฟซที่หลอกลวงเป็นคลาสนามธรรม, อินเตอร์เฟซที่มีการสร้าง

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

คุณจะใช้privateตัวดัดแปลงการเข้าถึงด้วยได้interfaceอย่างไร?
Rudra

0

โดยทั่วไป Constructors สำหรับการเริ่มต้นสมาชิกที่ไม่คงที่ของคลาสเฉพาะด้วยความเคารพต่อวัตถุ

ไม่มีการสร้างวัตถุสำหรับส่วนต่อประสานเนื่องจากมีวิธีการประกาศเท่านั้น แต่ไม่ได้กำหนดวิธีการ ทำไมเราไม่สามารถสร้างวัตถุเพื่อประกาศวิธีการสร้างวัตถุคืออะไร แต่การจัดสรรหน่วยความจำบางส่วน (ในหน่วยความจำฮีป) สำหรับสมาชิกที่ไม่คงที่

JVM จะสร้างหน่วยความจำสำหรับสมาชิกที่ได้รับการพัฒนาอย่างเต็มที่และพร้อมใช้งานโดยขึ้นอยู่กับสมาชิกเหล่านั้น JVM จะคำนวณจำนวนหน่วยความจำที่ต้องการสำหรับพวกเขาและสร้างหน่วยความจำ

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

สรุป:

ไม่มีการสร้างวัตถุไม่มีโอกาสที่จะเริ่มต้นสมาชิกที่ไม่คงที่ผ่านตัวสร้างนั่นคือเหตุผลที่ว่าทำไมตัวสร้างไม่ได้รับอนุญาตภายในส่วนต่อประสาน (เนื่องจากไม่มีการใช้ตัวสร้างภายในส่วนต่อประสาน)

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