Эту фразу можно встретить, читая о шаблонах проектирования.
Но я этого не понимаю, может ли кто-нибудь мне это объяснить?
Эту фразу можно встретить, читая о шаблонах проектирования.
Но я этого не понимаю, может ли кто-нибудь мне это объяснить?
Ответы:
Интерфейсы - это просто контракты или подписи, и они ничего не знают о реализациях.
Кодирование по отношению к интерфейсу означает, что клиентский код всегда содержит объект интерфейса, который предоставляется фабрикой. Любой экземпляр, возвращаемый фабрикой, будет иметь тип Интерфейс, который должен быть реализован любым классом-кандидатом фабрики. Таким образом, клиентская программа не заботится о реализации, а подпись интерфейса определяет, какие все операции могут быть выполнены. Это можно использовать для изменения поведения программы во время выполнения. Это также помогает вам писать гораздо лучшие программы с точки зрения обслуживания.
Вот вам простой пример.
public enum Language
{
English, German, Spanish
}
public class SpeakerFactory
{
public static ISpeaker CreateSpeaker(Language language)
{
switch (language)
{
case Language.English:
return new EnglishSpeaker();
case Language.German:
return new GermanSpeaker();
case Language.Spanish:
return new SpanishSpeaker();
default:
throw new ApplicationException("No speaker can speak such language");
}
}
}
[STAThread]
static void Main()
{
//This is your client code.
ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
speaker.Speak();
Console.ReadLine();
}
public interface ISpeaker
{
void Speak();
}
public class EnglishSpeaker : ISpeaker
{
public EnglishSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak English.");
}
#endregion
}
public class GermanSpeaker : ISpeaker
{
public GermanSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak German.");
}
#endregion
}
public class SpanishSpeaker : ISpeaker
{
public SpanishSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak Spanish.");
}
#endregion
}
Это всего лишь базовый пример, и фактическое объяснение принципа выходит за рамки этого ответа.
Я обновил приведенный выше пример и добавил абстрактный Speaker
базовый класс. В этом обновлении я добавил функцию «SayHello» для всех выступающих. Все выступающие говорят «Hello World». Так что это общая черта с аналогичной функцией. Обратитесь к диаграмме классов, и вы обнаружите, что интерфейс Speaker
реализации абстрактного класса ISpeaker
помечается Speak()
как абстрактный, что означает, что каждая реализация Speaker отвечает за реализацию Speak()
метода, поскольку он варьируется от Speaker
до Speaker
. Но все выступающие единодушно говорят «Привет». Итак, в абстрактном классе Speaker мы определяем метод, который говорит «Hello World», и каждая Speaker
реализация будет наследовать этот SayHello()
метод.
Рассмотрим случай, когда SpanishSpeaker
нельзя сказать «Привет», поэтому в этом случае вы можете переопределить SayHello()
метод для говорящего по-испански и вызвать соответствующее исключение.
Обратите внимание, что мы не вносили никаких изменений в интерфейс ISpeaker. И клиентский код, и SpeakerFactory также остаются неизменными. И этого мы достигаем с помощью программирования через интерфейс .
И мы могли бы добиться такого поведения, просто добавив базовый абстрактный класс Speaker и немного изменив его в реализации Each, оставив исходную программу без изменений. Это желательная функция любого приложения, которая делает ваше приложение легко обслуживаемым.
public enum Language
{
English, German, Spanish
}
public class SpeakerFactory
{
public static ISpeaker CreateSpeaker(Language language)
{
switch (language)
{
case Language.English:
return new EnglishSpeaker();
case Language.German:
return new GermanSpeaker();
case Language.Spanish:
return new SpanishSpeaker();
default:
throw new ApplicationException("No speaker can speak such language");
}
}
}
class Program
{
[STAThread]
static void Main()
{
//This is your client code.
ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
speaker.Speak();
Console.ReadLine();
}
}
public interface ISpeaker
{
void Speak();
}
public abstract class Speaker : ISpeaker
{
#region ISpeaker Members
public abstract void Speak();
public virtual void SayHello()
{
Console.WriteLine("Hello world.");
}
#endregion
}
public class EnglishSpeaker : Speaker
{
public EnglishSpeaker() { }
#region ISpeaker Members
public override void Speak()
{
this.SayHello();
Console.WriteLine("I speak English.");
}
#endregion
}
public class GermanSpeaker : Speaker
{
public GermanSpeaker() { }
#region ISpeaker Members
public override void Speak()
{
Console.WriteLine("I speak German.");
this.SayHello();
}
#endregion
}
public class SpanishSpeaker : Speaker
{
public SpanishSpeaker() { }
#region ISpeaker Members
public override void Speak()
{
Console.WriteLine("I speak Spanish.");
}
public override void SayHello()
{
throw new ApplicationException("I cannot say Hello World.");
}
#endregion
}
List
качестве типа a , вы все равно можете предполагать, что произвольный доступ выполняется быстро, многократно вызывая get(i)
.
Думайте об интерфейсе как о контракте между объектом и его клиентами. То есть интерфейс определяет, что может делать объект, и сигнатуры для доступа к этим вещам.
Реализации - это фактическое поведение. Скажем, например, у вас есть метод sort (). Вы можете реализовать QuickSort или MergeSort. Это не должно иметь значения для клиентского кода, вызывающего sort, до тех пор, пока интерфейс не изменяется.
Такие библиотеки, как Java API и .NET Framework, интенсивно используют интерфейсы, потому что миллионы программистов используют предоставленные объекты. Создатели этих библиотек должны быть очень осторожны, чтобы не изменить интерфейс к классам в этих библиотеках, потому что это повлияет на всех программистов, использующих библиотеку. С другой стороны, они могут менять реализацию сколько угодно.
Если, как программист, вы кодируете вопреки реализации, то как только она меняет, ваш код перестает работать. Итак, подумайте о преимуществах интерфейса следующим образом:
Это означает, что вы должны попытаться написать свой код так, чтобы он напрямую использовал абстракцию (абстрактный класс или интерфейс), а не реализацию.
Обычно реализация внедряется в ваш код через конструктор или вызов метода. Итак, ваш код знает об интерфейсе или абстрактном классе и может вызывать все, что определено в этом контракте. Поскольку используется реальный объект (реализация интерфейса / абстрактного класса), вызовы работают с объектом.
Это подмножество Liskov Substitution Principle
(LSP), L SOLID
принципов.
Примером в .NET будет код с IList
вместо List
или Dictionary
, поэтому вы можете использовать любой класс, который IList
взаимозаменяемо реализуется в вашем коде:
// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
// Do anything that IList supports
return myList.Count();
}
Другой пример из библиотеки базовых классов (BCL) - это ProviderBase
абстрактный класс - он предоставляет некоторую инфраструктуру и, что немаловажно, означает, что все реализации поставщика могут использоваться взаимозаменяемо, если вы кодируете против него.
Если бы вам пришлось писать класс автомобилей в эпоху Combustion-Car, то велика вероятность, что вы бы реализовали oilChange () как часть этого класса. Но когда появятся электромобили, у вас возникнут проблемы, так как в этих автомобилях не требуется замена масла и нет никаких дополнительных приспособлений.
Решение проблемы состоит в том, чтобы иметь интерфейс performMain maintenance () в классе Car и скрывать детали внутри соответствующей реализации. Каждый тип Car будет предоставлять свою собственную реализацию для performMain maintenance (). Все, с чем вам, как владельцу Автомобиля, приходится иметь дело, - это выполнитьMain maintenance () и не беспокоиться об адаптации при ИЗМЕНЕНИИ.
class MaintenanceSpecialist {
public:
virtual int performMaintenance() = 0;
};
class CombustionEnginedMaintenance : public MaintenanceSpecialist {
int performMaintenance() {
printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
return 0;
}
};
class ElectricMaintenance : public MaintenanceSpecialist {
int performMaintenance() {
printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
return 0;
}
};
class Car {
public:
MaintenanceSpecialist *mSpecialist;
virtual int maintenance() {
printf("Just wash the car \n");
return 0;
};
};
class GasolineCar : public Car {
public:
GasolineCar() {
mSpecialist = new CombustionEnginedMaintenance();
}
int maintenance() {
mSpecialist->performMaintenance();
return 0;
}
};
class ElectricCar : public Car {
public:
ElectricCar() {
mSpecialist = new ElectricMaintenance();
}
int maintenance(){
mSpecialist->performMaintenance();
return 0;
}
};
int _tmain(int argc, _TCHAR* argv[]) {
Car *myCar;
myCar = new GasolineCar();
myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */
myCar = new ElectricCar();
myCar->maintenance();
return 0;
}
Дополнительное объяснение: вы - автовладелец, у которого несколько машин. Вы создаете услугу, которую хотите передать на аутсорсинг. В нашем случае мы хотим передать обслуживание всех автомобилей на аутсорсинг.
Вам не нужно беспокоиться о привязке типа автомобиля к поставщику услуг. Вы просто указываете, когда хотите запланировать обслуживание, и вызываете его. Соответствующая сервисная компания должна вмешаться и выполнить работы по техническому обслуживанию.
Альтернативный подход.
Вы вызываете работу и делаете ее сами. Здесь вы будете выполнять соответствующие работы по техническому обслуживанию.
В чем обратная сторона второго подхода? Возможно, вы не являетесь экспертом в поиске лучшего способа обслуживания. Ваша задача - водить машину и получать от нее удовольствие. Не заниматься его поддержанием.
В чем обратная сторона первого подхода? Есть накладные расходы на поиск компании и т. Д. Если вы не компания по аренде автомобилей, это может не стоить усилий.
Это утверждение о сцеплении. Одна из возможных причин использования объектно-ориентированного программирования - повторное использование. Так, например, вы можете разделить свой алгоритм между двумя взаимодействующими объектами A и B. Это может быть полезно для последующего создания другого алгоритма, который может повторно использовать тот или иной из двух объектов. Однако, когда эти объекты общаются (отправляют сообщения - вызывают методы), они создают зависимости друг от друга. Но если вы хотите использовать одно без другого, вам нужно указать, что должен делать другой объект C для объекта A, если мы заменим B. Эти описания называются интерфейсами. Это позволяет объекту A без изменений связываться с другим объектом, полагаясь на интерфейс. В упомянутом вами заявлении говорится, что если вы планируете повторно использовать какую-то часть алгоритма (или, в более общем плане, программу), вы должны создавать интерфейсы и полагаться на них,
Как говорили другие, это означает, что ваш вызывающий код должен знать только об абстрактном родителе, а НЕ о фактическом реализующем классе, который будет выполнять эту работу.
Что помогает понять это, так это ПОЧЕМУ вы всегда должны программировать интерфейс. Есть много причин, но две из самых простых для объяснения:
1) Тестирование.
Скажем, у меня есть весь код моей базы данных в одном классе. Если моя программа знает о конкретном классе, я могу проверить свой код, только действительно запустив его для этого класса. Я использую -> для обозначения «разговаривает с».
WorkerClass -> DALClass Однако давайте добавим интерфейс к этому миксу.
WorkerClass -> IDAL -> DALClass.
Таким образом, DALClass реализует интерфейс IDAL, и рабочий класс вызывает ТОЛЬКО через него.
Теперь, если мы хотим написать тесты для кода, мы могли бы вместо этого создать простой класс, который просто действует как база данных.
WorkerClass -> IDAL -> IFakeDAL.
2) Повторное использование
Следуя приведенному выше примеру, допустим, мы хотим перейти с SQL Server (который использует наш конкретный DALClass) на MonogoDB. Это потребует серьезной работы, но НЕ если мы запрограммированы на интерфейс. В этом случае мы просто пишем новый класс БД и меняем (через фабрику)
WorkerClass -> IDAL -> DALClass
в
WorkerClass -> IDAL -> MongoDBClass