Что “программирует к интерфейсам, не реализациям”, средним?

Каждый натыкается на эту фразу при чтении о шаблонах разработки.

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

119
задан andrew.fox 23 January 2016 в 22:11
поделиться

4 ответа

Интерфейсы - это просто контракты или подписи, и они ничего не знают о реализациях.

Кодирование против интерфейса означает, что клиентский код всегда содержит объект интерфейса, который предоставляется фабрикой. Любой экземпляр, возвращаемый фабрикой, будет иметь тип Интерфейс, который должен быть реализован любым классом-кандидатом фабрики. Таким образом, клиентская программа не заботится о реализации, а подпись интерфейса определяет, какие все операции могут быть выполнены. Это можно использовать для изменения поведения программы во время выполнения. Это также помогает вам писать гораздо лучшие программы с точки зрения обслуживания.

Вот вам простой пример.

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 () как абстрактный, что означает, что каждая реализация Speak отвечает за реализацию метода Speak, поскольку он варьируется от Speak к Speaker. Но все выступающие единодушно говорят «Привет». Итак, в абстрактном классе Speaker мы определяем метод, который говорит «Hello World», и каждая реализация Speaker будет наследовать метод SayHello.

Рассмотрим случай, когда SpanishSpeaker не может сказать Hello, поэтому в этом случае вы можете переопределить метод SayHello для Spanish Speaker и вызвать соответствующее исключение.

Обратите внимание, что мы не вносили никаких изменений в интерфейс 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

133
ответ дан 24 November 2019 в 01:51
поделиться

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

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

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

15
ответ дан 24 November 2019 в 01:51
поделиться

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

4
ответ дан 24 November 2019 в 01:51
поделиться

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

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

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

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

  1. он скрывает то, что вам не нужно знать, делая объект более простым в использовании.
  2. он обеспечивает контракт о том, как объект будет вести себя, так что вы можете полагаться на это
26
ответ дан 24 November 2019 в 01:51
поделиться