Как я узнаю, когда создавать интерфейс? - PullRequest
188 голосов
/ 14 января 2009

Я нахожусь в процессе обучения, когда чувствую, что должен больше узнать об интерфейсах.

Я часто читаю о них, но мне кажется, что я не могу их понять.

Я читал примеры, такие как: базовый класс Animal, с интерфейсом IAnimal для таких вещей, как 'Walk', 'Run', 'GetLegs' и т. Д., Но я никогда не работал над чем-то и чувствовал, что "Эй, я должен используйте интерфейс здесь! "

Чего мне не хватает? Почему мне так сложно понять это? Меня просто пугает тот факт, что я, возможно, никогда не осознаю конкретной потребности в ней - в основном из-за какого-то недостающего аспекта их понимания! Это заставляет меня чувствовать, что я что-то упускаю из-за того, что являюсь разработчиком! Если у кого-то был подобный опыт, и у него был прорыв, я был бы признателен за несколько советов о том, как понять эту концепцию. Спасибо.

Ответы [ 24 ]

144 голосов
/ 14 января 2009

это решает эту конкретную проблему:

у вас есть a, b, c, d 4 разных типов. по всему коду у вас есть что-то вроде:

a.Process();
b.Process();
c.Process();
d.Process();

почему бы не сделать так, чтобы они реализовали IProcessable, а затем сделать

List<IProcessable> list;

foreach(IProcessable p in list)
    p.Process();

это будет масштабироваться намного лучше, если вы добавите, скажем, 50 типов классов, которые все делают одно и то же.


Еще одна конкретная проблема:

Вы когда-нибудь смотрели на System.Linq.Enumerable? Он определяет тонну методов расширения, которые работают с любым типом, который реализует IEnumerable. Поскольку все, что реализует IEnumerable, в основном говорит: «Я поддерживаю итерацию в неупорядоченном шаблоне типа foreach», вы можете определить сложные поведения (Count, Max, Where, Select и т. Д.) Для любого перечисляемого типа.

129 голосов
/ 14 января 2009

Мне очень нравится ответ Джимми, но я чувствую, что должен что-то добавить к нему. Ключом ко всему является «умение» в IProcess умение . Он указывает на возможность (или свойство, но означает «внутреннее качество», а не в смысле свойств C #) объекта, реализующего интерфейс. IAnimal, вероятно, не является хорошим примером для интерфейса, но IWalkable может быть хорошим интерфейсом, если ваша система имеет много возможностей для работы. У вас могут быть классы, полученные от животных, такие как собака, корова, рыба, змея. Первые два, вероятно, реализуют IWalkable, последние два не ходят, поэтому они не будут. Теперь вы спрашиваете: «Почему бы просто не иметь другого суперкласса, WalkingAnimal, от которого происходят Dog и Cow?». Ответ - когда у вас есть что-то полностью за пределами дерева наследования, которое также может ходить, например, робот. Робот будет реализовывать IWalkable, но, вероятно, не будет производным от Animal. Если вам нужен список вещей, по которым можно ходить, вы набираете его как IWalkable и можете включить в список всех гуляющих животных и роботов.

Теперь замените IWalkable чем-то более программно-подобным IPersistable, и аналогия станет намного ближе к тому, что вы увидите в реальной программе.

69 голосов
/ 14 января 2009

Используйте интерфейсы, когда реализации одной и той же функциональности будут отличаться.

Используйте абстрактные / базовые классы, когда вам нужно поделиться общей конкретной реализацией.

32 голосов
/ 14 января 2009

Думайте об интерфейсе как о контракте. Это способ сказать: «Эти классы должны следовать этим правилам».

Таким образом, в примере с IAnimal это способ сказать: «Я ДОЛЖЕН иметь возможность вызывать Run, Walk и т. Д. Для классов, которые реализуют IAnimal.»

Почему это полезно? Возможно, вы захотите создать функцию, основанную на том факте, что вы должны иметь возможность вызывать Run и Walk, например, для объекта. Вы можете иметь следующее:

public void RunThenWalk(Monkey m) {
    m.Run();
    m.Walk();
}

public void RunThenWalk(Dog d) {
    d.Run();
    d.Walk();
}

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

public void RunThenWalk(IAnimal a) {
    a.Run();
    a.Walk();
}

Программируя интерфейс, вы по существу доверяете классам для реализации намерений интерфейса. Таким образом, в нашем примере мысль звучит так: «Мне все равно , как они бегают и ходят, пока они бегут и ходят. Мой RunThenWalk будет действителен, пока они выполняют это соглашение. ну, ничего не зная об этом классе. "

Существует также хорошее обсуждение в этом связанном вопросе .

18 голосов
/ 14 января 2009

Не беспокойся так сильно. Многим разработчикам, редко понадобится написать интерфейс. Вы будете часто использовать интерфейсы, доступные в рамках .NET , но если вы не чувствуете необходимости писать их в ближайшее время, в этом нет ничего удивительного.

Пример, который я всегда привожу кому-то, - это если у вас есть класс Sailboat и класс Viper. Они наследуют класс Boat и класс Car соответственно. Теперь скажите, что вам нужно перебрать все эти объекты и вызвать их метод Drive(). В то время как вы могли бы написать код, подобный следующему:

if(myObject is Boat)
    ((Boat)myObject).Drive()
else
    if (myObject is Car)
        ((Car)myObject).Drive()

Было бы намного проще написать:

((IDrivable)myObject).Drive()
16 голосов
/ 14 января 2009

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

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

15 голосов
/ 07 апреля 2011

Мне нравится армейская аналогия.

Сержанту все равно, если вы разработчик программного обеспечения , музыкант или юрист .
С вами обращаются как с солдат .

uml

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

Способность людей вести себя как солдат называется полиморфизмом.

Интерфейсы - это программные конструкции, помогающие достичь полиморфизма.

Необходимость в абстрактных деталях для достижения простоты - ответ на Ваш вопрос.

Полиморфизм , который этимологически означает «много форм», - это способность обрабатывать объект любого подкласса базового класса, как если бы он был объектом базового класса. Следовательно, базовый класс имеет много форм: сам базовый класс и любой из его подклассов.

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

14 голосов
/ 14 января 2009

По моему опыту, движущей силы для создания интерфейсов не было, пока я не начал проводить модульное тестирование с помощью фреймворка. Стало совершенно очевидно, что использование интерфейсов значительно облегчит работу по моделированию (поскольку среда зависит от виртуальных методов). Когда я начал, я понял, насколько важно абстрагировать интерфейс от моего класса от реализации. Даже если я не создаю реальный интерфейс, я пытаюсь сделать мои методы виртуальными (предоставляя неявный интерфейс, который можно переопределить).

Есть много других причин, которые я обнаружил, чтобы усилить хорошую практику рефакторинга для интерфейсов, но модульное тестирование / насмешка были тем, что обеспечило начальный "ага момент" практического опыта.

EDIT : Чтобы уточнить, с модульным тестированием и имитацией у меня всегда есть две реализации - реальная, конкретная реализация и альтернативная реализация макета, используемая в тестировании. Когда у вас есть две реализации, значение интерфейса становится очевидным - разберитесь с ним с точки зрения интерфейса, чтобы вы могли заменить реализацию в любое время. В этом случае я заменяю его фиктивным интерфейсом. Я знаю, что могу сделать это без реального интерфейса, если мой класс построен правильно, но использование реального интерфейса усиливает это и делает его чище (понятнее читателю). Без этого стимула я не думаю, что оценил бы ценность интерфейсов, так как большинство моих классов когда-либо имели одну конкретную реализацию.

10 голосов
/ 14 января 2009

Некоторые примеры не программирования, которые могут помочь вам увидеть правильное использование интерфейсов в программировании.

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

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

Услуга, которую бизнес предоставляет клиенту , может быть описана как интерфейс: четко определенный интерфейс подчеркивает услугу и скрывает средства . Когда вы помещаете письмо в почтовый ящик, вы ожидаете, что почтовая система доставит письмо в течение определенного времени, но у вас нет никаких ожиданий относительно того, как письмо доставлено: вам не нужно знать , и Почтовая служба имеет возможность выбора способа доставки , который наилучшим образом соответствует требованиям и текущим обстоятельствам. Исключением из этого является возможность заказчиков выбирать авиапочту - это не тот интерфейс, который разработал бы современный программист, поскольку он раскрывает слишком много реализации.

Примеры от природы: я не слишком увлечен примерами eats (), makeSound (), move () и т. Д. Они действительно описывают поведение, которое является правильным, но они не описывают взаимодействия и как они включены . Очевидные примеры интерфейсов, которые обеспечивают взаимодействие в природе, связаны с размножением, например, цветок обеспечивает определенный интерфейс для пчелы, так что опыление может иметь место.

5 голосов
/ 20 марта 2009

Пример кода (комбинация Эндрю с моим дополнительным в что такое цель интерфейсов ), который также объясняет, почему интерфейс вместо абстрактного класса на языках без поддержки множественного наследования (c # и java):

interface ILogger
{
    void Log();
}
class FileLogger : ILogger
{
    public void Log() { }
}
class DataBaseLogger : ILogger
{
    public void Log() { }
}
public class MySpecialLogger : SpecialLoggerBase, ILogger
{
    public void Log() { }
}

Обратите внимание, что FileLogger и DataBaseLogger не нуждаются в интерфейсе (это может быть абстрактный базовый класс Logger). Но учтите, что вы обязаны использовать сторонний регистратор, который заставляет вас использовать базовый класс (допустим, он предоставляет защищенные методы, которые вам нужно использовать). Поскольку язык не поддерживает множественное наследование, вы не сможете использовать подход абстрактного базового класса.

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

...