เมื่อใดควรใช้ generics ในการออกแบบอินเตอร์เฟส


11

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

ปัจจุบันพวกเขาถูกกำหนดเป็น

สิ่งของ:

public interface Item {

    String getId();

    String getName();
}

ItemStack:

public interface ItemStackFactory {

    ItemStack createItemStack(Item item, int quantity);
}

ItemStackContainer:

public interface ItemStackContainer {

    default void add(ItemStack stack) {
        add(stack, 1);
    }

    void add(ItemStack stack, int quantity);
}

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

ตอนนี้ฉันพยายามทำให้ห้องสมุดนี้แข็งแกร่งที่สุด นี่ยังอยู่ในช่วงเริ่มต้น (pre-pre-alpha) ดังนั้นนี่อาจเป็นการกระทำของวิศวกรรมมากกว่า (YAGNI) นี่เป็นสถานที่ที่เหมาะสมในการใช้ยาชื่อสามัญหรือไม่?

public interface ItemStack<T extends Item> {

    T getItem();

    int getQuantity();
}

และ

public interface ItemStackFactory<T extends ItemStack<I extends Item>> {

    T createItemStack(I item, int quantity);
}

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


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

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

คำตอบ:


9

คุณใช้ข้อมูลทั่วไปในอินเทอร์เฟซของคุณเมื่อการใช้งานของคุณมีแนวโน้มที่จะเป็นเรื่องทั่วไปเช่นกัน

ตัวอย่างเช่นโครงสร้างข้อมูลใด ๆ ที่สามารถยอมรับวัตถุโดยพลการนั้นเป็นตัวเลือกที่ดีสำหรับอินเทอร์เฟซทั่วไป ตัวอย่าง: และList<T>Dictionary<K,V>

สถานการณ์ใด ๆ ที่คุณต้องการปรับปรุงความปลอดภัยของประเภทในลักษณะทั่วไปเป็นผู้สมัครที่ดีสำหรับ generics A List<String>คือรายการที่ทำงานกับสตริงเท่านั้น

สถานการณ์ใด ๆ ที่คุณต้องการใช้ SRP ด้วยวิธีการทั่วไปและหลีกเลี่ยงวิธีการเฉพาะประเภทนั้นเป็นตัวเลือกที่ดีสำหรับ generics ยกตัวอย่างเช่น PersonDocument, PlaceDocument และ ThingDocument Document<T>สามารถถูกแทนที่ด้วย

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


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

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

ฉันเห็นด้วย. บางทีฉันอาจเข้าใจผิดคำตอบของคุณ - คุณดูเหมือนจะให้คำแนะนำกับการออกแบบประเภทนั้น
Benjamin Hodgson

@BenjaminHodgson: จะไม่เขียนวิธีการของโรงงานกับส่วนต่อประสานที่เป็นรูปธรรมแทนที่จะเป็นวิธีทั่วไปหรือไม่ (แม้ว่า Abstract Factory จะเป็นแบบทั่วไป)
Robert Harvey

ผมคิดว่าเรากำลังเห็นพ้อง :) ผมอาจจะเขียนตัวอย่าง OP class StackFactory { Stack<T> Create<T>(T item, int count); }ในฐานะที่เป็นสิ่งที่ชอบ ฉันสามารถเขียนตัวอย่างของสิ่งที่ฉันหมายถึงโดย "การปรับพารามิเตอร์ประเภทในคลาสย่อย" ในคำตอบถ้าคุณต้องการคำชี้แจงเพิ่มเติมแม้ว่าคำตอบจะเกี่ยวข้องกับคำถามดั้งเดิม
Benjamin Hodgson

8

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

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

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


3

ตอนนี้ItemและItemStackFactoryฉันสามารถคาดการณ์ได้อย่างแน่นอนว่าบุคคลที่สามบางคนจำเป็นต้องขยายในอนาคต

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

class MyItem implements Item {
}
MyItem item = new MyItem();
ItemStack is = createItemStack(item, 10);
// They would have to cast here.
MyItem theItem = (MyItem)is.getItem();

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


2

ว้าว ... ฉันมีความแตกต่างอย่างสิ้นเชิงกับเรื่องนี้

ฉันสามารถคาดการณ์ได้ว่าต้องการบุคคลที่สามบางอย่าง

นี่เป็นความผิดพลาด คุณไม่ควรเขียนรหัส "สำหรับอนาคต"

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


0

ฉันคิดว่าการใช้ยาชื่อสามัญในสถานการณ์ของคุณซับซ้อนเกินไป

หากคุณเลือกอินเทอร์เฟซรุ่น generics การออกแบบบ่งชี้ว่า

  1. ItemStackContainer <A> มีสแต็กชนิดเดียว, A. (เช่น ItemStackContainer ไม่สามารถมีสแต็กได้หลายประเภท)
  2. ItemStack ทุ่มเทให้กับรายการประเภทเฉพาะ ดังนั้นจึงไม่มีรูปแบบนามธรรมของสแต็คทุกชนิด
  3. คุณต้องกำหนด ItemStackFactory เฉพาะสำหรับแต่ละประเภทของรายการ ถือว่าดีเกินไปตามรูปแบบโรงงาน
  4. เขียนโค้ดที่ยากขึ้นเช่น ItemStack <? ขยายรายการ> ลดการบำรุงรักษา

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


0

ผมว่ามันขึ้นอยู่กับคำถามนี้: ถ้ามีคนต้องการที่จะขยายการทำงานของItemและItemStackFactory,

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

ในกรณีแรกไม่จำเป็นต้องใช้ยาชื่อสามัญในกรณีที่สองอาจมี


0

บางทีคุณอาจมีลำดับชั้นของอินเทอร์เฟซโดยที่ Foo เป็นหนึ่งอินเทอร์เฟซ Bar เป็นอีกส่วนหนึ่ง เมื่อใดก็ตามที่ประเภทจะต้องเป็นทั้งสองอย่างมันเป็น FooAndBar

หากต้องการหลีกเลี่ยง desgin ให้สร้างประเภท FooAndBar เมื่อจำเป็นเท่านั้นและเก็บรายการอินเทอร์เฟซที่จะไม่ขยายสิ่งใด ๆ

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

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


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