Предположим, у вас есть два интерфейса:
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 {}
Но это имеет одну конкретную проблему: все реализации, которые поддерживают как Readable, так и Writable, должны реализовывать TheWholeShabam, если они хотят быть совместимыми с людьми, использующими интерфейс. Хотя он не предлагает ничего, кроме гарантированного наличия обоих интерфейсов.
Есть чистое решение этой проблемы или я должен пойти на интерфейс оболочки?
ОБНОВИТЬ
На самом деле часто бывает необходимо иметь объект, который может быть читаем и доступен для записи, поэтому простое разделение проблем в аргументах не всегда является чистым решением.
UPDATE2
(извлечено как ответ, так что проще комментировать)
Update3
Пожалуйста, имейте в виду, что основной сценарий использования этого не потоки (хотя они тоже должны поддерживаться). Потоки делают очень четкое различие между входом и выходом, и существует четкое разделение обязанностей. Скорее, подумайте о чем-то вроде байтового буфера, где вам нужен один объект, в который вы можете писать и читать, один объект, к которому прикреплено очень специфическое состояние. Эти объекты существуют, потому что они очень полезны для некоторых вещей, таких как асинхронный ввод-вывод, кодировки, ...
UPDATE4
Одна из первых вещей, которые я попробовал, была такой же, как предложение, приведенное ниже (проверьте принятый ответ), но оно оказалось слишком хрупким.
Предположим, у вас есть класс, который должен возвращать тип:
public <RW extends Readable & Writable> RW getItAll();
Если вы вызываете этот метод, общая RW определяется переменной, получающей объект, поэтому вам нужен способ описать эту переменную.
MyObject myObject = someInstance.getItAll();
Это будет работать, но еще раз связывает его с реализацией и может фактически генерировать исключения classcastexception во время выполнения (в зависимости от того, что возвращается).
Кроме того, если вам нужна переменная класса типа RW, вам нужно определить универсальный тип на уровне класса.
источник
Ответы:
Да, вы можете объявить ваш параметр метода как недостаточно определенный тип, который расширяет
Readable
иWritable
:Объявление метода выглядит ужасно, но использовать его проще, чем знать об унифицированном интерфейсе.
источник
process(Readable readThing, Writable writeThing)
и если вы должны вызывать его, используяprocess(foo, foo)
.<RW extends Readable&Writable>
?process
делает несколько разных вещей и нарушает принцип единой ответственности.Если есть место, где вам нужно
myObject
,Readable
иWritable
вы можете:Реорганизовать это место? Чтение и письмо - это две разные вещи. Если метод выполняет оба действия, возможно, он не следует принципу единой ответственности.
Передайте
myObject
дважды, как aReadable
и как aWritable
(два аргумента). Что заботит метод, является ли он одним и тем же объектом?источник
StreamWriter
противStreamReader
(и многие другие)Ни один из ответов в настоящее время не относится к ситуации, когда вам не нужны читаемые или записываемые, но оба . Вам нужны гарантии, что при записи в A вы можете читать эти данные обратно из A, а не записывать в A и читать из B, и просто надеяться, что это на самом деле один и тот же объект. Вариантов использования много, например, везде, где вы бы использовали ByteBuffer.
Во всяком случае, я почти закончил модуль, над которым я работаю, и в настоящее время я выбрал интерфейс оболочки:
Теперь вы можете по крайней мере сделать:
Мои собственные реализации контейнера (в настоящее время 3) реализуют Контейнер в отличие от отдельных интерфейсов, но если кто-то забудет об этом в реализации, IOUtils предоставляет служебный метод:
Я знаю, что это не оптимальное решение, но это все еще лучший выход на данный момент, потому что Контейнер все еще довольно большой сценарий использования.
источник
Учитывая общий экземпляр MyObject, вам всегда нужно будет знать, поддерживает ли он чтение или запись. Таким образом, у вас был бы такой код:
В простом случае, я не думаю, что вы можете улучшить это. Но если после записи его
readThisReadable
захочется записать в другой файл для чтения, это будет неловко.Так что я бы, наверное, пошел с этим:
Принимая это как параметр,
readThisReadable
теперьreadThisWholeShabam
можно обрабатывать любой класс, который реализует TheWholeShabam, а не только MyObject. И это может написать это, если это доступно для записи, и не написать это, если это не так. (У нас есть настоящий «Полиморфизм».)Таким образом, первый набор кода становится:
И вы можете сохранить строку здесь, сделав readThisWholeShebam (), чтобы сделать проверку читабельности.
Это означает, что наш прежний Readable-only должен реализовывать isWriteable () (возвращающий false ) и write () (ничего не делая), но теперь он может идти во все виды мест, которые он не мог найти раньше, и весь код, который обрабатывает TheWholeShabam объекты будут иметь дело с этим без каких-либо дополнительных усилий с нашей стороны.
Еще одна вещь: если вы можете обработать вызов read () в классе, который не читает, и вызов write () в классе, который не пишет, не удаляя что-то, вы можете пропустить isReadable () и isWriteable () методы. Это был бы самый элегантный способ справиться с этим - если он работает.
источник