Çoklu arayüzleri birleştirmek için boş arayüz


20

İki arayüzünüz olduğunu varsayalım:

interface Readable {
    public void read();
}

interface Writable {
    public void write();
}

Bazı durumlarda, uygulama nesneleri bunlardan sadece birini destekleyebilir, ancak çoğu durumda uygulamalar her iki arabirimi de destekler. Arayüzleri kullanan insanlar şöyle bir şey yapmak zorunda kalacaklar:

// 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();

Bu seçeneklerin hiçbiri son derece kullanışlı değildir, hatta bunu bir yöntem parametresi olarak istediğinizi düşünürken daha da fazladır.

Bir çözüm bir sarma arabirimi bildirmek olacaktır:

interface TheWholeShabam extends Readable, Writable {}

Okunabilir ve Yazılabilir hem destekleyen tüm uygulamaları: Ama bu belirli bir sorunu var olması onlar arabirimini kullanan kişilerle uyumlu olmasını istiyorsanız TheWholeShabam uygulamaktır. Her iki arayüzün garantili varlığından başka bir şey sunmasa da.

Bu sorunun temiz bir çözümü var mı yoksa sarıcı arabirimi için gitmeli miyim?

GÜNCELLEME

Aslında hem okunabilir hem de yazılabilir bir nesneye sahip olmak genellikle gereklidir, bu nedenle argümanlardaki endişeleri ayırmak her zaman temiz bir çözüm değildir.

Update2

(yorum olarak daha kolay cevap olarak çıkarılır)

Update3

Lütfen bunun için birincil kullanıcı tabanının akış olmadığına dikkat edin (bunlar da desteklenmelidir). Akarsu, girdi ve çıktı arasında çok belirgin bir ayrım yapar ve sorumlulukların arasında net bir ayrım vardır. Bunun yerine, yazabileceğiniz ve okuyabileceğiniz bir nesneye, kendisine çok özel bir duruma sahip bir nesneye ihtiyaç duyduğunuz bir bytebuffer gibi bir şey düşünün. Bu nesneler vardır, çünkü asenkron I / O, kodlamalar, ...

Update4

Denediğim ilk şeylerden biri, aşağıda verilen öneriyle aynıydı (kabul edilen cevabı kontrol edin), ancak çok kırılgan olduğu kanıtlandı.

Diyelim ki türü döndürmesi gereken bir sınıfınız var:

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

Bu yöntemi çağırırsanız, genel RW nesneyi alan değişken tarafından belirlenir, bu nedenle bu var.

MyObject myObject = someInstance.getItAll();

Bu işe yarayacaktır, ancak bir kez daha bir uygulamaya bağlar ve aslında çalışma zamanında sınıf ifadeleri atabilir (döndürülene bağlı olarak).

Buna ek olarak, RW türünde bir sınıf değişkeni istiyorsanız, sınıf düzeyinde genel tanımlamanız gerekir.


5
Ifade "bütün shebang"
kevin cline

Bu iyi bir soru, ancak bence örnek arayüzler olarak Okunabilir ve 'Yazılabilir' kullanmak, genellikle farklı roller olduklarından suları biraz
çamurluyor

@Basueta Adlandırma basitleştirilmiş olsa da, okunabilir ve yazılabilir aslında benim kullanıcı tabanımı oldukça iyi iletir. Bazı durumlarda yalnızca okumak istersiniz, bazı durumlarda yalnızca yazma ve şaşırtıcı miktarda okuma ve yazma.
nablex

Hem okunabilir hem de yazılabilir tek bir akışa ihtiyaç duyduğum bir zamanı düşünemiyorum ve diğer insanların cevaplarından / yorumlarından yola çıkarak tek olduğumu sanmıyorum. Sadece daha az tartışmalı bir arayüz çifti
seçmenin

@Baqueta java.nio * paketleri ile ilgili bir şey var mı? Akışlara sadık kalırsanız, kullanıcı tabanı gerçekten ByteArray * Stream'i kullanacağınız yerlerle sınırlıdır.
Nablex

Yanıtlar:


19

Evet, hem uzanan bir underspecified türü olarak yöntem parametresini ilan edebilir Readableve Writable:

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

Yöntem bildirimi korkunç görünüyor, ancak bunu kullanmak, birleştirilmiş arayüz hakkında bilgi sahibi olmaktan daha kolaydır.


2
Konrads'ın ikinci yaklaşımını burada daha çok tercih ederim: process(Readable readThing, Writable writeThing)ve bunu kullanarak çağırmanız gerekiyorsa process(foo, foo).
Joachim Sauer

1
Doğru sözdizimi değil <RW extends Readable&Writable>mi?
GELMEKTEDİR

1
@JoachimSauer: Neden sadece görsel olarak çirkin bir yaklaşımdan kolayca kopan bir yaklaşımı tercih edesiniz ki? Eğer süreç (foo, bar) ve foo ve bar farklıysa, yöntem başarısız olabilir.
Michael Shaw

@MichaelShaw: Söylediğim şey, farklı nesneler olduğunda başarısız olmamalı . Neden olsun ki? Eğer öyleyse, bunun processçok farklı şeyler yaptığını ve tek sorumluluk ilkesini ihlal ettiğini iddia ederim .
Joachim Sauer

@JoachimSauer: Neden başarısız olmaz? for (i = 0; j <100; i ++), (i = 0; i <100; i ++) kadar faydalı bir döngü değildir. For döngüsü aynı değişkeni hem okur hem de yazar ve bunu yapmak için SRP'yi ihlal etmez.
Michael Shaw

12

myObjectHem a olarak hem de ihtiyacınız olan bir yer varsa Readableve Writableşunları yapabilirsiniz:

  • O yeri refactor mu? Okuma ve yazma iki farklı şeydir. Bir yöntem her ikisini birden yaparsa, belki de tek sorumluluk ilkesine uymaz.

  • Geçiş myObjectbir şekilde iki kez Readableve bir şekilde Writable(iki argüman). Yöntem, aynı nesne olup olmamasına ne dikkat ediyor?


1
Bu bir argüman olarak kullandığınızda işe yarayabilir ve bunlar ayrı kaygılardır. Ancak bazen gerçekten aynı anda okunabilir ve yazılabilir bir nesne istersiniz (örneğin ByteArrayOutputStream'i kullanmak istediğinizle aynı şekilde)
nablex

Nasıl olur? Çıkış akışları, adından da anlaşılacağı gibi, yazabilen giriş akışlarıdır. Aynı C # - orada bir StreamWritervs StreamReader(ve diğerleri)
Konrad Morawski

Baytlara (toByteArray ()) ulaşmak için bir ByteArrayOutputStream öğesine yazarsınız. Bu yazma + okumaya eşdeğerdir. Arayüzlerin arkasındaki gerçek işlevsellik aynıdır ancak daha genel bir şekildedir. Bazen sadece okuma veya yazma, bazen ikisini de isteyeceksiniz. Başka bir örnek ByteBuffer.
nablex

2
İkinci noktada biraz geri teptim, ama bir an düşündükten sonra bunun gerçekten kötü bir fikir gibi görünmediğini düşünüyorum. Sadece okuma ve yazmayı ayırmakla kalmaz, daha esnek bir işlev yapar ve değiştirdiği giriş durumu miktarını azaltırsınız.
Phoshi

2
@Phoshi Sorun, endişelerin her zaman ayrı olmamasıdır. Bazen hem okuyabilen hem de yazabilen bir nesne istersiniz ve aynı nesne olduğunu garanti etmek istersiniz (örneğin ByteArrayOutputStream, ByteBuffer, ...)
nablex

4

Şu anda cevapların hiçbiri, okunabilir veya yazılabilir değil, her ikisi de gerekmediği durumla ilgili değildir . A'ya yazarken, bu verileri A'dan geri okuyabileceğinizi, A'ya yazıp B'den okuyamayacağınızı ve sadece aynı nesne olduklarını umabileceğinizi garanti etmeniz gerekir. Usecases bol, örneğin bir ByteBuffer kullanmak her yerde.

Neyse, üzerinde çalıştığım modülü neredeyse bitirdim ve şu anda sarmalayıcı arayüzünü seçtim:

interface Container extends Readable, Writable {}

Şimdi en azından şunları yapabilirsiniz:

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

Kendi kapsayıcı uygulamalarım (şu anda 3) ayrı kapsayıcıların aksine kapsayıcıyı uyguluyor ancak birisi bunu bir uygulamada unutursa, IOUtils bir yardımcı yöntem sağlar:

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

Bunun en uygun çözüm olmadığını biliyorum, ancak şu anda hala en iyi çıkış yolu çünkü Container hala oldukça büyük bir kullanım alanı.


1
Bence bu kesinlikle iyi. Diğer cevaplarda önerilen diğer yaklaşımlardan daha iyidir.
Tom Anderson

0

Genel bir MyObject örneği verildiğinde, bunun her zaman okumaları mı yoksa yazmaları mı desteklediğini bilmeniz gerekir. Yani şöyle bir kodunuz olur:

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

Basit bir durumda, bunu geliştirebileceğinizi sanmıyorum. Ancak Okunabilir'i okuduktan sonra başka bir dosyaya yazmakreadThisReadable isterse , garipleşir.

Bu yüzden muhtemelen bununla giderdim:

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

Bunu bir parametre olarak almak readThisReadable, şimdi readThisWholeShabam, sadece MyObject'i değil, TheWholeShabam'ı uygulayan herhangi bir sınıfı işleyebilir. Ve buna yazılabilir ise bunu yazmak ve Değilse yazamam. (Gerçek "Çok Biçimliliğimiz" var.)

Böylece ilk kod kümesi şöyle olur:

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

Ve readThisWholeShebam () okunabilirlik kontrolünü yaparak bir satırı buraya kaydedebilirsiniz.

Bu, yalnızca Okunabilir olanın sadece isWriteable () ( false döndüren ) ve write () (hiçbir şey yapmadan) uygulaması gerektiği anlamına gelir , ancak şimdi daha önce gidemediği her türlü yere ve TheWholeShabam'ı işleyen tüm kodlara gidebilir nesneler bizim tarafımızdan daha fazla çaba sarf etmeden onunla ilgilenecektir.

Başka bir şey: okumayan bir sınıfta read () çağrısını ve bir şeyi çöpe atmadan yazmayan bir sınıfta write () çağrısı yapabiliyorsanız, isReadable () ve isWriteable öğelerini atlayabilirsiniz. () yöntemleri. Eğer işe yararsa, bu en şık yol olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.