Załóżmy, że masz dwa interfejsy:
interface Readable {
public void read();
}
interface Writable {
public void write();
}
W niektórych przypadkach obiekty implementujące mogą obsługiwać tylko jeden z nich, ale w wielu przypadkach implementacje będą obsługiwały oba interfejsy. Ludzie korzystający z interfejsów będą musieli zrobić coś takiego:
// 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();
Żadna z tych opcji nie jest szczególnie wygodna, tym bardziej, gdy rozważasz, że chcesz to jako parametr metody.
Jednym rozwiązaniem byłoby zadeklarowanie interfejsu owijania:
interface TheWholeShabam extends Readable, Writable {}
Ma to jednak jeden konkretny problem: wszystkie implementacje obsługujące zarówno Readable, jak i Writable muszą implementować TheWholeShabam, jeśli chcą być kompatybilne z osobami korzystającymi z interfejsu. Mimo że nie oferuje nic oprócz gwarantowanej obecności obu interfejsów.
Czy istnieje czyste rozwiązanie tego problemu, czy powinienem wybrać interfejs opakowania?
AKTUALIZACJA
W rzeczywistości często konieczne jest posiadanie obiektu, który jest zarówno czytelny, jak i zapisywalny, więc po prostu rozdzielenie obaw w argumentach nie zawsze jest czystym rozwiązaniem.
AKTUALIZACJA 2
(wyodrębnione jako odpowiedź, więc łatwiej jest komentować)
AKTUALIZACJA 3
Uwaga: podstawowym przypadkiem użycia tego nie są strumienie (chociaż one również muszą być obsługiwane). Strumienie wprowadzają bardzo konkretne rozróżnienie między nakładem a wynikiem, a obowiązki są wyraźnie rozdzielone. Pomyśl raczej o bytebuferze, w którym potrzebujesz jednego obiektu, do którego możesz pisać i czytać, jednego obiektu, który ma bardzo specyficzny stan związany z nim. Te obiekty istnieją, ponieważ są bardzo przydatne w niektórych rzeczach, takich jak asynchroniczne operacje we / wy, kodowanie ...
AKTUALIZACJA 4
Jedną z pierwszych rzeczy, które wypróbowałem, było to samo, co podana poniżej sugestia (sprawdź przyjętą odpowiedź), ale okazała się zbyt delikatna.
Załóżmy, że masz klasę, która musi zwrócić typ:
public <RW extends Readable & Writable> RW getItAll();
Jeśli wywołasz tę metodę, ogólna RW jest określana przez zmienną odbierającą obiekt, więc potrzebujesz sposobu na opisanie tej zmiennej.
MyObject myObject = someInstance.getItAll();
To zadziałałoby, ale po raz kolejny wiąże je z implementacją i może generować wyjątki klasycast w czasie wykonywania (w zależności od tego, co jest zwracane).
Dodatkowo, jeśli chcesz mieć zmienną klasową typu RW, musisz zdefiniować ogólną na poziomie klasy.
źródło
Odpowiedzi:
Tak, możesz zadeklarować parametr metody jako nieokreślony typ, który rozszerza zarówno
Readable
iWritable
:Deklaracja metody wygląda okropnie, ale korzystanie z niej jest łatwiejsze niż znajomość zunifikowanego interfejsu.
źródło
process(Readable readThing, Writable writeThing)
i jeśli musisz je przywołać za pomocąprocess(foo, foo)
.<RW extends Readable&Writable>
?process
robi wiele różnych rzeczy i narusza zasadę pojedynczej odpowiedzialności.Jeśli jest miejsce, w którym potrzebujesz
myObject
zarówno a, jakReadable
iWritable
możesz:Refaktoryzować to miejsce? Czytanie i pisanie to dwie różne rzeczy. Jeśli metoda spełnia oba te warunki, być może nie przestrzega zasady pojedynczej odpowiedzialności.
Przekaż
myObject
dwukrotnie, jako aReadable
i jakoWritable
(dwa argumenty). Co obchodzi metoda, czy jest to ten sam obiekt, czy nie?źródło
StreamWriter
kontraStreamReader
(i wiele innych)Żadna z odpowiedzi nie odnosi się obecnie do sytuacji, gdy nie potrzebujesz czytelnego ani zapisywalnego, ale jedno i drugie . Potrzebujesz gwarancji, że pisząc do A, możesz odczytać te dane z powrotem z A, nie pisać do A i czytać z B i mieć tylko nadzieję, że w rzeczywistości są one tym samym obiektem. Przypadki użycia są obfite, na przykład wszędzie, gdzie można użyć ByteBuffer.
W każdym razie, prawie skończyłem moduł, nad którym pracuję i obecnie zdecydowałem się na interfejs opakowania:
Teraz możesz przynajmniej:
Moje własne implementacje kontenera (obecnie 3) implementują kontener w przeciwieństwie do oddzielnych interfejsów, ale jeśli ktoś zapomni o tym w implementacji, IOUtils zapewnia metodę użyteczności:
Wiem, że to nie jest optymalne rozwiązanie, ale w tej chwili jest to najlepsze wyjście, ponieważ Container wciąż jest dość dużą obudową.
źródło
Biorąc pod uwagę ogólną instancję MyObject, zawsze będziesz musiał wiedzieć, czy obsługuje ona odczyty czy zapisy. Więc miałbyś kod taki jak:
W prostym przypadku nie sądzę, że można to poprawić. Ale jeśli po przeczytaniu
readThisReadable
chce zapisać plik Readable do innego pliku, robi się niezręcznie.Więc prawdopodobnie wybrałbym to:
Biorąc to za parametr,
readThisReadable
terazreadThisWholeShabam
może obsłużyć każdą klasę, która implementuje TheWholeShabam, nie tylko MyObject. I może napisać, jeśli jest do zapisu, a nie napisać, jeśli nie. (Mamy prawdziwy „polimorfizm”.)Tak więc pierwszy zestaw kodu staje się:
Możesz tutaj zapisać wiersz, zlecając readThisWholeShebam () sprawdzenie czytelności.
Oznacza to, że nasz poprzedni tylko do odczytu musi zaimplementować isWriteable () (zwracając wartość false ) i write () (nie robiąc nic), ale teraz może przejść do wszystkich miejsc, w których wcześniej nie mógł przejść i całego kodu, który obsługuje TheWholeShabam obiekty sobie z tym poradzą bez żadnego dalszego wysiłku z naszej strony.
Jeszcze jedno: jeśli potrafisz obsłużyć wywołanie read () w klasie, która nie czyta oraz wywołanie write () w klasie, która nie pisze bez usuwania czegoś, możesz pominąć isReadable () i isWriteable () metody. Byłby to najbardziej elegancki sposób, aby sobie z tym poradzić - jeśli to działa.
źródło