Для мене, коли починається, то до них стало зрозуміло лише тоді, коли ви перестаєте дивитися на них як на речі, щоб полегшити / швидше писати ваш код - це не їх мета. Вони мають ряд застосувань:
(Це втратить аналогію піци, оскільки не дуже легко візуалізувати використання цього)
Скажіть, ви робите просту гру на екрані, і в ній будуть істоти, з якими ви взаємодієте.
Відповідь: Вони можуть полегшити підтримку вашого коду в майбутньому, ввівши нескінченну муфту між вашим переднім кінцем та реалізацією заднього кінця.
Ви можете написати це для початку, оскільки там будуть лише тролі:
// 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);
}
Через два тижні, маркетинг вирішує, що вам також потрібні орки, оскільки вони читають про них на Twitter, тож вам доведеться зробити щось на кшталт:
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, для якої потрібно викликати метод уже в А. Ви не можете це зробити за допомогою конкретних реалізацій, оскільки отримаєте циркулярну посилання.
Ви можете мати інтерфейс, оголошений в B, який реалізує клас у А. Ваш метод в B може бути переданий екземпляр класу, який реалізує інтерфейс без проблем, навіть якщо конкретний об'єкт має тип А.