อินเตอร์เฟสว่างเพื่อรวมหลายอินเตอร์เฟส


20

สมมติว่าคุณมีสองอินเตอร์เฟส:

interface Readable {
    public void read();
}

interface Writable {
    public void write();
}

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

// can't write to it without explicit casting
Readable myObject = new MyObject();

// can't read from it without explicit casting
Writable myObject = new MyObject();

// tight coupling to actual implementation
MyObject myObject = new MyObject();

ไม่มีตัวเลือกเหล่านี้สะดวกมากยิ่งขึ้นดังนั้นเมื่อพิจารณาว่าคุณต้องการให้นี่เป็นพารามิเตอร์วิธี

ทางออกหนึ่งคือการประกาศส่วนต่อประสานการห่อ:

interface TheWholeShabam extends Readable, Writable {}

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

มีวิธีแก้ไขปัญหานี้หมดจดหรือฉันควรจะไปหาส่วนต่อประสานห่อหุ้ม?

UPDATE

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

UPDATE2

(แยกเป็นคำตอบดังนั้นจึงง่ายที่จะแสดงความคิดเห็นใน)

Update3

โปรดระวังว่าการใช้ usecase หลักนี้ไม่ใช่สตรีม (แม้ว่าจะต้องได้รับการสนับสนุนด้วย) ลำธารทำให้เกิดความแตกต่างอย่างมากระหว่างอินพุตและเอาต์พุตและมีการแบ่งแยกหน้าที่ที่ชัดเจน แต่ลองนึกถึงสิ่งที่คล้ายกับ bytebuffer ที่คุณต้องการวัตถุหนึ่งชิ้นที่คุณสามารถเขียนและอ่านได้วัตถุหนึ่งที่มีสถานะเฉพาะเจาะจงติดอยู่ วัตถุเหล่านี้มีอยู่เพราะมีประโยชน์มากสำหรับบางสิ่งเช่น I / O แบบอะซิงโครนัสการเข้ารหัส ...

UPDATE4

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

สมมติว่าคุณมีคลาสที่ต้องส่งคืนชนิด:

public <RW extends Readable & Writable> RW getItAll();

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

MyObject myObject = someInstance.getItAll();

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

นอกจากนี้หากคุณต้องการตัวแปรคลาสของประเภท RW คุณจะต้องกำหนดค่าทั่วไปในระดับชั้นเรียน


5
วลีนี้คือ "she shebang ทั้งหมด"
kevin cline

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

@Basueta ในขณะที่การตั้งชื่อนั้นง่ายขึ้นการอ่านและเขียนได้สามารถสื่อถึง usecase ของฉันได้ค่อนข้างดี ในบางกรณีคุณต้องการอ่านอย่างเดียวในบางกรณีเขียนอย่างเดียวและในกรณีที่อ่านและเขียนได้อย่างน่าประหลาดใจ
nablex

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

@Baqueta จะทำอะไรกับแพ็คเกจ java.nio? หากคุณยึดติดกับลำธาร usecase จะถูก จำกัด เฉพาะสถานที่ที่คุณจะใช้ ByteArray * Stream
nablex

คำตอบ:


19

ได้คุณสามารถประกาศพารามิเตอร์ method เป็นประเภท underspecified ที่ขยายทั้งสองReadableและWritable:

public <RW extends Readable & Writable> void process(RW thing);

การประกาศวิธีการนั้นดูแย่มาก แต่การใช้มันง่ายกว่าการรู้อินเทอร์เฟซแบบรวม


2
ฉันต้องการมากชอบ Konrads แนวทางที่สองที่นี่: และถ้าคุณต้องเรียกใช้มันprocess(Readable readThing, Writable writeThing) process(foo, foo)
Joachim Sauer

1
ไวยากรณ์<RW extends Readable&Writable>ไม่ถูกต้องใช่ไหม
จาก

1
@JoachimSauer: ทำไมคุณต้องการวิธีการที่แตกง่ายกว่าที่น่าเกลียดภาพ? ถ้าฉันเรียกโปรเซส (foo, bar) และ foo and bar นั้นแตกต่างกันเมธอดอาจล้มเหลว
Michael Shaw

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

@JoachimSauer: ทำไมมันไม่ล้มเหลวล่ะ สำหรับ (i = 0; j <100; i ++) จะไม่เป็นประโยชน์ต่อลูปสำหรับ (i = 0; i <100; i ++) การวนรอบทั้งอ่านและเขียนไปยังตัวแปรเดียวกันและมันไม่ได้ละเมิด SRP ที่จะทำเช่นนั้น
Michael Shaw

12

หากมีสถานที่ที่คุณต้องการmyObjectทั้งเป็นReadableและWritableคุณสามารถ:

  • Refactor นั้น การอ่านและการเขียนเป็นสองสิ่งที่แตกต่างกัน หากวิธีการทั้งคู่อาจจะไม่เป็นไปตามหลักการความรับผิดชอบเดียว

  • ผ่านmyObjectสองครั้งเป็นReadableและและWritable(สองข้อโต้แย้ง) วิธีการดูแลไม่ว่าจะเป็นหรือไม่เป็นวัตถุเดียวกัน


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

มาทำไม สตรีมเอาต์พุตเขียนตามชื่อบ่งชี้ - เป็นสตรีมอินพุตที่สามารถอ่านได้ เหมือนกันใน C # - มีการStreamWriterเปรียบเทียบStreamReader(และอื่น ๆ อีกมากมาย)
Konrad Morawski

คุณเขียนถึง ByteArrayOutputStream เพื่อรับไบต์ (toByteArray ()) นี่เทียบเท่ากับเขียน + อ่าน ฟังก์ชันการทำงานจริงที่อยู่เบื้องหลังอินเทอร์เฟซเหมือนกันมาก แต่ในลักษณะทั่วไปมากกว่า บางครั้งคุณจะต้องการอ่านหรือเขียนบางครั้งคุณจะต้องการทั้งสองอย่าง ตัวอย่างอื่นคือ ByteBuffer
nablex

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

2
@Phoshi ปัญหาคือความกังวลไม่ได้แยกออกจากกันเสมอไป บางครั้งคุณต้องการวัตถุที่สามารถอ่านและเขียนได้และคุณต้องการการรับประกันว่ามันเป็นวัตถุเดียวกัน (เช่น ByteArrayOutputStream, ByteBuffer, ... )
nablex

4

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

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

interface Container extends Readable, Writable {}

อย่างน้อยตอนนี้คุณสามารถทำได้:

Container container = IOUtils.newContainer();
container.write("something".getBytes());
System.out.println(IOUtils.toString(container));

การใช้งานคอนเทนเนอร์ของฉันเอง (ปัจจุบัน 3) ใช้งานคอนเทนเนอร์ทั้งหมดซึ่งต่างจากอินเทอร์เฟซที่แยกต่างหาก แต่ควรมีใครลืมสิ่งนี้ในการนำไปใช้งาน IOUtils จัดเตรียมวิธีการยูทิลิตี้:

Readable myReadable = ...;
// assuming myReadable is also Writable you can do this:
Container container = IOUtils.toByteContainer(myReadable);

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


1
ฉันคิดว่ามันดีจริง ๆ ดีกว่าวิธีอื่น ๆ ที่เสนอในคำตอบอื่น
Tom Anderson

0

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

if (myObject instanceof Readable)  {
    Readable  r = (Readable) myObject;
    readThisReadable( r );
}

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

ดังนั้นฉันอาจจะไปกับสิ่งนี้:

interface TheWholeShabam  {
    public boolean  isReadable();
    public boolean  isWriteable();
    public void     read();
    public void     write();
}

การใช้พารามิเตอร์readThisReadableนี้เป็นตอนนี้readThisWholeShabamสามารถจัดการกับคลาสใด ๆ ที่ใช้ TheWholeShabam ไม่ใช่แค่ MyObject และมันสามารถเขียนได้ถ้ามันเขียนได้และไม่เขียนถ้าไม่ใช่ (เรามี "Polymorphism" จริง ๆ )

ดังนั้นรหัสชุดแรกจึงกลายเป็น:

TheWholeShabam  myObject = ...;
if (myObject.isReadable()
    readThisWholeShebam( myObject );

และคุณสามารถบันทึกบรรทัดที่นี่โดย readThisWholeShebam () ทำการตรวจสอบการอ่าน

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

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

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