มอบหมาย vs อินเทอร์เฟซ - มีคำชี้แจงเพิ่มเติมอีกหรือไม่


23

หลังจากอ่านบทความ - เมื่อใดควรใช้ผู้ได้รับมอบหมายแทนการเชื่อมต่อ (คู่มือการเขียนโปรแกรม C #)ฉันต้องการความช่วยเหลือในการทำความเข้าใจจุดที่กำหนดด้านล่างซึ่งฉันพบว่าไม่ชัดเจน (สำหรับฉัน) ตัวอย่างหรือคำอธิบายโดยละเอียดสำหรับสิ่งเหล่านี้?

ใช้ผู้รับมอบสิทธิ์เมื่อ:

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

ใช้อินเทอร์เฟซเมื่อ:

  • มีกลุ่มของวิธีการที่เกี่ยวข้องที่อาจถูกเรียกว่าเป็น
  • คลาสต้องการเพียงหนึ่งการใช้งานเมธอด

คำถามของฉันคือ

  1. พวกเขาหมายถึงรูปแบบการออกแบบ eventing หรือไม่
  2. วิธีการจัดองค์ประกอบจะง่ายถ้าใช้ผู้รับมอบสิทธิ์
  3. หากมีกลุ่มของวิธีการที่เกี่ยวข้องที่อาจถูกเรียกใช้อินเทอร์เฟซ - มันมีประโยชน์อะไร?
  4. ถ้าคลาสต้องการเพียงหนึ่งการใช้งานของวิธีการใช้อินเตอร์เฟซมันเป็นธรรมในแง่ของผลประโยชน์?

คำตอบ:


11

พวกเขาหมายถึงรูปแบบการออกแบบ eventing หรือไม่

พวกเขาส่วนใหญ่อ้างถึงการใช้งานของรูปแบบการสังเกตการณ์ซึ่งเป็นภาษาหลักในการสร้าง C #, เปิดเผยเป็น ' เหตุการณ์ ' การฟังเหตุการณ์เป็นไปได้ด้วยการเชื่อมต่อผู้เข้าร่วมกิจกรรม ตามที่ Yam Marcovic ได้ชี้ให้เห็นว่าEventHandlerเป็นประเภทตัวแทนพื้นฐานสำหรับกิจกรรม แต่ประเภทตัวแทนสามารถใช้งานได้

วิธีการจัดองค์ประกอบจะง่ายถ้าใช้ผู้รับมอบสิทธิ์

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

class Bunny
{
    Func<bool> _canHop;

    public Bunny( Func<bool> canHop )
    {
        _canHop = canHop;
    }

    public void Hop()
    {
        if ( _canHop() )  Console.WriteLine( "Hop!" );
    }
}

Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );

การทำสิ่งที่คล้ายกับอินเทอร์เฟซจะทำให้คุณต้องใช้รูปแบบกลยุทธ์หรือใช้Bunnyคลาสพื้นฐาน (นามธรรม) ซึ่งคุณจะขยายกระต่ายที่เฉพาะเจาะจงมากขึ้น

หากมีกลุ่มของวิธีการที่เกี่ยวข้องที่อาจถูกเรียกใช้อินเทอร์เฟซ - มันมีประโยชน์อะไร?

อีกครั้งฉันจะใช้กระต่ายเพื่อแสดงว่ามันจะง่ายขึ้น

interface IAnimal
{
    void Jump();
    void Eat();
    void Poo();
}

class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }

// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump();  bunny.Eat();  bunny.Poo();
IAnimal chick = new Chick();
chick.Jump();  chick.Eat();  chick.Poo();

// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...

ถ้าคลาสต้องการเพียงหนึ่งการใช้งานของวิธีการใช้อินเตอร์เฟซมันเป็นธรรมในแง่ของผลประโยชน์?

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

ข้อสรุป

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

เหตุผลเพิ่มเติมเมื่อใช้ผู้รับมอบสิทธิ์คือเมื่อคุณต้องการเปิดเผยเฉพาะส่วนของคลาสที่คุณไม่สามารถปรับไฟล์ต้นฉบับได้

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


12

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

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

นอกจากนี้ฉันไม่เห็นด้วยอย่างสมบูรณ์ว่าผู้ได้รับมอบหมายจะมีประโยชน์หลักเมื่อใช้เป็นโซลูชันสำหรับรูปแบบผู้สังเกตการณ์ / ผู้สมัครสมาชิก มันเป็นทางออกที่สง่างามสำหรับ "ปัญหา verbosity java"

สำหรับคำถามของคุณ:

1 และ 2)

หากคุณต้องการสร้างระบบเหตุการณ์ใน Java โดยปกติคุณจะใช้อินเทอร์เฟซเพื่อเผยแพร่เหตุการณ์บางอย่างเช่น:

interface KeyboardListener
{
    void KeyDown(int key);
    void KeyUp(int key)
    void KeyPress(int key);
    .... and so on
}

KeyPress(int key)ที่นี้หมายถึงระดับของคุณจะต้องชัดเจนใช้วิธีการเหล่านี้และให้สมบูรณ์สำหรับพวกเขาทั้งหมดแม้ว่าคุณเพียงแค่ต้องการที่จะดำเนินการ

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

+1 สำหรับคะแนน 3-5

นอกจากนี้:

ผู้ได้รับมอบหมายมีประโยชน์มากเมื่อคุณต้องการจัดหาตัวอย่างเช่นฟังก์ชั่น "map" ซึ่งจะนำรายการและฉายแต่ละองค์ประกอบลงในรายการใหม่ด้วยองค์ประกอบจำนวนเท่ากัน แต่แตกต่างกันในบางวิธี เป็นหลัก IEnumerable.Select (... )

IEnumerable.Select ใช้ a Func<TSource, TDest>ซึ่งเป็นตัวแทนการห่อฟังก์ชันที่ใช้องค์ประกอบ TSource และแปลงองค์ประกอบนั้นเป็นองค์ประกอบ TDest

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

แน่นอนคุณสามารถใช้คลาสที่ไม่ระบุชื่อซึ่งเป็นแนวคิดที่คล้ายกัน (ใน java)


ลองแก้ไขโพสต์ของคุณเพื่อตอบคำถามของเขาจริง ๆ
Yam Marcovic

@YamMarcovic คุณพูดถูกฉันเบียดเสียดสักหน่อยแทนที่จะตอบคำถามของเขาโดยตรง ถึงกระนั้นฉันคิดว่ามันอธิบายเล็กน้อยเกี่ยวกับกลไกที่เกี่ยวข้อง นอกจากนี้คำถามของเขาก็ค่อนข้างน้อยเมื่อฉันตอบคำถาม : p
สูงสุด

เป็นธรรมชาติ เกิดขึ้นกับฉันเช่นกันและมันมีมูลค่าต่อ se นั่นเป็นเหตุผลที่ฉันแนะนำเท่านั้นแต่ไม่ได้บ่นเกี่ยวกับเรื่องนี้ :)
Yam Marcovic

3

1) รูปแบบการจัดอีเวนต์คลาสสิกเป็นรูปแบบการสังเกตการณ์นี่คือลิงค์ Microsoft ที่ดี Microsoft คุยกับนักสังเกตการณ์

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

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


3

ก่อนอื่นคำถามที่ดี ฉันชื่นชมที่คุณให้ความสำคัญกับยูทิลิตี้แทนที่จะยอมรับ "แนวทางปฏิบัติที่ดีที่สุด" อย่างสุ่ม ๆ +1 สำหรับสิ่งนั้น

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

ฉันจะไปที่จุดตอบคำถามของคุณ

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

พวกเขาหมายถึงรูปแบบการออกแบบ eventing หรือไม่

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

เมื่อคุณกำหนดกิจกรรมเช่นในตัวอย่างนี้:

class MyClass {
  // Note: EventHandler is just a multicast delegate,
  // that returns void and accepts (object sender, EventArgs e)!
  public event EventHandler MyEvent;

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

คอมไพเลอร์แปลงสิ่งนี้เป็นโค้ดต่อไปนี้:

class MyClass {
  private EventHandler MyEvent = null;

  public void add_MyEvent(EventHandler value) {
    MyEvent += value;
  }

  public void remove_MyEvent(EventHandler value) {
    MyEvent -= value;
  }

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

จากนั้นคุณสมัครสมาชิกกิจกรรมโดยทำ

MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;

ซึ่งคอมไพล์ลงไป

MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));

นั่นคืออีเวนติ้งใน C # (หรือ. NET โดยทั่วไป)

วิธีการจัดองค์ประกอบจะง่ายถ้าใช้ผู้รับมอบสิทธิ์

สิ่งนี้อาจแสดงให้เห็นได้อย่างง่ายดาย:

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

interface RequiredMethods {
  void DoX();
  int DoY();
};

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

sealed class RequiredMethods {
  public Action DoX;
  public Func<int> DoY();
}

วิธีนี้ผู้โทรต้องสร้างอินสแตนซ์ของ RequiredMethods และเมธอดการเชื่อมโยงไปยังผู้รับมอบสิทธิ์ที่รันไทม์ นี้คือมักจะง่ายขึ้น

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

ประโยชน์ของการใช้อินเทอร์เฟซเมื่อมีกลุ่มของวิธีการที่เกี่ยวข้อง

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

ประโยชน์ของการใช้อินเทอร์เฟซถ้าคลาสต้องการเพียงหนึ่งการใช้งาน

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

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

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

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

ฉันหวังว่าคำถามของคุณจะได้รับคำตอบเพื่อความพึงพอใจของคุณ และอีกครั้งขอชื่นชมสำหรับคำถาม


2

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

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

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

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

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


1

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

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


1

การชี้แจงที่ใหญ่ที่สุดที่ฉันสามารถเสนอได้:

  1. ผู้รับมอบสิทธิ์กำหนดลายเซ็นของฟังก์ชั่น - พารามิเตอร์ที่ฟังก์ชั่นการจับคู่จะยอมรับและสิ่งที่มันจะกลับมา
  2. อินเทอร์เฟซเกี่ยวข้องกับชุดของฟังก์ชันเหตุการณ์คุณสมบัติและเขตข้อมูลทั้งหมด

ดังนั้น:

  1. เมื่อคุณต้องการทำให้ฟังก์ชั่นทั่วไปมีลายเซ็นเหมือนกันให้ใช้ผู้รับมอบสิทธิ์
  2. เมื่อคุณต้องการพูดคุยพฤติกรรมหรือคุณภาพของคลาสให้ใช้อินเทอร์เฟซ

1

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

ความแตกต่างที่แท้จริงคือ:

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

แน่นอน แต่สิ่งนี้หมายความว่าอย่างไร

ลองมาตัวอย่างนี้ (รหัสอยู่ใน haXe เนื่องจาก C # ของฉันไม่ดีมาก):

class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:T->Bool):Collection<T> { 
         //build a new collection with all elements e, such that predicate(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

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

ตัวอย่างถูกสร้างขึ้นมาก แต่ปัญหาสำคัญคือการเรียกใช้โค้ดที่c.filterมีค่าฟังก์ชั่นบางอย่างเป็นอาร์กิวเมนต์ไม่มีวิธีที่จะรู้ว่าอาร์กิวเมนต์นั้นเหมาะสมหรือไม่ (เช่นในที่สุดจะประหยัดค่าคงที่) รหัสที่สร้างค่าฟังก์ชั่นอาจหรืออาจไม่รู้ว่ามันจะถูกตีความว่าเป็นคำกริยา

ตอนนี้ขอเปลี่ยนสิ่ง:

interface Predicate<T> {
    function test(value:T):Bool;
}
class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:Predicate<T>):Collection<T> { 
         //build a new collection with all elements e, such that predicate.test(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

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

ดังนั้นเพื่อใช้ถ้อยคำใหม่สิ่งที่ถูกกล่าวข้างต้นในเพียงไม่กี่คำ:

  • อินเทอร์เฟซหมายถึงการใช้การพิมพ์เล็กน้อยและความสัมพันธ์ที่ชัดเจน
  • ผู้ได้รับมอบหมายหมายถึงการใช้การพิมพ์เชิงโครงสร้างและความสัมพันธ์โดยนัย

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


1
เป็นมูลค่าการกล่าวขวัญว่า "ใช้อินเทอร์เฟซอย่างชัดเจน" (ภายใต้ "subtyping เล็กน้อย") ไม่เหมือนกับการใช้งานอย่างชัดเจนหรือโดยนัยของสมาชิกอินเตอร์เฟส ใน C # คลาสจะต้องประกาศอินเทอร์เฟซที่ใช้อย่างชัดเจนเสมอตามที่คุณพูด แต่สมาชิกอินเทอร์เฟซอาจถูกนำไปใช้อย่างชัดเจน (เช่นint IList.Count { get { ... } }) หรือโดยนัย ( public int Count { get { ... } }) ความแตกต่างนี้ไม่เกี่ยวข้องกับการสนทนานี้ แต่สมควรได้รับการกล่าวถึงเพื่อหลีกเลี่ยงผู้อ่านที่สับสน
phoog

@phoog: ใช่ขอบคุณสำหรับการแก้ไข ฉันไม่รู้ด้วยซ้ำว่าคนแรกเป็นไปได้ แต่ใช่วิธีการที่ภาษาบังคับให้ใช้งานอินเตอร์เฟสแตกต่างกันมาก ใน Objective-C ตัวอย่างเช่นจะแจ้งเตือนคุณหากไม่มีการใช้งานเท่านั้น
back2dos

1
  1. รูปแบบการออกแบบ eventing หมายถึงโครงสร้างที่เกี่ยวข้องกับกลไกการเผยแพร่และสมัครสมาชิก กิจกรรมถูกเผยแพร่โดยแหล่งที่มาสมาชิกทุกคนจะได้รับสำเนาของรายการที่เผยแพร่และรับผิดชอบการกระทำของตนเอง นั่นเป็นกลไกคู่ที่หลวมเพราะผู้เผยแพร่ไม่จำเป็นต้องรู้ว่ามีสมาชิกอยู่ นับประสามีสมาชิกไม่จำเป็น (มีไม่สามารถ)
  2. องค์ประกอบคือ "ง่ายขึ้น" ถ้ามีการใช้ผู้ร่วมประชุมเมื่อเทียบกับอินเทอร์เฟซ อินเทอร์เฟซกำหนดคลาสอินสแตนซ์เป็นวัตถุ "ชนิดของตัวจัดการ" ซึ่งเป็นอินสแตนซ์สร้างองค์ประกอบดูเหมือนจะ "มีตัวจัดการ" ดังนั้นลักษณะที่ปรากฏของอินสแตนซ์นั้นถูก จำกัด น้อยลงโดยใช้ผู้รับมอบสิทธิ์และมีความยืดหยุ่นมากขึ้น
  3. จากความคิดเห็นด้านล่างฉันพบว่าฉันจำเป็นต้องปรับปรุงโพสต์ของฉันดูสิ่งนี้สำหรับการอ้างอิง: http://bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate

1
3. เหตุใดผู้ได้รับมอบหมายจึงต้องการการคัดเลือกนักแสดงมากขึ้นและทำไมการมีเพศสัมพันธ์ที่แน่นกว่าจึงเป็นประโยชน์? 4. คุณไม่จำเป็นต้องใช้เหตุการณ์เพื่อใช้ผู้ได้รับมอบหมายและด้วยผู้ได้รับมอบหมายมันก็เหมือนกับวิธีการถือ - คุณรู้ว่ามันเป็นประเภทส่งคืนและประเภทของอาร์กิวเมนต์ นอกจากนี้ทำไมเมธอดที่ประกาศในอินเตอร์เฟสทำให้การจัดการข้อผิดพลาดง่ายกว่าวิธีที่แนบกับผู้รับมอบสิทธิ์
Yam Marcovic

ในกรณีที่เป็นเพียงข้อมูลจำเพาะของผู้ได้รับมอบคุณก็ถูกต้อง ในกรณีของการจัดการเหตุการณ์ (อย่างน้อยนั่นคือสิ่งที่อ้างอิงของฉันเกี่ยวข้องกับ) คุณจำเป็นต้องสืบทอดรูปแบบของ eventArgs บางชนิดเพื่อทำหน้าที่เป็นที่เก็บคอนเทนเนอร์สำหรับประเภทที่กำหนดเองดังนั้นแทนที่จะสามารถทดสอบค่าที่คุณต้องการได้อย่างปลอดภัยเสมอ ต้องแกะชนิดของคุณก่อนจัดการค่า
Carlo Kuip

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

1
เริ่มแรกเราพูดถึงผู้ร่วมประชุม vs interfaces ไม่ใช่อีเวนต์ vs interfaces ประการที่สองการสร้างฐานกิจกรรมบนตัวแทน EventHandler เป็นเพียงการประชุมและไม่จำเป็นต้องมีข้อบังคับ แต่อีกครั้งการพูดคุยเกี่ยวกับเหตุการณ์ที่นี่คิดถึงจุด สำหรับความคิดเห็นที่สองของคุณ - ข้อยกเว้นไม่ได้รับการตรวจสอบใน C # คุณกำลังสับสนกับ Java (ซึ่งบังเอิญไม่มีแม้แต่ผู้รับมอบสิทธิ์)
Yam Marcovic

พบกระทู้ในหัวข้อนี้เพื่ออธิบายความแตกต่างที่สำคัญ: bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.