Что означает программа для интерфейсов, а не реализация?

Эту фразу можно встретить, читая о шаблонах проектирования.

Но я этого не понимаю, может ли кто-нибудь мне это объяснить?


person never_had_a_name    schedule 23.04.2010    source источник


Ответы (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 text

Это всего лишь базовый пример, и фактическое объяснение принципа выходит за рамки данного ответа.

РЕДАКТИРОВАТЬ

Я обновил приведенный выше пример и добавил абстрактный базовый класс Speaker. В этом обновлении я добавил функцию «SayHello» для всех выступающих. Все выступающие говорят «Привет, мир». Так что это общая черта с аналогичной функцией. Обратитесь к диаграмме классов, и вы обнаружите, что Speaker абстрактный класс реализует интерфейс ISpeaker и помечает Speak() как абстрактный, что означает, что каждая реализация Speaker отвечает за реализацию метода Speak(), поскольку он варьируется от Speaker до Speaker. Но все выступающие единодушно говорят «Привет». Итак, в абстрактном классе Speaker мы определяем метод, который говорит «Hello World», и каждая реализация Speaker будет наследовать метод SayHello().

Рассмотрим случай, когда SpanishSpeaker не может сказать «Привет», поэтому в этом случае вы можете переопределить метод SayHello() для говорящего по-испански и вызвать соответствующее исключение.

Обратите внимание, что мы не вносили никаких изменений в Interface ISpeaker. И клиентский код, и SpeakerFactory также остаются неизменными. И именно этого мы достигаем с помощью Programming-to-Interface.

И мы могли бы добиться такого поведения, просто добавив базовый абстрактный класс 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
}

alt text

person this. __curious_geek    schedule 23.04.2010
comment
Программирование интерфейса касается не только типа ссылочной переменной. Это также означает, что вы не используете никаких неявных предположений о своей реализации. Например, если вы используете List в качестве типа, вы все равно можете предполагать, что произвольный доступ выполняется быстро, многократно вызывая get(i). - person Joachim Sauer; 23.04.2010
comment
Фабрики ортогональны программированию и интерфейсам, но я думаю, что это объяснение создает впечатление, будто они являются его частью. - person T .; 23.04.2010
comment
@Toon: согласен с тобой. Я хотел предоставить очень простой и простой пример программирования интерфейса. Я не хотел запутывать вопрошающего, реализуя интерфейс IFlyable для нескольких классов птиц и животных. - person this. __curious_geek; 23.04.2010
comment
@это. если я вместо этого использую абстрактный класс или паттерн фасада, будет ли он вызываться программой для интерфейса? или мне явно нужно использовать интерфейс и реализовать его в классе? - person never_had_a_name; 23.04.2010
comment
Нет. Использование абстрактного класса было бы другим сценарием по сравнению с программным интерфейсом. В этом случае ваш абстрактный класс будет реализовывать интерфейс и позволить другим классам наследоваться от абстрактного класса, что также заставит их наследовать интерфейс. Скоро обновлю свои примеры для справки. - person this. __curious_geek; 24.04.2010
comment
@ajsie: Обновлен ответ с измененным примером и диаграммой классов. Надеюсь это поможет. - person this. __curious_geek; 24.04.2010
comment
Один из URL-адресов изображения не работает :( - person appbootup; 12.03.2013
comment
Какой инструмент uml вы использовали для создания изображений? - person Adam Arold; 25.06.2013
comment
... клиентский код всегда содержит объект интерфейса, который предоставляется фабрикой. Нет, это неверно. Принцип не в этом. Если вы пишете List aList = new ArrayList(), вы кодируете интерфейс List, а не реализацию ArrayList (здесь используется Java). Более того, если вы используете класс без отдельного интерфейса, например StringBuilder bldr = new StringBuilder() (который реализует интерфейс CharSequence, но мы можем проигнорировать это здесь), вы по-прежнему кодируете интерфейс, а не реализацию ( неявный публичный интерфейс класса StringBuilder). - person Rogério; 19.09.2014
comment
хорошо, зачем нужен абстрактный класс и интерфейс. у вас может быть просто абстрактный класс, а подклассы расширяют этот класс. Первая реализация имеет для меня больше смысла - интерфейс и 3 подкласса, которыми, если я не ошибаюсь, является Factory-Pattern. - person miatech; 18.02.2020

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

Реализации - это фактическое поведение. Скажем, например, у вас есть метод sort (). Вы можете реализовать QuickSort или MergeSort. Это не должно иметь значения для клиентского кода, вызывающего сортировку, до тех пор, пока интерфейс не изменяется.

Такие библиотеки, как Java API и .NET Framework, интенсивно используют интерфейсы, потому что миллионы программистов используют предоставленные объекты. Создатели этих библиотек должны быть очень осторожны, чтобы не изменить интерфейс к классам в этих библиотеках, потому что это повлияет на всех программистов, использующих библиотеку. С другой стороны, они могут изменять реализацию сколько угодно.

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

  1. он скрывает то, что вам не нужно знать, делая объект более простым в использовании.
  2. он предоставляет контракт о том, как объект будет вести себя, поэтому вы можете зависеть от этого
person Vincent Ramdhanie    schedule 23.04.2010
comment
Это означает, что вам нужно знать, что вы заключаете контракт с объектом: в приведенном примере вы заключаете контракт только на сортировку, а не обязательно на стабильную сортировку. - person penguat; 23.04.2010
comment
Подобно тому, как в документации библиотеки не упоминается реализация, это просто описания включенных интерфейсов классов. - person Joe Iddon; 30.10.2018

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

Обычно реализация вводится в ваш код через конструктор или вызов метода. Итак, ваш код знает об интерфейсе или абстрактном классе и может вызывать все, что определено в этом контракте. Поскольку используется реальный объект (реализация интерфейса / абстрактного класса), вызовы работают с объектом.

Это подмножество Liskov Substitution Principle (LSP), L из _ 2_ принципы.

Примером в .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 абстрактный класс - это обеспечивает некоторую инфраструктуру и, что немаловажно, означает, что все реализации провайдера могут использоваться взаимозаменяемо, если вы кодируете против него.

person Oded    schedule 23.04.2010
comment
но как клиент может взаимодействовать с интерфейсом и использовать его пустые методы? - person never_had_a_name; 23.04.2010
comment
Клиент взаимодействует не с интерфейсом, а через интерфейс :) Объекты взаимодействуют с другими объектами через методы (сообщения), а интерфейс - это своего рода язык - когда вы знаете, что определенный объект (человек) реализует (говорит) английский язык (IList ), вы можете использовать его без необходимости знать больше об этом объекте (что он также итальянец), потому что в этом контексте нет необходимости (если вы хотите попросить о помощи, вам не нужно знать, что он также говорит по-итальянски. если понимаете английский). - person Gabriel Ščerbák; 23.04.2010
comment
КСТАТИ. ИМХО принцип подстановки Лисков связан с семантикой наследования и не имеет ничего общего с интерфейсами, которые можно найти и в языках без наследования (Go from Google). - person Gabriel Ščerbák; 23.04.2010

Если бы вы писали класс автомобилей в эпоху Combustion-Car, то велика вероятность, что вы бы реализовали oilChange () как часть этого класса. Но когда появятся электромобили, у вас возникнут проблемы, так как в этих автомобилях не требуется замена масла и никаких дополнительных приспособлений.

Решение проблемы состоит в том, чтобы иметь интерфейс performMain maintenance () в классе Car и скрывать детали внутри соответствующей реализации. Каждый тип Car предоставит свою собственную реализацию для performMain maintenance (). Все, с чем вы, как владелец Автомобиля, должны иметь дело - это performMain 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;
}

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

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

    Альтернативный подход.

  4. Вы определяете работу (может быть новый интерфейс Interface), которая подходит для всех ваших автомобилей.
  5. Вы придумываете механизм предоставления услуги. В основном вы собираетесь предоставить реализацию.
  6. Вы вызываете работу и делаете ее сами. Здесь вы будете выполнять соответствующие работы по техническому обслуживанию.

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

    В чем обратная сторона первого подхода? Есть накладные расходы на поиск компании и т. Д. Если вы не являетесь компанией по аренде автомобилей, это может не стоить усилий.

person Raghav Navada    schedule 11.03.2014

Это утверждение о сцеплении. Одна из возможных причин использования объектно-ориентированного программирования - это повторное использование. Так, например, вы можете разделить свой алгоритм между двумя взаимодействующими объектами A и B. Это может быть полезно для последующего создания другого алгоритма, который может повторно использовать тот или иной из двух объектов. Однако, когда эти объекты обмениваются данными (отправляют сообщения - вызывают методы), они создают зависимости друг от друга. Но если вы хотите использовать одно без другого, вам нужно указать, что должен делать какой-либо другой объект C для объекта A, если мы заменим B. Эти описания называются интерфейсами. Это позволяет объекту A без изменений связываться с другим объектом, полагаясь на интерфейс. В упомянутом вами заявлении говорится, что если вы планируете повторно использовать некоторую часть алгоритма (или, в более общем плане, программу), вы должны создавать интерфейсы и полагаться на них, поэтому вы можете изменить конкретную реализацию в любое время, не изменяя другие объекты, если вы используете заявленный интерфейс.

person Gabriel Ščerbák    schedule 23.04.2010

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

Понять это помогает ПОЧЕМУ вы всегда должны программировать интерфейс. Есть много причин, но две из самых простых для объяснения:

1) Тестирование.

Допустим, у меня есть весь код моей базы данных в одном классе. Если моя программа знает о конкретном классе, я могу проверить свой код, только действительно запустив его для этого класса. Я использую -> для обозначения «разговаривает с».

WorkerClass -> DALClass Однако давайте добавим интерфейс к этому миксу.

WorkerClass -> IDAL -> DALClass.

Таким образом, DALClass реализует интерфейс IDAL, и рабочий класс вызывает ТОЛЬКО через него.

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

WorkerClass -> IDAL -> IFakeDAL.

2) Повторное использование

Следуя приведенному выше примеру, допустим, мы хотим перейти с SQL Server (который использует наш конкретный класс DALClass) на MonogoDB. Это потребует серьезной работы, но НЕ, если мы запрограммировали интерфейс. В этом случае мы просто пишем новый класс БД и меняем (через фабрику)

WorkerClass -> IDAL -> DALClass

to

WorkerClass -> IDAL -> MongoDBClass

person Mathieson    schedule 29.10.2013

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

person rektide    schedule 23.09.2010