Для меня смысл этого стал ясен только тогда, когда вы перестали смотреть на них как на вещи, которые делают ваш код легче / быстрее писать - это не их цель.У них есть несколько применений:
(Это потеряет аналогию с пиццей, так как ее использование будет не очень легко представить)
Скажем, вы делаете простую игру на экранеи у него будут существа, с которыми вы взаимодействуете.
A: Они могут упростить поддержку вашего кода в будущем, введя слабую связь между вашим внешним интерфейсом и вашей серверной реализацией.
Вы могли бы написать это для начала, так как будут только тролли:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
Фронт:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
Две недели спустя,маркетологи решили, что вам также нужны орки, так как они читают о них в твиттере, поэтому вам нужно будет сделать что-то вроде:
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
Внешний интерфейс:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
И вы можете увидеть, какэто начинает становиться грязным.Здесь вы можете использовать интерфейс, чтобы ваш интерфейс был написан один раз и (вот важный бит) протестирован, а затем вы можете подключить дополнительные элементы интерфейса, как требуется:
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
Тогда интерфейс будет:
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
Внешний интерфейс теперь заботится только об интерфейсе ICreature - его не беспокоит внутренняя реализация тролля или орка, а только тот факт, что они реализуют ICreature.
Важно отметить, что, глядя на это с этой точки зрения, вы могли бы также легко использовать класс абстрактных существ, и с этой точки зрения он имеет такой же эффект.
И вы можете извлечь творение на фабрику:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
И тогда наш внешний интерфейс станет:
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
Теперь внешний интерфейс даже не будетдолжна иметь ссылку на библиотеку, в которой реализованы Troll и Orc (при условии, что фабрика находится в отдельной библиотеке) - ей вообще ничего не нужно знать о них.
B: Скажем, у вас есть функциональность, которая тольконекоторые существа будут иметь в своей однородной структуре данных , например,
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
Тогда передний конец может быть:
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C: Использование для внедрения зависимости
С большинством структур внедрения зависимостей легче работать, когда существует очень слабая связь между кодом переднего плана и реализацией внутреннего интерфейса.Если мы возьмем наш фабричный пример выше и сделаем так, чтобы наша фабрика реализовала интерфейс:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
Наш интерфейс мог бы затем внедрить это (например, контроллер MVC API) через конструктор (обычно):
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
С нашей структурой DI (например, Ninject или Autofac) мы можем настроить их так, чтобы во время выполнения экземпляр CreatureFactory создавался всякий раз, когда в конструкторе требуется ICreatureFactory - это делает наш код красивым и простым.
Это также означает, что, когда мы пишем модульный тест для нашего контроллера, мы можем предоставить фиктивную ICreatureFactory (например, если конкретная реализация требует доступа к БД, мы не хотим, чтобы наши модульные тесты зависели от этого) и легкопротестируйте код в нашем контроллере.
D: есть и другие варианты использования, например, у вас есть два проекта A и B, которые по «устаревшим» причинам не имеют четкой структуры, и A имеет ссылку на B.
Затем вы найдете функциональность в B, которая должна вызывать метод уже в A. Вы не можете сделать это с помощью конкретных реализаций, когда получите циклическую ссылку.
Вы можете получитьинтерфейс объявил в B, что класс в A затем реализует.Вашему методу в B можно передать экземпляр класса, который без проблем реализует интерфейс, даже если конкретный объект имеет тип в A.