Что означает" программа для интерфейсов, а не реализации"?
одна натыкается на эту фразу при чтении о шаблонах проектирования.
но я не понимаю, может кто-то объяснить это для меня?
7 ответов:
интерфейсы контракты или просто подписи и они не знают что-нибудь о реализации.
Кодирование по интерфейсу означает, что клиентский код всегда содержит объект интерфейса, который поставляется фабрикой. Любой экземпляр, возвращаемый фабрикой, будет иметь интерфейс типа, который должен быть реализован любым классом-кандидатом фабрики. Таким образом, клиентская программа не беспокоится о реализации и определяет сигнатуру интерфейса что все операции можно сделать. Это можно использовать для изменения поведения программы во время выполнения. Это также помогает вам писать гораздо лучшие программы с точки зрения обслуживания.
вот простой пример для вас.
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 }
alt текст http://ruchitsurati.net/myfiles/interface.png
Это просто основной пример и фактическое объяснение принципа за рамками этого ответ.
EDIT
я обновил приведенный выше пример и добавил базовый класс абстрактного динамика. В этом обновлении я добавил функцию, чтобы все Spakers, чтобы "поздороваться". Все спикеры говорят "Привет Мир". Так что это общая черта с аналогичной функцией. Обратитесь к диаграмме классов, и вы обнаружите, что абстрактный класс Speaker реализует интерфейс ISpeaker и помечает Speak() как абстрактный, что означает, что каждая реализация Speaker отвечает за реализация метода Speak, поскольку он варьируется от динамика к динамику. Но все спикеры говорят" Привет " единогласно. Поэтому в абстрактном классе динамиков мы определяем метод, который говорит "Hello World", и каждая реализация динамиков будет выводить метод SayHello.
рассмотрим случай, когда SpanishSpeaker не может поздороваться, поэтому в этом случае вы можете переопределить метод SayHello для испанского говорящего и создать правильное исключение.
обратите внимание, что мы не делать любые изменения в интерфейсе ISpeaker. И клиентский код, и SpeakerFactory также остаются в силе не менявшийся. И это то, что мы достигаем с помощью программирование на интерфейс.
и мы могли бы достичь такого поведения, просто добавив базовый абстрактный динамик класса и некоторые незначительные изменения в каждой реализации, таким образом, оставляя исходную программу без изменений. Это желаемая функция любого приложения, и это делает ваше приложение легко ремонтопригодный.
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 }
alt текст http://demo.ruchitsurati.net/myfiles/interface1.png
интерфейс как контракт между объектом и его клиентами. То есть интерфейс определяет то, что может сделать объект, и сигнатуры для доступа к этим вещам.
реализации-это фактическое поведение. Скажем, например, у вас есть метод sort(). Вы можете реализовать быстрая сортировка или сортировка слиянием. Это не должно иметь значения для клиентского кода, вызывающего сортировку, пока интерфейс не изменится.
библиотеки, такие как Java API и .NET Фреймворк активно использует интерфейсы, потому что миллионы программистов используют предоставленные объекты. Создатели этих библиотек должны быть очень осторожны, чтобы они не меняли интерфейс для классов в этих библиотеках, потому что это повлияет на всех программистов, использующих библиотеку. С другой стороны, они могут изменить реализацию так, как им нравится.
Если, как программист, вы кодируете против реализации, то как только он изменяет ваш код перестает работать. Так что подумайте преимущества интерфейса таким образом:
- он скрывает вещи, которые вам не нужно знать, что делает объект проще в использовании.
- он обеспечивает контракт о том, как объект будет вести себя, так что вы можете зависеть от этого
это означает, что вы должны попытаться написать свой код, чтобы он использовал абстракцию (абстрактный класс или интерфейс) вместо непосредственной реализации.
обычно реализация вводится в код через конструктор или вызов метода. Таким образом, ваш код знает об интерфейсе или абстрактном классе и может вызывать все, что определено в этом контракте. Как фактический объект (реализация интерфейса/абстрактного класса) используется, вызовы работают на объект.
это подмножество
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
абстрактный класс - это обеспечивает некоторую инфраструктуру, а главное означает, что все реализации поставщика могут использоваться взаимозаменяемо, если вы кодируете против него.
Это утверждение о связи. Одной из потенциальных причин использования объектно-ориентированного программирования является повторное использование. Так, например, вы можете разделить свой алгоритм сотрудничества между двумя объектами A и B. Это может быть полезным для последующего создания другого алгоритма, который может использовать один или другой из двух объектов. Однако, когда эти объекты взаимодействуют (отправляют сообщения - вызывают методы), они создают зависимости друг от друга. Но если вы хотите использовать один без другого, нужно указать, что должен ли какой-то другой объект C сделать для объекта A, если мы заменим B. эти описания называются интерфейсами. Это позволяет объекту a взаимодействовать без изменений с другим объектом, полагаясь на интерфейс. В заявлении, которое вы упомянули, говорится, что если вы планируете повторно использовать какую-либо часть алгоритма (или, более широко, программу), вы должны создавать интерфейсы и полагаться на них, поэтому вы можете изменить конкретную реализацию в любое время без изменения других объектов, если вы используете объявленный интерфейс.
Если бы вы написали класс автомобиля в эпоху сжигания автомобилей, то есть большой шанс, что вы реализуете oilChange() как часть этого класса. Но, когда электромобили будут введены, вы будете в беде, поскольку для этих автомобилей нет замены масла, и нет имплементации.
решение проблемы состоит в том, чтобы иметь интерфейс performMaintenance() в классе Car и скрыть детали внутри соответствующей реализации. Каждый тип автомобиля обеспечит свою собственную реализацию для performMaintenance (). Как владелец автомобиля все, что вам нужно иметь дело с performMaintenance() и не беспокоиться об адаптации, когда есть изменения.
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; }
дополнительные объяснения: Вы владелец автомобиля, который владеет несколькими автомобилями. Вы создаете сервис, который хотите передать на аутсорсинг. В нашем случае мы хотим заказать техническое обслуживание всех автомобилей.
- вы определяете контракт (интерфейс), который подходит для всех ваших автомобилей и услуг услуги поставщиков.
- поставщики услуг выходят с механизмом для предоставления услуги.
вы не хотите беспокоиться о связи типа машина с поставщиком услуг. Вы просто указываете, когда вы хотите запланировать обслуживание и вызвать его. Соответствующая сервисная компания должна вскочить и выполнить работы по техническому обслуживанию.
альтернативный подход.
- вы определяете работу(может быть новый интерфейс интерфейса), который хорошо подходит для всех ваших автомобили.
- вы выходите с механизмом для предоставления услуги. В основном вы собираетесь обеспечить реализацию.
вы вызываете работу и делаете ее сами. Здесь вы будете выполнять соответствующие работы по техническому обслуживанию.
в чем недостаток 2-го подхода? Вы не можете быть экспертом в поиске лучшего способа сделать обслуживание. Ваша задача-водить машину и наслаждаться ею. Не быть в бизнесе поддержания оно.
в чем его обратная сторона первого подхода? Есть накладные расходы на поиск компании и т. д. Если вы не являетесь компанией по прокату автомобилей, это может не стоить усилий.
как говорили другие, это означает, что ваш вызывающий код должен знать только об абстрактном родителе, а не о фактическом классе реализации, который будет выполнять эту работу.
что помогает понять это, почему вы всегда должны программировать на интерфейс. Есть много причин, но две из самых простых, чтобы объяснить
1) тестирование.
допустим, у меня есть весь код базы данных в одном классе. Если моя программа знает о конкретном классе, я могу только проверить свой код действительно запустив его против этого класса. Я использую -> в смысле "разговаривает".
WorkerClass -> DALClass Однако, давайте добавим к смеси.
WorkerClass - > IDAL - > DALClass.
таким образом, DALClass реализует интерфейс IDAL, и рабочий класс только вызывает через это.
теперь, если мы хотим писать тесты для кода, мы можем сделать простой класс, который просто действует как база данных.
WorkerClass - > IDAL - > Ифакедал.
2) повторное использование
следуя приведенному выше примеру, предположим, что мы хотим перейти от SQL Server (который использует наш конкретный DALClass) к MonogoDB. Это потребует большой работы, но не тогда, когда мы запрограммированы на интерфейс. В этом случае мы просто пишем новый класс БД и меняем (через завод)
WorkerClass - > IDAL - > DALClass
до
WorkerClass - > IDAL - > MongoDBClass