Вот недавнее обсуждение списка рассылки Гуавы - мой ответ затрагивает этот, довольно фундаментальный вопрос.
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, вы действительно можете почувствовать боль, которую вызывает эта проблема.Да, некоторые хорошие люди пытаются скрыть это за хорошими абстракциями, но основная проблема тем не менее болезненна.