Принцип разделения интерфейса - Как решить, что отделить?
Я считаю, что вопрос не требует пояснений. Я бы предпочел уделить больше внимания примеру для поддержки вопроса.
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface ICollection : IEnumerable
{
void CopyTo(Array array, int index);
int Count { get; }
Object SyncRoot { get; }
bool IsSynchronized { get; }
}
public interface IList : ICollection
{
Object this[int index] { get; set; }
int Add(Object value);
bool Contains(Object value);
void Clear();
bool IsReadOnly { get; }
bool IsFixedSize { get; }
int IndexOf(Object value);
void Insert(int index, Object value);
void Remove(Object value);
void RemoveAt(int index);
}
Ясно, что IEnumerable
Отдельно, чтобы разрешить только зацикливание на коллекции. Но я не понимаю, почему они держали ICollection
а также IList
отделить? Даже если бы они были одним, IList не стал бы толстым, потому что такое поведение всегда требовалось бы любой коллекции?
Я наткнулся на этот пост, который говорит, что ваш API вернется IEnumerable
если клиенту нужно только зациклить элементы, ICollection
если требуется только доступ только для чтения и так далее.
Как MSFT применил ISP так идеально, что он никогда не вызывал проблем за все эти годы?
Является ли применение ISP продолжительным процессом, основанным на потребностях клиентского кода или разовым приложением? Если провайдер применяется только один раз, то мой вопрос в самом названии поста.
1 ответ
Я не понимаю, почему они держали
ICollection
а такжеIList
отделить?
IList
это коллекция, в которой получение элемента по индексу имеет логический смысл. Разделение его на подчиненный интерфейс выполняется с учетом того факта, что существуют другие коллекции, для которых индексация не определена. ISet
является примером такой коллекции.
сращивание ICollection
а также IList
будет иметь негативное влияние на способность определять ISet
в чистом виде из-за необходимости реализовать эти четыре метода:
this[int index]
int IndexOf(Object value)
void Insert(int index, Object value)
void RemoveAt(int index)
С другой стороны, можно обоснованно утверждать, что методы
int Add(Object value);
bool Contains(Object value);
void Clear();
bool IsReadOnly { get; }
bool IsFixedSize { get; }
void Remove(Object value);
может принадлежать ICollection
интерфейс.
Принцип разделения интерфейса - Как решить, что отделить?
Задача принятия решения о том, что нужно разделить, намного проще вернуться назад, когда классы на месте. Посмотрите, требуется ли какой-либо класс, чтобы выдать не реализованное исключение. Эти методы являются основными кандидатами для перемещения в подчиненный интерфейс, который необходимо ввести.
Например, рассмотрите возможность начать дизайн с IList
методы переехали в ICollection
, и нет IList
суб-интерфейс. Вы можете успешно построить List
а также LinkedList
классы с этими двумя интерфейсами.
Теперь рассмотрим добавление HashSet
к смеси. Этот класс должен реализовывать четыре метода, которые неуместны в наборе множеств, что предполагает, что вам следует рассмотреть возможность добавления одного или двух подчиненных интерфейсов - один для списков, а другой для наборов.
Гораздо сложнее определиться с сегрегацией до того, как у вас появятся какие-либо классы, потому что ваш интерфейс стремится удовлетворить потребности определенного класса, который должен его использовать. Нередко начинать с плоской структуры интерфейса и расширять ее путем рефакторинга в последующих выпусках.