O que significa “programa para interfaces, não implementações”?

Respostas:

148

As interfaces são apenas contratos ou assinaturas e eles não sabem nada sobre implementações.

Codificando contra meios de interface, o código do cliente sempre mantém um objeto de Interface que é fornecido por uma fábrica. Qualquer instância retornada pela fábrica seria do tipo Interface que qualquer classe candidata à fábrica deve ter implementado. Dessa forma, o programa cliente não está preocupado com a implementação e a assinatura da interface determina o que todas as operações podem ser feitas. Isso pode ser usado para alterar o comportamento de um programa em tempo de execução. Também ajuda a escrever programas muito melhores do ponto de vista da manutenção.

Aqui está um exemplo básico para você.

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
}

texto alternativo

Este é apenas um exemplo básico e a explicação real do princípio está além do escopo desta resposta.

EDITAR

Atualizei o exemplo acima e adicionei uma Speakerclasse base abstrata . Nesta atualização, adicionei um recurso a todos os alto-falantes no "SayHello". Todos os oradores falam "Olá Mundo". Portanto, esse é um recurso comum com função semelhante. Consulte o diagrama de classes e você encontrará essa interface de Speakerimplementação de classe abstrata ISpeakere marca Speak()como abstrata, o que significa que cada implementação do Speaker é responsável por implementar o Speak()método, pois varia de Speakerpara Speaker. Mas todos os oradores dizem "Olá" por unanimidade. Portanto, na classe abstrata Speaker, definimos um método que diz "Hello World" e cada Speakerimplementação derivará o SayHello()método.

Considere um caso em SpanishSpeakerque não é possível dizer Olá, portanto, nesse caso, você pode substituir o SayHello()método para quem fala espanhol e gerar uma exceção adequada.

Observe que não fizemos alterações no Interface ISpeaker. E o código do cliente e o SpeakerFactory também permanecem inalterados. E é isso que alcançamos através da programação em interface .

E podemos obter esse comportamento simplesmente adicionando uma classe abstrata base Speaker e algumas pequenas modificações em Cada implementação, deixando assim o programa original inalterado. Esse é um recurso desejado de qualquer aplicativo e facilita sua manutenção.

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
}

texto alternativo

isto. __curious_geek
fonte
19
A programação para a interface não é apenas sobre o tipo da variável de referência. Isso também significa que você não usa nenhuma suposição implícita sobre sua implementação. Por exemplo, se você usar a Listcomo o tipo, você ainda pode estar assumindo que o acesso aleatório é rápido chamando repetidamente get(i).
Joachim Sauer
16
As fábricas são ortogonais à programação para interfaces, mas acho que essa explicação faz parecer que fazem parte dela.
T.
@ Toon: concordo com você. Eu queria fornecer um exemplo muito básico e simples de programação para interface. Eu não queria confundir o questionador implementando a interface IFlyable em poucas classes de aves e animais.
isso. __curious_geek
@isto. se eu usar uma classe abstrata ou um padrão de fachada, ele ainda será chamado de "programa para uma interface"? ou preciso explicitamente usar uma interface e implementá-la em uma classe?
never_had_a_name
1
Qual ferramenta uml você estava usando para criar as imagens?
Adam Arold
29

Pense em uma interface como um contrato entre um objeto e seus clientes. Essa é a interface especifica as coisas que um objeto pode fazer e as assinaturas para acessar essas coisas.

Implementações são os comportamentos reais. Digamos, por exemplo, que você tenha um método sort (). Você pode implementar o QuickSort ou MergeSort. Isso não deve importar a classificação de chamada de código do cliente, desde que a interface não seja alterada.

Bibliotecas como a API Java e o .NET Framework fazem uso pesado de interfaces, porque milhões de programadores usam os objetos fornecidos. Os criadores dessas bibliotecas precisam ter muito cuidado para não alterar a interface para as classes nessas bibliotecas, pois isso afetará todos os programadores que usam a biblioteca. Por outro lado, eles podem alterar a implementação o quanto quiserem.

Se, como programador, você codificar na implementação, assim que ela mudar, seu código deixará de funcionar. Então, pense nos benefícios da interface desta maneira:

  1. oculta o que você não precisa saber, tornando o objeto mais simples de usar.
  2. fornece o contrato de como o objeto se comportará para que você possa depender disso
Vincent Ramdhanie
fonte
Isso significa que você precisa estar ciente do que está contratando o objeto: no exemplo, desde que você esteja contratando apenas uma classificação, não necessariamente uma classificação estável.
Penguat
Portanto, semelhante à documentação da biblioteca que não menciona a implementação, são apenas descrições das interfaces de classe incluídas.
Joe Iddon
17

Isso significa que você deve tentar escrever seu código para que ele use uma abstração (classe abstrata ou interface) em vez da implementação diretamente.

Normalmente, a implementação é injetada no seu código por meio do construtor ou de uma chamada de método. Portanto, seu código conhece a interface ou a classe abstrata e pode chamar qualquer coisa definida neste contrato. Como um objeto real (implementação da interface / classe abstrata) é usado, as chamadas estão operando no objeto.

Este é um subconjunto do Liskov Substitution Principle(LSP), o L dos SOLIDprincípios.

Um exemplo no .NET seria codificar em IListvez de Listou Dictionary, para que você pudesse usar qualquer classe que implemente de forma IListintercambiável no seu código:

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}

Outro exemplo da BCL (Base Class Library) é a ProviderBaseclasse abstrata - isso fornece alguma infraestrutura e, o que é mais importante, significa que todas as implementações de provedor podem ser usadas de forma intercambiável se você codificar contra ela.

Oded
fonte
mas como um cliente pode interagir com uma interface e usar seus métodos vazios?
#
1
O cliente não interage com a interface, mas através da interface :) Os objetos interagem com outros objetos através de métodos (mensagens) e uma interface é uma espécie de idioma - quando você sabe que determinado objeto (pessoa) implementa (fala) inglês (IList ), você pode usá-lo sem precisar saber mais sobre esse objeto (que ele também é italiano), porque não é necessário nesse contexto (se você deseja pedir ajuda, não precisa saber que ele também fala italiano) se você entende inglês).
Gabriel Ščerbák
Entre. O princípio de substituição do IMHO Liskov é sobre a semântica da herança e não tem nada a ver com interfaces, que também podem ser encontradas em idiomas sem herança (Go from Google).
Gabriel Ščerbák
5

Se você escrever uma classe de carro na era de carros de combustão, há uma grande chance de implementar o oilChange () como parte dessa classe. Mas, quando carros elétricos são introduzidos, você estaria com problemas, pois não há troca de óleo envolvida nesses carros e nenhuma implementação.

A solução para o problema é ter uma interface performMaintenance () na classe Car e ocultar detalhes dentro da implementação apropriada. Cada tipo de carro forneceria sua própria implementação para performMaintenance (). Como proprietário de um carro, tudo com o qual você precisa lidar é performMaintenance () e não se preocupa em se adaptar quando houver uma MUDANÇA.

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;
}

Explicação adicional: você é proprietário de um carro e possui vários carros. Você cria o serviço que deseja terceirizar. No nosso caso, queremos terceirizar o trabalho de manutenção de todos os carros.

  1. Você identifica o contrato (Interface) válido para todos os seus carros e prestadores de serviços.
  2. Os provedores de serviços apresentam um mecanismo para fornecer o serviço.
  3. Você não quer se preocupar em associar o tipo de carro ao provedor de serviços. Você apenas especifica quando deseja agendar a manutenção e invocá-la. Uma empresa de serviços apropriada deve entrar e executar o trabalho de manutenção.

    Abordagem alternativa.

  4. Você identifica o trabalho (pode ser uma nova interface) que vale para todos os seus carros.
  5. Vocês cria um mecanismo para fornecer o serviço. Basicamente, você fornecerá a implementação.
  6. Você invoca o trabalho e faz você mesmo. Aqui você fará o trabalho de manutenção apropriado.

    Qual é a desvantagem da segunda abordagem? Você pode não ser o especialista em encontrar a melhor maneira de fazer a manutenção. Seu trabalho é dirigir o carro e se divertir. Não estar no negócio de mantê-lo.

    Qual é a desvantagem da primeira abordagem? Existe a sobrecarga de encontrar uma empresa etc. A menos que você seja uma empresa de aluguel de carros, pode não valer a pena.

Raghav Navada
fonte
4

Esta afirmação é sobre acoplamento. Um motivo potencial para o uso de programação orientada a objetos é a reutilização. Portanto, por exemplo, você pode dividir seu algoritmo entre dois objetos colaboradores A e B. Isso pode ser útil para a criação posterior de outro algoritmo, que pode reutilizar um ou outro dos dois objetos. No entanto, quando esses objetos se comunicam (enviam mensagens - chamam métodos), eles criam dependências entre si. Mas se você quiser usar um sem o outro, precisará especificar o que outro objeto C deve fazer para o objeto A, se substituirmos B. Essas descrições são chamadas de interfaces. Isso permite que o objeto A se comunique sem alterações com objetos diferentes que dependem da interface. A declaração que você mencionou diz que, se você planeja reutilizar parte de um algoritmo (ou mais geralmente um programa), deve criar interfaces e confiar nelas,

Gabriel Ščerbák
fonte
2

Como já foi dito, isso significa que seu código de chamada deve conhecer apenas um pai abstrato, NÃO a classe de implementação real que fará o trabalho.

O que ajuda a entender isso é o PORQUE você deve sempre programar em uma interface. Há muitas razões, mas duas das mais fáceis de explicar são

1) Teste.

Digamos que eu tenho todo o código do banco de dados em uma classe. Se meu programa souber sobre a classe concreta, só posso testar meu código executando-o realmente nessa classe. Estou usando -> para significar "fala com".

WorkerClass -> DALClass No entanto, vamos adicionar uma interface ao mix.

WorkerClass -> IDAL -> DALClass.

Portanto, o DALClass implementa a interface IDAL, e a classe Worker apenas chama através disso.

Agora, se quisermos escrever testes para o código, poderíamos criar uma classe simples que funciona como um banco de dados.

WorkerClass -> IDAL -> IFakeDAL.

2) Reutilização

Seguindo o exemplo acima, digamos que queremos passar do SQL Server (que nosso DALClass concreto usa) para o MonogoDB. Isso exigiria muito trabalho, mas NÃO se programamos para uma interface. Nesse caso, apenas escrevemos a nova classe de banco de dados e alteramos (via fábrica)

WorkerClass -> IDAL -> DALClass

para

WorkerClass -> IDAL -> MongoDBClass

Mathieson
fonte
1

interfaces descrevem recursos. ao escrever um código imperativo, fale sobre os recursos que você está usando, em vez de tipos ou classes específicos.

rektide
fonte