อินเทอร์เฟซควรขยาย (และในการทำสืบทอดวิธีของ) อินเทอร์เฟซอื่น ๆ


13

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

public interface IContextProvider
{
   IDataContext { get; set; }
   IAreaContext { get; set; }
}

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

public interface IEventContextFactory 
{
   IEventContext CreateEventContext(int eventId);
}

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

// example of problem
public class MyClass
{
    public MyClass(IContextProvider context, IEventContextFactory event)
    {
       _context = context;
       _event = event;
    }

    public void DoSomething() 
    {
       // the only place _event is used in the class is to pass it through
       var myClass = new MyChildClass(_event);
       myClass.PerformCalculation();      
    }
}

ดังนั้นคำถามหลักของฉันคือสิ่งนี้จะเป็นที่ยอมรับหรือเป็นเรื่องปกติหรือเป็นแนวปฏิบัติที่ดีที่จะทำสิ่งนี้ (ส่วนต่อขยายไปยังส่วนต่อประสานอื่น):

public interface IContextProvider : IEventContextFactory

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


2
ฉันจะใส่ชื่อคำถามของคุณใหม่ว่า "อินเทอร์เฟซควรสืบทอดส่วนต่อประสาน" เนื่องจากไม่มีส่วนต่อประสานที่ใช้งานจริง
เจสซี C. ตัวแบ่ง

2
ภาษาปกติคืออินเทอร์เฟซ "ขยาย" พาเรนต์ของมันและ (ในการทำเช่นนั้น) "สืบทอด" เมธอดของอินเตอร์เฟสพาเรนต์ดังนั้นฉันขอแนะนำให้ใช้ "ขยาย" ที่นี่
Theodore Murdock

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

คำตอบ:


10

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

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


6

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

  1. อินเทอร์เฟซ A เพิ่มส่วนต่อประสาน B อย่างมีเหตุผลหรือไม่เช่น IList เพิ่มฟังก์ชันการทำงานให้กับ ICollection
  2. อินเตอร์เฟส A จำเป็นต้องกำหนดลักษณะการทำงานที่ระบุไว้แล้วโดยอินเทอร์เฟซอื่นตัวอย่างเช่นการนำ IDisposable ไปใช้หากมีทรัพยากรที่จำเป็นต้องมีการจัดระเบียบหรือ IEnumerable ถ้าสามารถวนซ้ำได้
  3. การใช้อินเตอร์เฟส A ทุกครั้งจำเป็นต้องมีพฤติกรรมที่ระบุโดยอินเตอร์เฟส B หรือไม่

ในตัวอย่างของคุณฉันไม่คิดว่ามันจะตอบตกลงกับคนเหล่านั้น คุณอาจจะดีกว่าที่จะเพิ่มเป็นคุณสมบัติอื่น:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}

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

1
ความแตกต่างคือถ้าคุณIContextProviderขยายIEventContextFactoryการContextProviderใช้งานจะต้องใช้วิธีการนั้นเช่นกัน หากคุณเพิ่มเป็นคุณสมบัติคุณกำลังบอกว่า a ContextProviderมีIEventContextFactoryแต่ไม่ทราบรายละเอียดการใช้งานจริง
Trevor Pilley

4

ใช่มันเป็นเรื่องดีที่จะขยายส่วนต่อขยายจากอีกส่วนหนึ่ง

คำถามที่ว่าสิ่งที่ถูกต้องในกรณีเฉพาะนั้นขึ้นอยู่กับว่ามีความสัมพันธ์แบบ "is-a" ที่แท้จริงระหว่างสองสิ่งนี้หรือไม่

สิ่งที่จำเป็นสำหรับความสัมพันธ์ "is-a" ที่ถูกต้องคืออะไร

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

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

หากทั้งสองอย่างนั้นเป็นจริงการสืบทอดคือความสัมพันธ์ที่ถูกต้องสำหรับสองอินเตอร์เฟส


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

1

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

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


ขอบคุณสำหรับสิ่งนั้น คุณหมายถึงการประพฤติตนเป็นลักษณะมากกว่าความรับผิดชอบ
dreza

@dreza ฉันหมายถึงความรับผิดชอบเช่นเดียวกับใน SRP ใน SOLID และลักษณะเหมือนบางอย่างเช่นลักษณะของสกาล่า แนวคิดของอินเตอร์เฟสเป็นส่วนใหญ่ทำให้เกิดปัญหาของคุณ มันแสดงถึงส่วนหลักของปัญหาหรือมุมมองเป็นชิ้นส่วนทั่วไปของประเภทที่แตกต่างกันอย่างอื่น?
Telastyn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.