การเชื่อมต่อ - ประเด็นคืออะไร?


269

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

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

ในตัวอย่างที่ระบุด้านล่างนำมาจากเธรดอินเทอร์เฟซ C # ที่แตกต่างกันบนสแตกล้นฉันจะสร้างคลาสพื้นฐานที่เรียกว่าพิซซ่าแทนอินเทอร์เฟซ

ตัวอย่างง่าย ๆ (นำมาจากการสนับสนุนสแตกล้นที่แตกต่างกัน)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

ฉันรู้สึกว่ามันซ้ำซ้อนกับคำถามนี้ที่ SO แต่ดูเหมือนพวกเขาทั้งหมดจะอธิบายเฉพาะส่วนสัญญาของอินเทอร์เฟซดังนั้นฉันไม่แน่ใจว่าพวกเขาจะใช้
Lasse V. Karlsen

7
พยายามเป็นผู้ใช้ที่ดีและเป็นระเบียบเรียบร้อยฉันมักจะมองหาคำตอบของฉันก่อนในฟอรัมต่าง ๆ ก่อนที่จะโพสต์บางสิ่ง น่าเสียดายที่พวกเขาส่วนใหญ่เริ่มในเวลาต่อมาและคนอื่น ๆ ก็ไม่ได้ช่วยอะไรเลย ฉันกำลังดิ้นรนกับพื้นฐาน "ทำไมมัน?" ดูเหมือนว่าฉันจะชอบมันมากเกินไปโดยไม่จำเป็น Btw ขอบคุณทุกคำตอบที่รวดเร็วมาก ฉันต้องย่อยพวกเขาทั้งหมดก่อน แต่ฉันคิดว่าตอนนี้ฉันมีความคิดที่ดีพอสมควรเกี่ยวกับประเด็นของพวกเขา ดูเหมือนว่าฉันจะมองจากมุมที่แตกต่าง ขอบคุณมาก ๆ สำหรับความช่วยเหลือของคุณ.
Nebelhom


1
นอกจากนี้อินเตอร์เฟสยังช่วยในการสร้างการสืบทอดเช่นstructประเภท
ja72

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

คำตอบ:


176

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

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

Python ไม่ได้ถูกพิมพ์แบบสแตติกดังนั้นประเภทจะถูกเก็บไว้และค้นหาที่รันไทม์ ดังนั้นคุณสามารถลองเรียกใช้Order()เมธอดบนวัตถุใดก็ได้ รันไทม์มีความสุขตราบเท่าที่วัตถุมีวิธีการดังกล่าวและอาจแค่ยักไหล่และบอกว่า "Meh." ถ้าไม่ ไม่เช่นนั้นใน C # คอมไพเลอร์มีหน้าที่ในการโทรออกที่ถูกต้องและถ้ามันมีเพียงบางสุ่มobjectคอมไพเลอร์ไม่ทราบว่าอินสแตนซ์ในช่วงรันไทม์จะมีวิธีการที่ จากมุมมองของคอมไพเลอร์มันไม่ถูกต้องเนื่องจากไม่สามารถตรวจสอบได้ (คุณสามารถทำสิ่งต่าง ๆ ด้วยการไตร่ตรองหรือdynamicคำหลัก แต่ตอนนี้ฉันเดาไปได้แล้ว)

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


1
+1 แม้ว่าฉันจะไม่พูดว่าส่วนต่อประสาน (ในความหมายของสัญญา) อาจเป็นนามธรรมหรือคลาสปกติ
Groo

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

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

1
ถ้าอย่างนั้นคำถามของคุณจะกลายเป็น: "ทำไมต้องใช้ภาษาที่พิมพ์แบบคงที่เมื่อภาษาที่พิมพ์แบบไดนามิกนั้นง่ายต่อการตั้งโปรแกรม" แม้ว่าฉันไม่ใช่ผู้เชี่ยวชาญที่จะตอบคำถามนั้น แต่ฉันก็กล้าที่จะพูดว่าการแสดงเป็นปัญหาในการตัดสินใจ เมื่อมีการโทรไปยังวัตถุหลามกระบวนการจะต้องตัดสินใจในช่วงเวลาการทำงานว่าวัตถุนั้นมีวิธีการที่เรียกว่า "สั่งซื้อ" ในขณะที่ถ้าคุณทำการโทรไปยังวัตถุ C # ก็เป็นที่ยอมรับแล้วว่ามันใช้วิธีนั้นและ สามารถโทรไปยังที่อยู่ดังกล่าวได้
Boluc Papuccuoglu

3
@BolucPapuccuoglu: นอกเหนือจากนั้นด้วยวัตถุแบบคงที่พิมพ์ถ้าใครรู้ว่าfooการดำเนินการIWidgetแล้วโปรแกรมเมอร์เห็นการเรียกร้องให้foo.woozle()สามารถดูเอกสารสำหรับIWidgetและรู้ว่าวิธีที่ควรจะทำ โปรแกรมเมอร์อาจไม่มีทางรู้ว่ารหัสสำหรับการใช้งานจริงนั้นมาจากที่ใด แต่ประเภทใดที่ปฏิบัติตามIWidgetสัญญาส่วนต่อประสานจะนำไปใช้fooในลักษณะที่สอดคล้องกับสัญญานั้น ในภาษาแบบไดนามิกตรงกันข้ามไม่มีจุดอ้างอิงที่ชัดเจนสำหรับสิ่งที่foo.woozle()ควรหมายถึง
supercat

440

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

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

เมื่อเขียนเป็นรหัสคุณสามารถทำได้ในทางเทคนิค

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

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

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

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

ตอนนี้รหัสการจัดการคำสั่งซื้อของคุณไม่จำเป็นต้องรู้ว่าพิซซ่าประเภทใดที่สั่งให้จัดการส่วนผสม มันมี:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

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


11
+1 ฉันชอบตัวอย่างของการใช้รายการเพื่อแสดงการใช้อินเทอร์เฟซ
danielunderwood

21
คำตอบที่ดี แต่อาจแก้ไขเพื่อชี้แจงว่าทำไมอินเทอร์เฟซในกรณีนี้ดีกว่าแค่ใช้คลาสนามธรรม (เช่นตัวอย่างง่ายๆคลาสนามธรรมอาจดีกว่าจริง ๆ ?)
Jez

3
นี่คือคำตอบที่ดีที่สุด มีการพิมพ์ผิดเล็กน้อยหรืออาจเป็นเพียงคุณคัดลอกและวางรหัส ใน C # publicอินเตอร์เฟซที่คุณไม่ได้ประกาศการปรับเปลี่ยนการเข้าถึงเช่น ดังนั้นภายในอินเทอร์เฟซมันควรจะเป็นอย่างนั้นvoid Prepare();
Dush

26
ฉันไม่เห็นว่าสิ่งนี้ตอบคำถามได้อย่างไร ตัวอย่างนี้สามารถทำได้อย่างง่ายดายด้วยคลาสฐานและวิธีนามธรรมซึ่งเป็นสิ่งที่คำถามดั้งเดิมชี้ให้เห็น
Jamie Kitson

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

123

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

(สิ่งนี้จะสูญเสียการเปรียบเทียบพิซซ่าเนื่องจากมันไม่ง่ายเลยที่จะเห็นภาพการใช้สิ่งนี้)

สมมติว่าคุณกำลังสร้างเกมง่ายๆบนหน้าจอและมันจะมีสิ่งมีชีวิตที่คุณโต้ตอบ

ตอบ: พวกเขาสามารถทำให้รหัสของคุณง่ายต่อการบำรุงรักษาในอนาคตโดยการแนะนำการเชื่อมต่อระหว่างส่วนหน้าของคุณและการใช้งานส่วนหลังของคุณ

คุณสามารถเขียนสิ่งนี้เพื่อเริ่มต้นเนื่องจากมีเพียงจะหมุนรอบ:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

ส่วนหน้า:

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

สองสัปดาห์ที่ผ่านมาการตลาดตัดสินใจว่าคุณต้องมี Orcs เช่นเดียวกับที่พวกเขาอ่านเกี่ยวกับพวกเขาใน twitter ดังนั้นคุณจะต้องทำอะไรเช่น:

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

ส่วนหน้า:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

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

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

ส่วนหน้าคือ:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

ตอนนี้ส่วนหน้าสนใจเฉพาะส่วนต่อประสาน ICreature - มันไม่ได้กังวลเกี่ยวกับการใช้งานภายในของ troll หรือ orc แต่เพียงความจริงที่ว่าพวกเขาใช้ ICreature

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

และคุณสามารถแยกสิ่งสร้างออกจากโรงงานได้:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

และส่วนหน้าของเราก็จะกลายเป็น:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

ส่วนหน้าตอนนี้ไม่จำเป็นต้องมีการอ้างอิงไปยังห้องสมุดที่มีการใช้งาน Troll และ Orc (ให้โรงงานอยู่ในห้องสมุดแยก) - มันไม่จำเป็นต้องรู้อะไรเกี่ยวกับพวกมันเลย

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

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

ส่วนหน้าอาจเป็น:

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C: การใช้งานสำหรับการฉีดพึ่งพา

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

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

ส่วนหน้าของเราสามารถทำการฉีดได้ (เช่นตัวควบคุม MVC API) ผ่านตัวสร้าง (โดยทั่วไป):

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

ด้วยโครงสร้าง DI ของเรา (เช่น Ninject หรือ Autofac) เราสามารถตั้งค่าเพื่อให้สร้างอินสแตนซ์ของ CreatureFactory ที่รันไทม์เมื่อใดก็ตามที่จำเป็นต้องมี ICreatureFactory ใน Constructor - สิ่งนี้ทำให้โค้ดของเราดีและเรียบง่าย

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

D: มีการใช้งานอื่น ๆ เช่นคุณมีสองโครงการ A และ B ที่ด้วยเหตุผล 'มรดก' ไม่ได้มีโครงสร้างที่ดีและ A มีการอ้างอิงถึง B

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

คุณสามารถมีอินเตอร์เฟสที่ประกาศใน B ว่าคลาสใน A นั้นนำมาใช้ วิธีการของคุณใน B สามารถส่งผ่านอินสแตนซ์ของคลาสที่ใช้อินเทอร์เฟซโดยไม่มีปัญหาแม้ว่าวัตถุคอนกรีตเป็นชนิดใน A.


6
ไม่ใช่เรื่องน่ารำคาญเมื่อคุณพบคำตอบที่พยายามจะพูดในสิ่งที่คุณเป็น แต่มันดีกว่ามาก - stackoverflow.com/a/93998/117215
Paddy

18
ข่าวดีก็คือตอนนี้มันเป็นหน้าตาย เป็นตัวอย่างที่ดี!
Sealer_05

1
การสนทนาของคุณหายไปเล็กน้อย แต่ฉันชอบการสนทนา A และ B ของคุณเพราะพวกเขาทั้งคู่อธิบายว่าอินเตอร์เฟซสามารถนำมาใช้เพื่อให้การทำงานประเภททั่วไปในหลายคลาสได้อย่างไร พื้นที่ของอินเทอร์เฟซที่ยังคงคลุมเครือสำหรับฉันคือวิธีการใช้อินเตอร์เฟสสำหรับการเชื่อมต่อแบบหลวม บางทีนั่นคือสิ่งที่การสนทนา C ของคุณกำลังพูดถึงอยู่? ถ้าเป็นเช่นนั้นฉันเดาว่าฉันต้องการตัวอย่างที่มีรายละเอียดเพิ่มเติม :)
user2075599

1
ดูเหมือนว่าในตัวอย่างของคุณที่ ICreature น่าจะเหมาะกว่าที่จะเป็นคลาสเบสที่เป็นนามธรรม (Creature?) สำหรับ Troll และ Orc จากนั้นตรรกะทั่วไประหว่างสิ่งมีชีวิตสามารถนำไปใช้ได้ที่นั่น ซึ่งหมายความว่าคุณไม่ต้องการอินเทอร์เฟซ ICreature เลย ...
Skarsnik

1
@Skarsnik - ค่อนข้างซึ่งเป็นสิ่งที่บันทึกนี้เป็นเรื่องเกี่ยวกับ: "จุดสำคัญที่ควรทราบเมื่อดูที่นี่จากมุมมองนี้คือว่าคุณสามารถใช้ชั้นสิ่งมีชีวิตที่เป็นนามธรรมได้อย่างง่ายดายและจากมุมมองนี้สิ่งนี้มีเหมือนกัน ผล."
Paddy

33

นี่คือตัวอย่างของคุณอธิบายอีกครั้ง:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}

27

ตัวอย่างข้างต้นไม่สมเหตุสมผลนัก คุณสามารถบรรลุตัวอย่างข้างต้นทั้งหมดโดยใช้คลาส (คลาสนามธรรมหากคุณต้องการให้มันทำงานเป็นสัญญาเท่านั้น):

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

คุณได้รับพฤติกรรมเช่นเดียวกับอินเทอร์เฟซ คุณสามารถสร้างList<Food>และย้ำว่าไม่มีชั้นเรียนอยู่

ตัวอย่างที่เพียงพอมากกว่านั้นคือการสืบทอดหลายแบบ:

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

จากนั้นคุณสามารถใช้ทั้งหมดเป็นMenuItemและไม่สนใจว่าพวกเขาจัดการแต่ละวิธีการโทร

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}

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

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

25

คำอธิบายง่ายๆพร้อมการเปรียบเทียบ

ปัญหาที่ต้องแก้ไข: อะไรคือจุดประสงค์ของความแตกต่าง?

ความคล้ายคลึง: ดังนั้นฉันจึงเป็นหัวหน้าคนงานในพื้นที่ก่อสร้าง

พ่อค้าเดินบนสถานที่ก่อสร้างตลอดเวลา ฉันไม่รู้ว่าใครกำลังจะเดินผ่านประตูเหล่านั้น แต่ฉันบอกพวกเขาว่าต้องทำอย่างไร

  1. ถ้าเป็นช่างไม้ฉันจะพูดว่า: สร้างนั่งร้านไม้
  2. ถ้าเป็นช่างประปาฉันพูดว่า: "ติดตั้งท่อ"
  3. ถ้าเป็นช่างไฟฟ้าฉันพูดว่า "ดึงสายเคเบิลออกแล้วเปลี่ยนเป็นสายใยแก้วนำแสง"

ปัญหาของวิธีการข้างต้นคือฉันต้อง: (i) รู้ว่าใครกำลังเดินเข้ามาในประตูนั้นและขึ้นอยู่กับว่ามันคือใครฉันต้องบอกพวกเขาว่าจะต้องทำอย่างไร นั่นหมายความว่าฉันต้องรู้ทุกอย่างเกี่ยวกับการค้าโดยเฉพาะ มีค่าใช้จ่าย / ผลประโยชน์ที่เกี่ยวข้องกับวิธีนี้:

ผลกระทบของการรู้ว่าจะทำอย่างไร:

  • ซึ่งหมายความว่าหากรหัสของช่างไม้เปลี่ยนจาก: BuildScaffolding()เป็นBuildScaffold()(เช่นเปลี่ยนชื่อเล็กน้อย) ดังนั้นฉันจะต้องเปลี่ยนคลาสที่เรียก (เช่นForepersonคลาส) ด้วย - คุณจะต้องทำการเปลี่ยนแปลงสองรหัสแทน (โดยทั่วไป ) แค่หนึ่ง. ด้วยความหลากหลายคุณ (โดยทั่วไป) จะต้องทำการเปลี่ยนแปลงเพียงครั้งเดียวเพื่อให้ได้ผลลัพธ์เดียวกัน

  • ประการที่สองคุณไม่ต้องถามอย่างต่อเนื่อง: คุณเป็นใคร ตกลงทำสิ่งนี้ ... คุณเป็นใคร ตกลงทำ ..... polymorphism - มันแห้งรหัสนั้นและมีประสิทธิภาพมากในบางสถานการณ์:

  • ด้วย polymorphism คุณสามารถเพิ่มคลาสการซื้อขายเพิ่มเติมได้อย่างง่ายดายโดยไม่ต้องเปลี่ยนรหัสใด ๆ ที่มีอยู่ (นั่นคือหลักการออกแบบที่สองของ SOLID: หลักการ Open-close)

การแก้ไขปัญหา

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

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

ดังนั้นแทนที่จะเป็นเช่นนี้:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

ฉันสามารถทำสิ่งนี้:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

ประโยชน์คืออะไร

ประโยชน์ก็คือถ้าความต้องการงานเฉพาะของช่างไม้เปลี่ยนแปลง ฯลฯ หัวหน้างานไม่จำเป็นต้องเปลี่ยนรหัสของเขา - เขาไม่จำเป็นต้องรู้หรือสนใจ สิ่งที่สำคัญคือช่างไม้รู้ว่าสิ่งใดมีความหมายโดย Work () ประการที่สองถ้าคนงานก่อสร้างรูปแบบใหม่เข้ามาในไซต์งานหัวหน้าคนงานไม่จำเป็นต้องรู้อะไรเกี่ยวกับการค้า - หัวหน้าคนงานทุกคนใส่ใจคือถ้าคนงานก่อสร้าง (.eg Welder, Glazier, Tiler และอื่น ๆ ) สามารถ ทำงานให้เสร็จ ()


ภาพประกอบปัญหาและแนวทางแก้ไข (มีและไม่มีส่วนต่อประสาน)

ไม่มีส่วนต่อประสาน (ตัวอย่างที่ 1):

ตัวอย่างที่ 1: ไม่มีส่วนต่อประสาน

ไม่มีส่วนต่อประสาน (ตัวอย่างที่ 2):

ตัวอย่างที่ 2: ไม่มีส่วนต่อประสาน

ด้วยอินเตอร์เฟส:

ตัวอย่างที่ 3: ประโยชน์ของการใช้อินเทอร์เฟซ

สรุป

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

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


4
บวกหนึ่งสำหรับผู้ชายที่เข้ามาทำงานใน hoodie
Yusha

11

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


3
+1 ถูกต้องถ้าคุณพิมพ์เป็ดคุณไม่ต้องการอินเทอร์เฟซ แต่พวกเขาบังคับใช้ความปลอดภัยประเภทที่สูงขึ้น
Groo

11

ตัวอย่างพิซซ่านั้นไม่ดีเพราะคุณควรใช้คลาสนามธรรมที่จัดการการสั่งซื้อและพิซซ่าควรแทนที่ประเภทพิซซ่าเช่น

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

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


10

พิจารณากรณีที่คุณไม่ได้ควบคุมหรือเป็นเจ้าของคลาสพื้นฐาน

ใช้การควบคุมด้วยภาพเช่นใน. NET สำหรับ Winforms ซึ่งสืบทอดมาจากการควบคุมคลาสพื้นฐานที่กำหนดไว้อย่างสมบูรณ์ใน. NET Framework

สมมติว่าคุณอยู่ในธุรกิจการสร้างการควบคุมแบบกำหนดเอง คุณต้องการสร้างปุ่มใหม่ textboxes, listviews, grids, whatnot และคุณต้องการให้ทุกคนมีคุณสมบัติบางอย่างที่ไม่เหมือนใครกับชุดควบคุมของคุณ

ตัวอย่างเช่นคุณอาจต้องการวิธีทั่วไปในการจัดการกับชุดรูปแบบหรือวิธีทั่วไปในการจัดการการแปล

ในกรณีนี้คุณไม่สามารถ "เพียงแค่สร้างคลาสพื้นฐาน" เพราะถ้าคุณทำเช่นนั้นคุณจะต้องปรับใช้ทุกสิ่งที่เกี่ยวข้องกับการควบคุม

แต่คุณจะลงมาจากปุ่มกล่องข้อความ ListView, GridView ฯลฯ และเพิ่มรหัสของคุณ

แต่สิ่งนี้ก่อให้เกิดปัญหาคุณจะทราบได้อย่างไรว่าตัวควบคุมใดเป็น "ของคุณ" คุณจะสร้างรหัสที่ระบุว่า "สำหรับตัวควบคุมทั้งหมดในรูปแบบที่เป็นของฉันได้อย่างไรตั้งค่าชุดรูปแบบเป็น X"

เข้าสู่ส่วนต่อประสาน

อินเทอร์เฟซเป็นวิธีการดูวัตถุเพื่อตรวจสอบว่าวัตถุยึดตามสัญญาบางอย่าง

คุณจะสร้าง "YourButton" ลงมาจากปุ่มและเพิ่มการสนับสนุนสำหรับอินเทอร์เฟซทั้งหมดที่คุณต้องการ

นี่จะอนุญาตให้คุณเขียนโค้ดดังนี้:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

สิ่งนี้จะเป็นไปไม่ได้หากไม่มีอินเตอร์เฟส แต่คุณต้องเขียนโค้ดดังนี้:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}

ใช่ฉันรู้ว่าคุณไม่ควรใช้ "เป็น" แล้วโยนทิ้งไว้
Lasse V. Karlsen

4
ถอนหายใจฉันรู้ แต่ที่เกิดจากอุบัติเหตุที่นี่
Lasse V. Karlsen

7

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

  1. คลาสสามารถใช้หลายอินเตอร์เฟส เพียงกำหนดคุณสมบัติที่คลาสต้องมี การใช้งานอินเทอร์เฟซที่หลากหลายหมายความว่าคลาสสามารถเติมเต็มฟังก์ชั่นที่หลากหลายในสถานที่ที่แตกต่างกัน

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

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


6

พิจารณาว่าคุณไม่สามารถใช้การสืบทอดหลายรายการใน C # แล้วดูคำถามของคุณอีกครั้ง



4

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

คุณเรียกวิธีการจากโรงงาน: MyInterface i = MyGraphics.getInstance(). MyInterfaceแล้วคุณมีการทำสัญญาเพื่อให้คุณรู้ว่าสิ่งที่ฟังก์ชั่นที่คุณสามารถคาดหวังใน ดังนั้นคุณสามารถโทรi.drawRectangleหรือi.drawCubeและรู้ว่าถ้าคุณสลับหนึ่งไลบรารี่หนึ่งไปยังอีกไลบรารี่หนึ่งฟังก์ชั่นนั้นจะรองรับ

สิ่งนี้มีความสำคัญมากขึ้นหากคุณใช้การพึ่งพาการพึ่งพาจากนั้นคุณสามารถสลับการใช้งานได้ในไฟล์ XML

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

สิ่งนี้ใช้คอลเลกชันจำนวนมากใน. NET อย่างที่คุณควรใช้ตัวอย่างเช่นListตัวแปรและไม่ต้องกังวลว่ามันเป็น ArrayList หรือ LinkedList

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

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


4

อินเทอร์เฟซเป็นสัญญาจริง ๆ ที่การใช้คลาสต้องปฏิบัติตามจริง ๆ แล้วเป็นพื้นฐานสำหรับรูปแบบการออกแบบทุกอย่างที่ฉันรู้

ในตัวอย่างของคุณอินเทอร์เฟซถูกสร้างขึ้นเพราะสิ่งที่IS A Pizza ซึ่งหมายถึงการใช้อินเทอร์เฟซของพิซซ่ารับประกันว่าจะมีการใช้งาน

public void Order();

หลังจากโค้ดที่กล่าวถึงของคุณคุณสามารถมีดังนี้:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

วิธีนี้คุณใช้ความหลากหลายและสิ่งที่คุณสนใจก็คือวัตถุของคุณตอบสนองต่อการสั่งซื้อ ()


4

ฉันค้นหาคำว่า "การแต่งเพลง" ในหน้านี้และไม่เห็นอีกเลย คำตอบนี้เป็นอย่างมากนอกเหนือจากคำตอบข้างต้น

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

บทแนะนำ "รูปแบบมัณฑนากร" ที่ยอดเยี่ยมนี้โดย Derek Banas (ซึ่ง - ตลกพอ - ใช้พิซซ่าเป็นตัวอย่าง) เป็นตัวอย่างที่มีค่า:

https://www.youtube.com/watch?v=j40kRwSm4VE


2
ฉันตกใจจริง ๆ ว่านี่ไม่ใช่คำตอบที่ดีที่สุด อินเทอร์เฟซในประโยชน์ทั้งหมดของพวกเขามีการใช้ที่ใหญ่ที่สุดในการจัดองค์ประกอบ
Maciej Sitko

3

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

public class Car { ... }

public class Tree { ... }

คุณต้องการเพิ่มฟังก์ชั่นที่ใช้งานได้ของทั้งสองคลาส แต่แต่ละชั้นเรียนมีวิธีการเผาไหม้ของตนเอง ดังนั้นคุณเพียงแค่ทำ;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

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

1
ตกลง. อินเตอร์เฟส = "สามารถทำได้" Class / Abstract Calss = "Is A"
Peter.Wang

3

คุณจะได้รับอินเทอร์เฟซเมื่อคุณต้องการ :) คุณสามารถศึกษาตัวอย่าง แต่คุณต้องการ Aha! ผลกระทบที่จะได้รับพวกเขาจริงๆ

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


3

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

ลองขยายความคล้ายคลึงกันของพิซซ่าเพื่อบอกมื้ออาหาร 3 คอร์สเต็มเปี่ยม เราจะยังคงมีPrepare()อินเทอร์เฟซหลักสำหรับทุกประเภทอาหารของเรา แต่เรายังมีการประกาศนามธรรมสำหรับการเลือกหลักสูตร (เริ่มต้นหลักของหวาน) และคุณสมบัติที่แตกต่างกันสำหรับประเภทอาหาร (คาวหวานหวานมังสวิรัติ / ไม่ใช่มังสวิรัติ ตังฟรี ฯลฯ )

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

หากฉันมีเวลามากขึ้นฉันอยากจะเขียนตัวอย่างแบบเต็มของสิ่งนี้หรือใครบางคนสามารถขยายสิ่งนี้ให้ฉันได้ แต่โดยสรุปอินเตอร์เฟส C # จะเป็นเครื่องมือที่ดีที่สุดในการออกแบบระบบประเภทนี้


1
คำตอบนี้สมควรได้รับคะแนนมากขึ้น! อินเทอร์เฟซส่องแสงเมื่อใช้ในรูปแบบที่ออกแบบเช่นรูปแบบของรัฐ ดูplus.google.com/+ZoranHorvat-Programmingเพื่อรับข้อมูลเพิ่มเติม
Alex Nolasco

3

นี่คืออินเทอร์เฟซสำหรับวัตถุที่มีรูปร่างเป็นสี่เหลี่ยม:

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

สิ่งที่ต้องการคือคุณใช้วิธีเข้าถึงความกว้างและความสูงของวัตถุ

ตอนนี้เราจะกำหนดวิธีการที่จะทำงานกับวัตถุใด ๆ ที่IRectangular:

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

ที่จะส่งคืนพื้นที่ของวัตถุสี่เหลี่ยมใด ๆ

ลองสร้างคลาสSwimmingPoolที่เป็นรูปสี่เหลี่ยมผืนผ้า:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

และอีกคลาสหนึ่งHouseที่เป็นรูปสี่เหลี่ยมผืนผ้าด้วย:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

ระบุว่าคุณสามารถเรียกAreaวิธีการในบ้านหรือสระว่ายน้ำ:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

ด้วยวิธีนี้คลาสของคุณสามารถ "รับช่วง" พฤติกรรม (static-methods) จากอินเตอร์เฟสจำนวนเท่าใดก็ได้


3

อินเทอร์เฟซกำหนดสัญญาระหว่างผู้ให้บริการของฟังก์ชันการทำงานบางอย่างและผู้บริโภคที่สอดคล้องกัน มันแยกการใช้งานจากสัญญา (อินเตอร์เฟส) คุณควรดูที่สถาปัตยกรรมและการออกแบบเชิงวัตถุ คุณอาจต้องการเริ่มต้นด้วย wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)


3

อะไร ?

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

ในC#ส่วนติดต่อชื่อตามการประชุมถูกกำหนดโดยคำนำหน้า 'ฉัน' ดังนั้นหากคุณต้องการให้ส่วนต่อประสานที่เรียกว่ารูปร่างคุณจะประกาศว่าเป็นIShapes

ตอนนี้ทำไม

Improves code re-usability

ช่วยบอกว่าคุณต้องการที่จะดึงCircle, Triangle. คุณสามารถจัดกลุ่มเข้าด้วยกันและเรียกพวกเขาShapesและมีวิธีการวาดCircleและTriangle แต่มีการดำเนินการที่เป็นรูปธรรมจะเป็นความคิดที่ไม่ดีเพราะวันพรุ่งนี้คุณอาจตัดสินใจที่จะมีอีก 2 &Shapes Rectangle Squareตอนนี้เมื่อคุณเพิ่มพวกเขามีโอกาสที่ดีที่คุณอาจทำลายส่วนอื่น ๆ ของรหัสของคุณ

ด้วยส่วนต่อประสานคุณสามารถแยกการใช้งานที่แตกต่างจากสัญญา


วันที่ถ่ายทอดสด 1

คุณถูกขอให้สร้างแอปเพื่อวาดวงกลมและสามเหลี่ยม

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

วันถ่ายทอดสด 2

หากคุณถูกขอให้เพิ่มSquareและเพิ่มRectangleสิ่งที่คุณต้องทำคือสร้างการฝังสำหรับรายการclass Square: IShapesและMainเพิ่มในรายการshapes.Add(new Square());


เหตุใดคุณจึงเพิ่มคำตอบสำหรับคำถามอายุ 6 ปีที่มีคำตอบอื่น ๆ อีกนับสิบรายการที่อัปโหลดหลายร้อยครั้ง ไม่มีอะไรที่นี่ที่ยังไม่ได้พูด
Jonathon Reinhart

@ JonathonReinhart ใช่ฉันคิดอย่างนั้น แต่ฉันคิดตัวอย่างนี้และวิธีที่อธิบายจะช่วยให้ร่างกายดีขึ้นกว่าคนอื่น ๆ
Clint

2

มีคำตอบที่ดีมากมายที่นี่ แต่ฉันต้องการที่จะลองจากมุมมองที่แตกต่างกันเล็กน้อย

คุณอาจคุ้นเคยกับหลักการของการออกแบบเชิงวัตถุ สรุป:

S - หลักการความรับผิดชอบเดี่ยว O - เปิด / ปิดหลักการ L - Liskov หลักการทดแทน I - หลักการแยกส่วนเชื่อมต่อ D - หลักการผกผันพึ่งพา

การปฏิบัติตามหลักการของ SOLID ช่วยในการสร้างรหัสที่มีความสะอาด ระบุว่า:

"การจัดการการพึ่งพาเป็นสิ่งท้าทายที่สำคัญในซอฟต์แวร์ทุกระดับ" (Donald Knuth)

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

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


1

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


1

เธเรเซถามตัวอย่างที่ดีเยี่ยมจริงๆ

ในกรณีของคำสั่ง switch คุณไม่จำเป็นต้องดูแลรักษาและสลับทุกครั้งที่คุณต้องการให้ rio ทำงานในลักษณะเฉพาะ

ในตัวอย่างพิซซ่าของคุณหากต้องการสร้างพิซซ่าอินเทอร์เฟซคือสิ่งที่คุณต้องการจากนั้นพิซซ่าแต่ละแห่งจะดูแลตรรกะของตัวเอง

สิ่งนี้จะช่วยลดการแต่งงานและความซับซ้อนของวงจร คุณยังต้องใช้ตรรกะ แต่จะมีน้อยกว่าที่คุณต้องติดตามในภาพที่กว้างขึ้น

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


1

วิธีที่ง่ายที่สุดในการคิดเกี่ยวกับอินเตอร์เฟสคือการจดจำความหมายของการสืบทอด หากคลาส CC สืบทอดคลาส C หมายความว่าทั้งสอง:

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

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

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

คุณลักษณะที่ดีอย่างหนึ่งของสิ่งนี้ (underutilized ใน. net framework, IMHO) คือการทำให้เป็นไปได้ที่จะระบุสิ่งที่วัตถุสามารถทำได้อย่างชัดเจน ยกตัวอย่างเช่นวัตถุบางอย่างจะต้องการวัตถุแหล่งข้อมูลที่สามารถดึงสิ่งต่าง ๆ ตามดัชนี (เท่าที่จะเป็นไปได้ด้วยรายการ) แต่พวกเขาไม่จำเป็นต้องเก็บอะไรไว้ที่นั่น กิจวัตรอื่น ๆ จะต้องมีวัตถุรับฝากข้อมูลที่พวกเขาสามารถจัดเก็บสิ่งที่ไม่ได้ตามดัชนี (เช่นเดียวกับ Collection.Add) แต่พวกเขาไม่จำเป็นต้องอ่านอะไรกลับ บางชนิดข้อมูลจะอนุญาตให้เข้าถึงโดยดัชนี แต่จะไม่อนุญาตให้เขียน; อื่น ๆ จะอนุญาตให้เขียน แต่จะไม่อนุญาตให้เข้าถึงโดยดัชนี แน่นอนว่าบางอย่างจะอนุญาตทั้งสองอย่าง

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


1

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

O = "ควรเปิดคลาสสำหรับส่วนขยาย แต่ปิดเพื่อแก้ไข"


1

สำหรับฉันข้อดี / ประโยชน์ของอินเทอร์เฟซคือมันมีความยืดหยุ่นมากกว่าคลาสนามธรรม เนื่องจากคุณสามารถรับคลาสนามธรรมเพียงคลาสเดียวเท่านั้น แต่คุณสามารถใช้หลายอินเตอร์เฟสได้การเปลี่ยนแปลงระบบที่สืบทอดคลาสนามธรรมในหลาย ๆ ที่กลายเป็นปัญหา หากได้รับการสืบทอดใน 100 แห่งการเปลี่ยนแปลงจำเป็นต้องเปลี่ยนแปลงทั้งหมด 100 แต่ด้วยอินเตอร์เฟสคุณสามารถทำการเปลี่ยนแปลงใหม่ในอินเทอร์เฟซใหม่และใช้อินเทอร์เฟซที่ต้องการ (Interface Seq. จาก SOLID) นอกจากนี้การใช้หน่วยความจำดูเหมือนจะน้อยลงด้วยอินเทอร์เฟซเป็นวัตถุในตัวอย่างอินเทอร์เฟซที่ใช้เพียงครั้งเดียวในหน่วยความจำแม้มีกี่ที่ใช้อินเทอร์เฟซ


1

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

ฉันจะให้ตัวอย่างสนับสนุนโดยกราฟิกด้านล่าง

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

ป้อนคำอธิบายรูปภาพที่นี่

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

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}

1
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

ให้ฉันอธิบายสิ่งนี้ด้วยมุมมองที่ต่างออกไป ลองสร้างเรื่องราวตามตัวอย่างที่ฉันได้แสดงไว้ด้านบน

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

และโปรแกรมเรียนรู้วิธีการใช้งานโดยความช่วยเหลือของ IMachine

อินเตอร์เฟซให้การสื่อสารและการพัฒนาโครงการคู่ที่หลวม

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


1

ฉันรู้ว่าฉันมาช้ามาก .. (เกือบเก้าปี) แต่ถ้ามีคนต้องการคำอธิบายเล็กน้อยคุณก็สามารถทำสิ่งนี้ได้:

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

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

หมายเหตุสำคัญ:ส่วนต่อประสานนั้นเป็นส่วนรวมเสมอ

หวังว่านี่จะช่วยได้

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