Я понимаю, что программирование интерфейсов помогает при слабой связи. Однако есть ли руководство, объясняющее, когда он наиболее эффективен?
Например, у меня есть простое веб-приложение, которое собирает данные о сотрудниках, их планах обучения, расходах и вычисляет их расходы за год и т. д. Это довольно простое приложение, и я, конечно, могу использовать интерфейс, но интересно, будут ли какие-нибудь использовать. Я буду использовать его ради использования.
Всегда можно утверждать, что по мере того, как приложение становится сложнее, и я передаю объекты, будет иметь больше смысла передавать тип (интерфейс), чем реализацию. Итак, мне следует ждать, пока приложение станет сложным, или использовать его сразу же? Мне интересно, что лучшей практикой может быть фраза «этот парень закончил делать что-то».





Причина программирования интерфейса состоит в том, чтобы изолировать классы более высокого уровня от изменений в классах более низкого уровня. Если вы не ожидаете, что класс нижнего уровня вообще изменится, тогда разумно нет запрограммировать интерфейс в этом случае. Этот статья (PDF) развивает идею, вводя термин «принцип инверсии зависимостей».
Если есть большая вероятность, что приложение станет более сложным, проще настроить леса раньше, чем позже. Однако, если приложение несложное и маловероятно, что оно не станет сложным, рентабельности инвестиций может не быть. Вы всегда можете провести рефакторинг позже.
Я думаю, что это суждение, основанное на том, что вы реализуете. Один из примеров, когда я бы сказал, что всегда нужно программировать интерфейсы, - это создание уровня DAO, поскольку интерфейсы будут использовать разные источники данных и создавать фиктивные данные для модульного тестирования.
Вы должны рассматривать интерфейс как контракт. Этот контракт определяет набор правил и операций, которые должны быть выполнены теми, кто подписал этот контракт, независимо от того, как это делается.
Простой пример, который открыл мне глаза на интерфейсы:
class SimpleClass
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
List<SimpleClass> Calc(IEnumerable<SimpleClass> list)
{
foreach(SimpleClass item in list)
{
item.C = item.A * item.C:
}
return list.ToList();
}
Обратите внимание на входной параметр IEnumerable. Используя это, я могу передать любую коллекцию, которая реализует IEnumerable как параметр in. List<SimpleClass>, SimpleClass[], Collection<SimpleClass>.
Интерфейс IEnumerable гарантирует мне, что я могу использовать цикл foreach, и таким образом я сделал свою функцию немного более общей, и у меня будет меньше шансов, что мне придется изменить этот код, поскольку вероятность изменений IEnumerable меньше, чем f.ex. Список изменений.
Я знаю о преимуществах встроенных типов и всегда стараюсь их использовать. Но я не уверен в пользовательских типах - в частности, мой вопрос касался этого. Спасибо за ответ и код :)
Принцип использования интерфейсов так же актуален для вас, как и для разработчиков библиотек .net, поэтому, если вы поймете, почему они их используют, вы поймете, почему и где вы должны их использовать. Конечно, от интерфейсов фреймворк выиграет гораздо больше.
Программирование интерфейса в простом приложении, в котором вы не планируете менять реализации, может показаться излишним.
Как только вы войдете в модульное тестирование, вы будете очень счастливы, если запрограммировали его на интерфейсы, потому что это дает вам возможность более легко имитировать реальный объект с помощью тестовых двойников.
Совершенно верно. Я часто слышал утверждение: «Никогда не используйте интерфейс для чего-то с одной реализацией». Что ж, когда вы выполняете модульное тестирование, вы создаете вторую реализацию классов-ответчиков. С хорошим набором модульных тестов у вас ВСЕГДА есть более одной реализации.
Если вы когда-нибудь заинтересуетесь разработкой через тестирование, необходимость в «программировании интерфейса» станет очевидной. Если вы хотите, чтобы класс тестировался изолированно от его зависимостей, вам нужно передавать интерфейсы вместо объектов.
Это основная причина, по которой я избегал разработки, основанной на тестировании. На первый взгляд TDD - отличная идея, но я думаю, что цена слишком высока, поскольку требует чрезмерной архитектуры большинства классов приложений.
Здесь я не согласен. Мое тестовое покрытие настолько велико, а мои классы настолько хорошо разделены, что я вынужден думать о хорошем дизайне с самого начала в очень реальной форме. Он создает более модульную расширяемую архитектуру, за которой стоит достаточно тестов, чтобы оставаться надежной. Я уже редко пишу ошибки.
Я обычно так думаю - есть только две вещи, которые отделяют интерфейс от реализации:
Теперь подумайте об объектах, которые будут унаследованы от вашей «структуры». Что для них будет важнее? Получат ли они наибольшую выгоду от реализаций методов по умолчанию или будет лучше, если у них будут другие базовые классы?
В большинстве случаев довольно ясно, какой из факторов является более важным. Если вы оказались на тонкой грани между ними ... не повезло. Базовые классы Microsoft рекомендует поверх интерфейсов.
Для всего, что является частью API, будь то для внешних клиентов или других команд, которые повторно используют ваш код, используйте интерфейсы в максимально возможной степени. Это того стоит, потому что он скрывает как можно больше информации о том, как работают ваши реализации, и дает вам большую свободу для их улучшения в будущем. Предоставьте только небольшой конкретный (возможно, статический) класс, из которого можно получить экземпляры.
Что касается внутренних частей вашего дизайна, у вас есть возможность начать с конкретных ссылок на классы повсюду и вводить интерфейсы только там, где это имеет смысл для дизайна.
Но еще одна важная вещь, которую следует учитывать, - это модульное тестирование. Вы можете полностью "смоделировать" интерфейс в CLR без каких-либо технических трудностей, но издевательство над другими вещами либо невозможно, либо потребует серьезных уловок. Итак, одна дисциплина, которую следует учитывать, - это разработка, управляемая тестированием: напишите тесты для своего кода по мере продвижения, и вы обнаружите, что вам нужны определенные вещи, которые должны быть выражены интерфейсами, чтобы вы могли предоставлять их макетные версии.
Если вы хотите иметь возможность тестировать свое приложение и не использовать (и впоследствии не создавать) полный объект сотрудника, вы можете создать интерфейс IEmployee, а затем создать легкие, удобные для тестирования макетные объекты сотрудников для тестирования с помощью . Это может быть большим преимуществом, если создать сотрудника сложно или даже невозможно вручную или без базы данных.
Еще одна веская причина в том, что это помогает вам определить, от чего именно вы зависите в своем классе сотрудников. Возможно, вы думали, что используете только несколько общедоступных методов, но позже узнали, что вы использовали 10 методов и что они более тесно связаны, чем вы думали.
Наконец, если вам нужно изменить свой код для использования SuperEmployee вместо Employee, если вы все время использовали интерфейсы, все, что вам нужно сделать, это заставить SuperEmployee реализовать IEmployee, и все будет настроено.
Просмотрите книгу Шаблоны проектирования Head-First ... вы увидите веские аргументы в пользу использования интерфейсов, которые не имеют ничего общего с TDD или полиморфизмом.
Интерфейсы позволяют вам изменять поведение объекта во время выполнения ... Подумайте о местах, где вам понадобится обработчик для определенного поведения, но вы можете не знать, какое поведение требуется, до времени выполнения. В случае подсчета расходов для сотрудников ... У менеджеров может быть более высокая надбавка на «развлекательные» расходы, чем у обычного сотрудника.
Если ваш объект Employee имеет ссылку на интерфейс IExpenseCalculator, вы можете назначить ему калькулятор менеджера или калькулятор сотрудника во время выполнения. Вызов Employee.GetExpenses () даст вам рассчитанный результат для менеджера иначе, чем для обычного сотрудника. Внутренне код будет выглядеть так:
public class Employee {
private IExpenseCalculator expenses;
public ExpenseSheet GetExpenses() {
return expenses.CalcExpenses();
}
}
В этом примере предполагается, что «расходы» представлены как свойство и что IExpenseCalculator имеет метод для CalcExpenses ().
Интерфейсы также тесно связаны с концепцией объектных фабрик ... подумайте об уровне базы данных. Когда вы настроили свой уровень данных как фабрику, вы можете создавать объекты для динамического подключения к Sql Server, Oracle или MySql во время выполнения на основе параметров конфигурации. Но клиенту нужен конкретный дескриптор объекта уровня базы данных ... введите Интерфейсы.
Интерфейсы - мощный инструмент, которым часто злоупотребляют. Их правильное использование изменит ваш образ мышления, но может значительно улучшить структуру вашего приложения.
Точно. Я могу установить строительные леса, чтобы в дальнейшем избежать ненужной работы. Но будет ли это называться плохой практикой из-за чрезмерного или плохого применения хорошей практики?