Java faz uma distinção clara entre class
e interface
. (Acredito que o C # também o faça, mas não tenho experiência com ele). Ao escrever C ++, no entanto, não há distinção imposta por linguagem entre classe e interface.
Consequentemente, sempre vi a interface como uma solução alternativa para a falta de herança múltipla em Java. Fazer essa distinção parece arbitrário e sem sentido em C ++.
Eu sempre tendi a seguir a abordagem "escrever as coisas da maneira mais óbvia"; portanto, em C ++, tenho o que pode ser chamado de interface em Java, por exemplo:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() = 0;
};
e então decidi que a maioria dos implementadores Foo
queria compartilhar algumas funcionalidades comuns que provavelmente escreveria:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() {}
protected:
// If it needs this to do its thing:
int internalHelperThing(int);
// Or if it doesn't need the this pointer:
static int someOtherHelper(int);
};
O que torna isso não mais uma interface no sentido Java.
Em vez disso, o C ++ possui dois conceitos importantes, relacionados ao mesmo problema de herança subjacente:
virtual
herançaClasses sem variáveis de membro não podem ocupar espaço extra quando usadas como base
"Subobjetos da classe base podem ter tamanho zero"
Entre aqueles que tento evitar o número 1, sempre que possível - é raro encontrar um cenário em que esse seja realmente o design "mais limpo". O número 2 é, no entanto, uma diferença sutil, mas importante, entre o meu entendimento do termo "interface" e os recursos da linguagem C ++. Como resultado, atualmente (quase) nunca me refiro a coisas como "interfaces" em C ++ e falo em termos de classes base e seus tamanhos. Eu diria que, no contexto de C ++, "interface" é um nome impróprio.
Chegou ao meu conhecimento que muitas pessoas não fazem essa distinção.
- Posso perder alguma coisa permitindo que (por exemplo
protected
) não-virtual
funções existam em uma "interface" em C ++? (Meu sentimento é exatamente o oposto - um local mais natural para código compartilhado) - O termo "interface" é significativo em C ++ - implica apenas puro
virtual
ou seria justo chamar classes C ++ sem variáveis-membro de interface ainda?
fonte
internalHelperThing
pode ser simulado em quase todos os casos.~Foo() {}
em uma classe abstrata é um erro em (quase) todas as circunstâncias.Respostas:
No C ++, o termo "interface" não possui apenas uma definição amplamente aceita - portanto, sempre que você for usá-lo, você deve dizer exatamente o que quer dizer - uma classe base virtual com ou sem implementações padrão, um arquivo de cabeçalho, membros públicos de uma classe arbitrária e assim por diante.
Com relação ao seu exemplo: em Java (e semelhante em C #), seu código provavelmente implicaria uma separação de preocupações:
No C ++, você pode fazer isso, mas não é obrigado a fazê-lo. E se você quiser chamar uma classe de interface, se ela não tiver variáveis de membro, mas contiver implementações padrão para alguns métodos, basta fazê-lo, mas verifique se todos com quem você está falando sabem o que você quer dizer.
fonte
public
partes do.h
arquivo.Parece que você pode ter caído na armadilha de confundir o significado do que é que uma interface é conceitualmente como uma implementação (a interface - minúscula 'i') e uma abstração (Uma Interface - maiúscula 'eu' )
Com relação aos seus exemplos, seu primeiro bit de código é apenas uma classe. Embora sua classe tenha uma interface no sentido de fornecer métodos para permitir acessos ao seu comportamento, não é uma Interface no sentido de uma declaração de Interface que fornece uma camada de abstração representando um tipo de comportamento que você pode querer que as classes implemento. A resposta do Doc Brown à sua postagem mostra exatamente o que estou falando aqui.
As interfaces são frequentemente apontadas como a "solução alternativa" para idiomas que não suportam herança múltipla, mas acho que isso é mais um equívoco do que uma verdade difícil (e suspeito que possa ser inflamado por afirmar isso!). As interfaces realmente não têm nada a ver com herança múltipla, pois elas não exigem que sejam herdadas ou sejam um ancestral para fornecer compatibilidade funcional entre classes ou abstração de implementação para classes. Na verdade, eles podem permitir que você efetivamente elimine completamente a herança, caso deseje implementar seu código dessa maneira - não que eu o recomende inteiramente, mas estou apenas dizendo que você poderiafaça. Portanto, a realidade é que, independentemente de questões de herança, as Interfaces fornecem um meio pelo qual os tipos de classe podem ser definidos, estabelecendo as regras que determinam como os objetos podem se comunicar, permitindo que você determine o comportamento que suas classes devem suportar sem ditando o método específico usado para implementar esse comportamento.
Uma interface pura deve ser inteiramente abstrata, porque permite a definição de um contrato de compatibilidade entre classes que pode não necessariamente ter herdado seu comportamento de um ancestral comum. Quando implementado, você deseja escolher se deseja permitir que o comportamento da implementação seja estendido em uma subclasse. Se seus métodos não forem
virtual
, você perde a capacidade de estender esse comportamento posteriormente, caso decida criar classes descendentes. Independentemente de a implementação ser virtual ou não, a Interface define a compatibilidade comportamental por si só, enquanto a classe fornece a implementação desse comportamento para as instâncias que a classe representa.Perdoe-me, mas já faz muito tempo que eu realmente escrevi um aplicativo sério em C ++. Lembro-me de que Interface é uma palavra-chave para fins de abstração, como os descrevi aqui. Eu não chamaria classes C ++ de nenhum tipo de Interface, mas diria que a classe tem uma interface dentro dos significados que descrevi acima. Nesse sentido, o termo é significativo, mas isso realmente depende do contexto.
fonte
As interfaces Java não são uma "solução alternativa", são uma decisão deliberada de design para evitar alguns dos problemas com herança múltipla, como herança de diamante, e incentivar práticas de design que minimizem o acoplamento.
Sua interface com métodos protegidos é um caso de necessidade de "preferir composição a herança". Para citar Sutter e Alexandrescu, na seção com esse nome, de seus excelentes padrões de codificação em C ++ :
Ao incluir as funções auxiliares em sua interface, você pode economizar um pouco de digitação agora, mas está introduzindo acoplamentos que o prejudicarão no futuro. É quase sempre melhor a longo prazo separar e passar as funções auxiliares em a
Foo*
.O STL é um bom exemplo disso. O maior número possível de funções auxiliares é extraído, em
<algorithm>
vez de pertencer às classes de contêineres. Por exemplo, comosort()
usa a API de contêiner público para fazer seu trabalho, você sabe que pode implementar seu próprio algoritmo de classificação sem precisar alterar nenhum código STL. Esse design permite bibliotecas como o boost, que aprimoram o STL em vez de substituí-lo, sem que o STL precise saber algo sobre o boost.fonte
Você pensaria isso, mas colocar um método não virtual protegido em uma classe abstrata, de outra forma, determina a implementação para quem escreve uma subclasse. Fazer isso derrota o propósito de uma interface em seu sentido puro, que é fornecer um verniz que oculta o que está por baixo.
Esse é um daqueles casos em que não há uma resposta única e você precisa usar sua experiência e julgamento para tomar uma decisão. Se você puder dizer com 100% de confiança que todas as subclasses possíveis de sua classe totalmente virtual
Foo
sempre precisarão sempre de uma implementação do método protegidobar()
, entãoFoo
é o lugar certo para isso. Depois de ter uma subclasseBaz
que não precisabar()
, você precisa conviver com o fato de queBaz
terá acesso ao código que não deveria ou passar pelo exercício de reorganizar sua hierarquia de classes. O primeiro não é uma boa prática e o segundo pode levar mais tempo do que os poucos minutos necessários para organizar as coisas corretamente em primeiro lugar.A Seção 10.4 do padrão C ++ faz uma menção passageira ao uso de classes abstratas para implementar interfaces, mas não as define formalmente. O termo é significativo em um contexto geral de ciência da computação, e qualquer pessoa competente deve entender que "
Foo
é uma interface para (o que quer que seja)" implica alguma forma de abstração. Aqueles com exposição a idiomas com construções de interface definidas podem pensar em virtual puro, mas qualquer pessoa que precise realmente trabalhar comFoo
isso analisará sua definição antes de prosseguir.fonte
baz
sempre pode ter uma implementação comoreturn false;
ou o que quer. Se o método não se aplicar a todas as subclasses, ele não pertence à classe abstrata de base, de nenhuma forma.