Я собирался задать тот же вопрос, но решил быть по-настоящему осторожным и заняться подробным поиском - чтобы избежать дублирования.Затем я нашел это - я отредактировал заголовок в надежде, что он станет более популярным, потому что он должен.
Во-первых, мы должны знать, что такой пустой интерфейс также известен какинтерфейс маркера - этот другой, оставшийся без ответа, SO обсуждает проблемы интерфейсов маркера , и ответ Скотта Вишневси (в настоящее время верхний) интересен при рассмотрении остальной части того, что я собираюсь сказать.
Висследуя пустые интерфейсы, вы, как правило, наткнетесь на тему MSDN, касающуюся предупреждения анализа кода CA1040 , в которой, в частности, предлагается избегать пустых интерфейсов, которые не реализуют по крайней мере двух других.
По этому признаку - и, конечно, это не обязательно означает, что руководящие принципы Microsoft по кодированию де-факто - в вашем примере это именно то, что вы делаете (и то, что я хочу сделать также в моем текущем проекте), и поэтому это будетпройти собственный лакмусовой тест MS.В результате я с радостью скажу: «Да, такой интерфейс вполне разумен».
Для обоснования, и я думаю, что особенно хороший пример причины, по которой установлен переключатель «по крайней мере два интерфейса»Это правило похоже на ситуацию, в которой я нахожусь:
public interface IReadsAResource
{
public byte[] Read(string id);
}
public interface IWritesAResource
{
//returns the id
public string Write(byte[] resource);
}
Учитывая эти два интерфейса, теперь я могу написать компонент, который прямо заявляет, что ему нужно только читать или писать:
public class NeedsRead
{
private readonly IReadsAResource Reader;
public NeedsRead(IReadsAResource reader){ Reader = reader; }
}
public class NeedsWrite
{
private readonly IWritesAResource Writer;
public NeedsWrite(IWritesAResource writer){ Writer = writer; }
}
Теперь я обычно выбираю реализацию этого интерфейса для одного класса ResourceReaderWriter
, учитывая, что для хранилища данных для ресурсов потребуются зависимости, которые являются общими для обеих реализаций метода, передавая его, когда необходим любой интерфейс:
var needsRead = new NeedsRead(new ResourceReaderWriter(/* dependencies */));
var needsWrite = new NeedsWriter(new ResourceReaderWriter(/* dependencies */));
Но что тогда, если у меня есть класс, который должен одновременно читать и записи ресурсов?Сейчас, когда экосистема интерфейса уже готова, мне нужны два параметра конструктора:
public class NeedsReadAndWrite{
public NeedsReadAndWrite(IReadsAResource reader, IWritesAResource writer){
/* reader/writer local variables elided */
}
}
Вы можете сказать, что это не такая уж сложная задача, но она вызывает две проблемы:
- Выводчто у нас есть два отдельных экземпляра, которые отвечают за чтение и запись ресурсов;поэтому наш класс не может быть уверен, что оба соответствуют друг другу.Точно так же нельзя быть уверенным, что между двумя состояниями правильно управляется.
- Как вызывающая сторона, я, конечно, могу обойти это, сначала создав один экземпляр и передав его обоим параметрам.Но это лишает меня возможности создавать объект в одну строку, и кажется странным .
Любой, кто застрял со мной так долго, увидит, куда я идуnow:)
Элегантное решение состоит в том, чтобы ввести интерфейс для типа, который поддерживает чтение и запись:
//denotes a component that can both read and write
public interface IReadsAndWritesAResource : IReadsAResource, IWritesAResource
{
}
С этим интерфейсом наш проблемный тип можетпереписать как
public class NeedsReadAndWrite{
public NeedsReadAndWrite(IReadsAndWritesAResource readerWriter){
/* local variable assignment elided */
}
}
И обе эти проблемы теперь обойдены стороной.
В равной степени становится просто создать базовый тип прокси, который реализует этот новый интерфейс, используя примерычитатель и писатель и перенаправляют вызовы чтения / записи к ним.
Я думаю, что этот пример демонстрирует, почему вы, или, возможно, даже должны , использовать интерфейсы маркеров, которые объединяют дваили больше других.