Есть ли обходной путь для интерфейсов композиции и маркера? - PullRequest
6 голосов
/ 08 августа 2010

Я вижу себя регулярно сталкивающимся со следующей проблемой.У меня есть какой-то интерфейс Marker Interface (для простоты давайте использовать java.io.Serializable) и несколько оболочек (Adapter, Decorator, Proxy, ...).Но когда вы помещаете экземпляр Serializable в другой экземпляр (который не сериализуем), вы теряете функциональность.Та же проблема возникает с java.util.RandomAccess, который может быть реализован реализациями List.Есть хороший способ ООП справиться с этим?

Ответы [ 4 ]

7 голосов
/ 11 августа 2010

Вот недавнее обсуждение списка рассылки Гуавы - мой ответ затрагивает этот, довольно фундаментальный вопрос.

http://groups.google.com/group/guava-discuss/browse_thread/thread/2d422600e7f87367/1e6c6a7b41c87aac

Суть этого такова: Не делайте• используйте интерфейсы маркеров, когда ожидаете, что ваши объекты будут обернуты .(Ну, это довольно общее - как сделать вы знаете, что ваш объект не будет обернут клиентом?)

Например, ArrayList.Это реализует RandomAccess, очевидно.Затем вы решаете создать оболочку для List объектов.К сожалению!Теперь, когда вы переносите, вы должны проверить обернутый объект, и если это RandomAccess, созданная вами оболочка должна также реализовать RandomAccess!

Это работает "отлично" ... если вытолько один интерфейс маркера!Но что, если обернутый объект может быть Сериализуемым?Что, если это, скажем, «неизменный» (при условии, что у вас есть тип для обозначения этого)?Или синхронно?(С тем же предположением).

Как я также отмечаю в своем ответе на список рассылки, этот недостаток дизайна также проявляется в старой доброй упаковке java.io.Скажем, у вас есть метод, принимающий InputStream.Будете ли вы читать прямо с него?Что, если это дорогостоящий поток, и никто не позаботился об этом в BufferedInputStream для вас?О, это просто!Вы просто проверяете stream instanceof BufferedInputStream, а если нет, вы сами оборачиваете его!Но нет.Поток может иметь буферизацию где-то вниз по цепочке, но вы можете получить его оболочку, , которая не является экземпляром BufferedInputStream .Таким образом, информация о том, что «этот поток буферизируется», теряется (и, возможно, вам придется пессимистически тратить память, чтобы снова ее буферизовать).

Если вы хотите сделать правильно , просто смоделируйте возможности как объекты.Рассмотрим:

interface YourType {
  Set<Capability> myCapabilities();
}

enum Capability {
  SERIALIAZABLE,
  SYNCHRONOUS,
  IMMUTABLE,
  BUFFERED //whatever - hey, this is just an example, 
           //don't throw everything in of course!
}

Редактировать: Следует отметить, что я использую перечисление просто для удобства.Интерфейс может Capability и открытый набор объектов, реализующих его (возможно, несколько перечислений).

Таким образом, когда вы оборачиваете объект в них, вы получаете набор возможностей, и вы можете легкорешить , какие возможности сохранить, какие удалить, а какие добавить .

Этот имеет , очевидно, имеет свои недостатки, поэтому его следует использовать только в случаяхгде вы действительно чувствуете боль скрытия возможностей оболочек, выраженных как маркерные интерфейсы.Например, предположим, что вы пишете фрагмент кода, который принимает List, но он должен быть RandomAccess AND Serializable.При обычном подходе это легко выразить:

<T extends List<Integer> & RandomAccess & Serializable> void method(T list) { ... }

Но в подходе, который я описываю, все, что вы можете сделать, это:

void method(YourType object) {
  Preconditions.checkArgument(object.getCapabilities().contains(SERIALIZABLE));
  Preconditions.checkArgument(object.getCapabilities().contains(RANDOM_ACCESS));
  ...
}

Я действительно хотел бы, чтобы были более удовлетворительныеподход, чем любой, но с точки зрения перспективы, это кажется невозможным (по крайней мере, не вызывая взрыв комбинаторного типа).

Редактировать: Еще один недостаток заключается в том, что без явного введите для каждой возможности, у нас нет естественного места для размещения методов, которые выражают то, что предлагает эта возможность.Это не слишком важно в этом обсуждении, поскольку мы говорим об интерфейсах marker , то есть о возможностях, которые не выражаются с помощью дополнительных методов, но я упомяну это для полноты.

PS: кстати,если вы просматриваете код коллекций Guava, вы действительно можете почувствовать боль, которую вызывает эта проблема.Да, некоторые хорошие люди пытаются скрыть это за хорошими абстракциями, но основная проблема тем не менее болезненна.

5 голосов
/ 11 августа 2010

Если все интересующие вас интерфейсы являются маркерными интерфейсами, все ваши классы-обертки могут реализовать интерфейс

public interface Wrapper {
    boolean isWrapperFor(Class<?> iface);
}

чья реализация будет выглядеть так:

public boolean isWrapperFor(Class<?> cls) {
    if (wrappedObj instanceof Wrapper) {
        return ((Wrapper)wrappedObj).isWrapperFor(cls);
    }
    return cls.isInstance(wrappedObj);
}

Вот как это делается в java.sql.Wrapper. Если интерфейс не просто маркер, но на самом деле имеет некоторые функции, вы можете добавить метод для развертывания:

<T> T unwrap(java.lang.Class<T> cls)
1 голос
/ 11 августа 2010

Есть несколько альтернатив, хотя ни одна из них не очень хорошая

  1. Заставить обертку реализовывать интерфейс, если во время компиляции известно, если обернутый объект также реализует интерфейс. Фабричный метод может использоваться для создания оболочки, если он не известен до времени выполнения, если обернутый объект будет реализовывать интерфейс. Это означает, что у вас есть отдельные классы-оболочки для возможных комбинаций реализованных интерфейсов. (С одним интерфейсом вам понадобятся 2 оболочки, одна с и одна без. Для 2 интерфейсов, 4 оболочки и т. Д.)

  2. Предоставьте обернутые объекты из оболочки, чтобы клиенты могли обходить цепочку и проверять каждый объект в цепочке на наличие интерфейса, используя instanceof. Это нарушает инкапсуляцию.

  3. Иметь специальный метод для извлечения интерфейса, реализуемый как оболочкой, так и обернутым объектом. Например. asSomeInterface(). Оболочка делегирует обернутый объект или создает прокси вокруг обернутого объекта для сохранения инкапсуляции.

  4. Создайте один класс оболочки для каждого интерфейса - оболочка реализована как обычно - она ​​реализует интерфейс и делегирует другую реализацию этого интерфейса. Обернутый объект может реализовывать несколько интерфейсов, поэтому несколько экземпляров оболочки объединяются в один логический экземпляр с использованием динамического прокси-сервера для делегирования методов интерфейса, реализованных прокси, соответствующему экземпляру оболочки. Необходимо, чтобы набор интерфейсов, реализованных прокси-сервером, не имел общих сигнатур методов.

Microsoft запекла агрегирование ( Wikipedia ) в свою объектную модель компонентов (COM). Похоже, что он не используется большинством, но приводит к значительной сложности для разработчиков COM-объектов, поскольку существуют правила, которых должен придерживаться каждый объект. Обернутые объекты инкапсулируются, если обернутые объекты знают о том, что они являются обертками, и должны поддерживать указатель на обертку, которая используется при реализации QueryInterface (свободно instanceof) для открытых открытых интерфейсов - обернутый объект возвращает интерфейс, реализованный на обертка, а не его собственная реализация.

Я не видел чистого, легкого для понимания / реализации и правильно инкапсулированного решения для этого. Агрегация COM работает и обеспечивает полную инкапсуляцию, но вы платите за каждый реализуемый вами объект, даже если он никогда не используется в агрегате.

1 голос
/ 08 августа 2010

Для таких как RandomAccess мало что можно сделать.Конечно, вы можете выполнить проверку instanceof и создать экземпляр соответствующего класса.Число классов растет по экспоненте с маркерами (хотя вы можете использовать java.lang.reflect.Proxy), и ваш метод создания должен знать обо всех маркерах когда-либо.

Serializable не так уж и плох.Если класс косвенности реализует Serializable, тогда целое будет сериализуемым, если целевой класс равен Serializable, а не если это не так.

...