อินเทอร์เฟซจะถือว่าว่างหรือไม่ถ้ามันสืบทอดมาจากอินเทอร์เฟซอื่นหรือไม่


12

อินเทอร์เฟซที่ว่างเปล่าโดยทั่วไปจะพิจารณาการปฏิบัติที่ไม่ดีเท่าที่ฉันสามารถบอกได้ - โดยเฉพาะอย่างยิ่งเมื่อสิ่งต่าง ๆ เช่นแอตทริบิวต์ได้รับการสนับสนุนโดยภาษา

อย่างไรก็ตามอินเทอร์เฟซถูกพิจารณาว่า 'ว่าง' ถ้ามันได้รับมาจากอินเทอร์เฟซอื่นหรือไม่

interface I1 { ... }
interface I2 { ... } //unrelated to I1

interface I3
    : I1, I2
{
    // empty body
}

สิ่งใดที่ใช้I3จะต้องนำไปปฏิบัติI1และI2และวัตถุจากคลาสที่แตกต่างกันซึ่งสืบทอดI3สามารถนำมาใช้แทนกันได้ (ดูด้านล่าง) ดังนั้นมันถูกต้องหรือI3 เปล่าที่จะเรียกว่าว่างเปล่า ? ถ้าเป็นเช่นนั้นจะมีวิธีใดที่ดีกว่าในการสร้างสถาปัตยกรรมนี้

// with I3 interface
class A : I3 { ... }
class B : I3 { ... }

class Test {
    void foo() {
        I3 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function();
    }
}

// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }

class Test {
    void foo() {
        I1 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function(); // we can't do this without casting first
    }
}

1
การไม่สามารถระบุหลายอินเตอร์เฟสเป็นชนิดของพารามิเตอร์ / ฟิลด์ ฯลฯ เป็นข้อบกพร่องในการออกแบบใน C # / .NET
CodesInChaos

ฉันคิดว่าวิธีที่ดีกว่าในการออกแบบสถาปัตยกรรมในส่วนนี้ควรเป็นคำถามแยกต่างหาก ตอนนี้คำตอบที่ยอมรับไม่เกี่ยวข้องกับชื่อคำถาม
Kapol

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

มันเหมาะสมหรือไม่ที่คลาสที่ใช้ I1 และ I2 แต่ไม่ใช่ I3 จะไม่สามารถใช้งานได้fooหรือไม่ (หากคำตอบคือ "ใช่" ให้ดำเนินการต่อไป!)
253751

@ Andy ในขณะที่ฉันไม่เห็นด้วยกับ CodesInChaos ในการอ้างว่าเป็นข้อบกพร่องแต่ฉันไม่คิดว่าพารามิเตอร์ประเภททั่วไปในวิธีการนั้นเป็นวิธีแก้ปัญหา - มันผลักดันความรับผิดชอบในการรู้ประเภทที่ใช้ กำลังเรียกวิธีการซึ่งรู้สึกผิด บางทีไวยากรณ์บางอย่างเช่นvar : I1, I2 something = new A();ตัวแปรในท้องถิ่นอาจจะดี (ถ้าเราเพิกเฉยต่อคะแนนที่ดีในคำตอบของ Konrad)
Kai

คำตอบ:


27

อะไรก็ตามที่ใช้ I3 จะต้องใช้ I1 และ I2 และวัตถุจากคลาสที่แตกต่างกันซึ่งสืบทอด I3 นั้นสามารถใช้แทนกันได้ (ดูด้านล่าง) ดังนั้นจึงถูกต้องที่จะเรียก I3 ว่าว่างเปล่า? ถ้าเป็นเช่นนั้นจะมีวิธีใดที่ดีกว่าในการสร้างสถาปัตยกรรมนี้

"Bundling" I1และI2เข้าไปในI3ข้อเสนอที่ดี (?) ทางลัดหรือบางจัดเรียงของน้ำตาลไวยากรณ์สำหรับทุกที่ที่คุณต้องการทั้งสองจะต้องดำเนินการ

ปัญหาด้วยวิธีนี้คือการที่คุณไม่สามารถหยุดการพัฒนาอื่น ๆ อย่างชัดเจนจากการดำเนินการI1 และ I2เคียงข้างกัน แต่มันไม่ทำงานทั้งสองวิธี - ในขณะที่: I3จะมีค่าเท่ากับ: I1, I2, ไม่เทียบเท่า: I1, I2 : I3แม้ว่าพวกเขาจะหน้าที่เหมือนเป็นระดับที่สนับสนุนทั้งสองI1และจะไม่ถูกตรวจพบว่าการสนับสนุนI2I3

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

void foo() {
    I1 something = new A();
    something = new B();
    something.SomeI1Function();
    something.SomeI2Function(); // we can't do this without casting first
}

ตกลงคุณทำไม่ได้ แต่บางทีมันอาจเป็นสัญญาณที่ดี?

หากI1และI2บ่งบอกถึงความรับผิดชอบที่แตกต่างกัน (ไม่เช่นนั้นไม่จำเป็นต้องแยกพวกเขาออกจากตำแหน่งแรก) ดังนั้นอาจfoo()เป็นความผิดที่จะลองทำsomethingหน้าที่สองอย่างที่แตกต่างกันในคราวเดียว?

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

แก้ไข:

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

void foo(I1 i1, I2 i2) 

และพวกเขาอาจเป็นวัตถุเดียวกัน:

foo(something, something);

อะไรจะfooสนใจถ้าพวกเขาเป็นตัวอย่างเดียวกันหรือไม่?

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

void Foo<T>(T something) where T: I1, I2

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

ฉันเห็นI3 : I2, I1ว่าเป็นความพยายามที่จะเลียนแบบประเภทของสหภาพในภาษาที่ไม่สนับสนุนมันออกมานอกกรอบ (C # หรือ Java ไม่ในขณะที่ประเภทของสหภาพอยู่ในภาษาที่ใช้งานได้)

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


4

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

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


ฮะ? แน่นอนคุณสามารถใช้อินเทอร์เฟซที่สองในคลาสเดียว คุณไม่ได้สืบทอดอินเตอร์เฟสคุณใช้มัน
Andy

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

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

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

"การเปลี่ยนแปลงคำศัพท์ระหว่างภาษา" ไม่มันไม่ได้เป็นเช่นนั้น คำศัพท์ OO นั้นสอดคล้องกันในทุกภาษา ฉันไม่เคยได้ยินระดับนามธรรมมีความหมายอะไรอย่างอื่นในแต่ละภาษาของ OO ที่ฉันใช้ ใช่คุณสามารถมีซีแมนทิกส์ของอินเทอร์เฟซใน C ++ ได้ แต่ประเด็นก็คือไม่มีอะไรที่ป้องกันไม่ให้ใครบางคนไปและเพิ่มพฤติกรรมให้กับคลาสนามธรรมในภาษานั้นและทำลายซีแมนติกเหล่านั้น
Andy

2

อินเทอร์เฟซที่ว่างเปล่าโดยทั่วไปจะพิจารณาการปฏิบัติที่ไม่ดี

ฉันไม่เห็นด้วย. อ่านข้อมูลเกี่ยวกับการเชื่อมต่อเครื่องหมาย

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


ฉันจินตนาการว่า OP ตระหนักถึงพวกเขา แต่จริงๆแล้วพวกเขาเป็นที่ถกเถียงกันอยู่เล็กน้อย ในขณะที่ผู้เขียนบางคนแนะนำให้พวกเขา (เช่น Josh Bloch ใน "Java ที่มีประสิทธิภาพ") บางคนอ้างว่าพวกเขาเป็น antipattern และมรดกจากครั้งเมื่อ Java ยังไม่มีคำอธิบายประกอบ (คำอธิบายประกอบใน Java เป็นคร่าว ๆ เทียบเท่าคุณลักษณะใน. NET) ความประทับใจของฉันคือพวกเขาเป็นที่นิยมในโลก Java มากกว่า. NET
Konrad Morawski

1
ฉันใช้พวกเขานาน ๆ ครั้ง แต่มันก็รู้สึกแฮ็คค่อนข้างแย่ข้อเสียที่อยู่ด้านบนหัวของฉันคือคุณไม่สามารถช่วยให้พวกเขาได้รับมรดกดังนั้นเมื่อคุณทำเครื่องหมายในชั้นเรียนกับเด็กทุกคนจะได้รับ ทำเครื่องหมายด้วยไม่ว่าคุณต้องการหรือไม่ และดูเหมือนว่าพวกเขาสนับสนุนให้ใช้วิธีปฏิบัติที่ไม่ดีinstanceofแทนที่จะเป็นรูปแบบต่าง ๆ
Konrad Morawski
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.