Como você declara uma interface em C ++?


Respostas:


686

Para expandir a resposta de bradtgmurray , convém fazer uma exceção à lista de métodos virtuais puros da sua interface adicionando um destruidor virtual. Isso permite que você passe a propriedade do ponteiro para outra parte sem expor a classe derivada concreta. O destruidor não precisa fazer nada, porque a interface não possui membros concretos. Pode parecer contraditório definir uma função como virtual e embutida, mas confie em mim - não é.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

Você não precisa incluir um corpo para o destruidor virtual - alguns compiladores têm problemas para otimizar um destruidor vazio e é melhor usar o padrão.


106
Desctutor virtual ++! Isto é muito importante. Você também pode incluir declarações virtuais puras do operador = e copiar as definições do construtor para impedir que o compilador gere automaticamente essas para você.
xan

33
Uma alternativa para um destruidor virtual é um destruidor protegido. Isso desativa a destruição polimórfica, que pode ser mais apropriada em algumas circunstâncias. Procure a "Diretriz 4" em gotw.ca/publications/mill18.htm .
Fred Larson

9
Uma outra opção é definir um =0destruidor virtual ( ) puro com um corpo. A vantagem aqui é que o compilador pode, teoricamente, ver que o vtable não tem membros válidos agora e descartá-lo completamente. Com um destruidor virtual com um corpo, o destruidor pode ser chamado (virtualmente), por exemplo, no meio da construção via thisponteiro (quando o objeto construído ainda é do Parenttipo) e, portanto, o compilador deve fornecer uma tabela válida. Portanto, se você não chamar explicitamente os destruidores virtuais thisdurante a construção :), poderá economizar no tamanho do código.
Pavel Minaev

51
Como é típico de uma resposta C ++ que a resposta principal não responda diretamente à pergunta (embora obviamente o código seja perfeito), ele otimiza a resposta simples.
Tim

18
Não esqueça que no C ++ 11 você pode especificar a overridepalavra-chave para permitir argumentos em tempo de compilação e verificar o tipo de valor de retorno. Por exemplo, na declaração de Childvirtual void OverrideMe() override;
Sean

245

Faça uma aula com métodos virtuais puros. Use a interface criando outra classe que substitui esses métodos virtuais.

Um método virtual puro é um método de classe definido como virtual e atribuído a 0.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

29
você deve ter um destruidor do not nothing no IDemo, para que seja definido um comportamento a ser feito: IDemo * p = new Child; / * qualquer que seja * / delete p;
Evan Teran

11
Por que o método OverrideMe na classe Child é virtual? Isso é necessário?
Cemre

9
@Cemre - não, não é necessário, mas também não dói.
PowerApp101

11
Geralmente, é uma boa idéia manter a palavra-chave 'virtual' sempre que substituir um método virtual. Embora não seja obrigatório, ele pode tornar o código mais claro - caso contrário, você não tem indicação de que esse método possa ser usado polimorficamente ou mesmo exista na classe base.
Kevin

27
@Kevin Exceto com overrideem C ++ 11
keyser

146

Todo o motivo pelo qual você possui uma categoria de tipo de interface especial, além das classes base abstratas em C # / Java, é porque o C # / Java não suporta herança múltipla.

O C ++ suporta herança múltipla e, portanto, não é necessário um tipo especial. Uma classe base abstrata sem métodos não abstratos (virtual puro) é funcionalmente equivalente a uma interface C # / Java.


17
Ainda seria bom poder criar interfaces, para nos impedir de digitar tanto (virtual, = 0, destruidor virtual). Também herança múltipla parece uma péssima idéia para mim e nunca a vi usada na prática, mas as interfaces são necessárias o tempo todo. Para piorar, a comunidade C ++ não introduzirá interfaces apenas porque eu as quero.
usar o seguinte comando

9
Ha11owed: Possui interfaces. Eles são chamados de classes com métodos virtuais puros e sem implementações de métodos.
Route de milhas

6
@doc: java.lang.Thread possui métodos e constantes que você provavelmente não deseja ter em seu objeto. O que o compilador deve fazer se você estender do Thread e de outra classe com o método público checkAccess ()? Você realmente prefere usar ponteiros base com nomes fortes como em C ++? Isso parece um design ruim, geralmente você precisa de composição onde pensa que precisa de herança múltipla.
Ha11owed

4
@ Como eu sabia que fazia muito tempo, não lembro de detalhes, mas havia métodos e conteúdos que eu queria ter na minha classe e, mais importante, queria que meu objeto de classe derivado fosse uma Threadinstância. A herança múltipla pode ter um design ruim e também uma composição. Tudo depende do caso.
doc

2
@ Dave: Realmente? O Objective-C possui avaliação em tempo de compilação e modelos?
Deduplicator

51

Não existe um conceito de "interface" propriamente dito em C ++. AFAIK, as interfaces foram introduzidas pela primeira vez em Java para solucionar a falta de herança múltipla. Esse conceito acabou sendo bastante útil, e o mesmo efeito pode ser alcançado em C ++ usando uma classe base abstrata.

Uma classe base abstrata é uma classe na qual pelo menos uma função de membro (método no Java Java) é uma função virtual pura declarada usando a seguinte sintaxe:

class A
{
  virtual void foo() = 0;
};

Uma classe base abstrata não pode ser instanciada, ou seja, você não pode declarar um objeto da classe A. Você só pode derivar classes de A, mas qualquer classe derivada que não forneça uma implementação de foo() também será abstrata. Para deixar de ser abstrata, uma classe derivada deve fornecer implementações para todas as funções virtuais puras que herda.

Observe que uma classe base abstrata pode ser mais do que uma interface, porque pode conter membros de dados e funções de membro que não são puramente virtuais. Um equivalente de uma interface seria uma classe base abstrata sem dados com apenas funções virtuais puras.

E, como Mark Ransom apontou, uma classe base abstrata deve fornecer um destruidor virtual, como qualquer classe base, nesse caso.


13
Mais do que "falta de herança múltipla", eu diria, para substituir a herança múltipla. O Java foi projetado assim desde o início, porque a herança múltipla cria mais problemas do que o que resolve. Boa resposta
OscarRyz 25/11/2008

11
Oscar, isso depende se você é um programador de C ++ que aprendeu Java ou vice-versa. :) IMHO, se usado criteriosamente, como quase tudo em C ++, a herança múltipla resolve problemas. Uma classe base abstrata da "interface" é um exemplo de uso muito criterioso da herança múltipla.
Dima

8
@OscarRyz Errado. O MI só cria problemas quando mal utilizado. A maioria dos problemas alegados com o MI também apresentava designs alternativos (sem o MI). Quando as pessoas têm um problema com seu design com o MI, a culpa é do MI; se eles tiverem um problema de design com o SI, a culpa será deles. "Diamante da morte" (herança repetida) é um excelente exemplo. O ataque ao MI não é pura hipocrisia, mas próximo.
precisa saber é o seguinte

4
Semanticamente, as interfaces são diferentes das classes abstratas, portanto, as interfaces do Java não são apenas uma solução técnica. A escolha entre definir uma interface ou uma classe abstrata é orientada pela semântica, não por considerações técnicas. Vamos imaginar uma interface "HasEngine": esse é um aspecto, um recurso, e pode ser aplicado / implementado por tipos muito diferentes (sejam classes ou classes abstratas); portanto, definiremos uma interface para isso, não uma classe abstrata.
Marek Stanley

2
@MarekStanley, você pode estar certo, mas eu gostaria de ter escolhido um exemplo melhor. Eu gosto de pensar nisso em termos de herdar uma interface versus herdar uma implementação. No C ++, você pode herdar a interface e a implementação juntas (herança pública) ou somente a implementação (herança privada). Em Java, você tem a opção de herdar apenas a interface, sem uma implementação.
Dima

43

Até onde pude testar, é muito importante adicionar o destruidor virtual. Estou usando objetos criados newe destruídos comdelete .

Se você não adicionar o destruidor virtual na interface, o destruidor da classe herdada não será chamado.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

Se você executar o código anterior sem virtual ~IBase() {};, verá que o destruidor Tester::~Tester()nunca é chamado.


3
A melhor resposta desta página, conforme ela é apresentada, fornecendo um exemplo prático e compilável. Felicidades!
Lumi

1
Testet :: ~ Tester () é executado somente quando o obj é "Declarado com Tester".
Alessandro L.

Na verdade, o destruidor do string privatename será chamado e, na memória, é para isso que ele será alocado. No que diz respeito ao tempo de execução, quando todos os membros concretos de uma classe são destruídos, o mesmo ocorre com a instância da classe. Eu tentei um experimento semelhante com uma classe Line que tinha duas estruturas Point e constatou que ambas as estruturas foram destruídas (Ha!) Após uma chamada de exclusão ou retorno da função abrangente. valgrind confirmou 0 vazamento.
Chris Reid

33

Minha resposta é basicamente a mesma que as outras, mas acho que há duas outras coisas importantes a fazer:

  1. Declare um destruidor virtual em sua interface ou faça um destruidor não virtual protegido para evitar comportamentos indefinidos se alguém tentar excluir um objeto do tipo IDemo.

  2. Use herança virtual para evitar problemas com herança múltipla. (Muitas vezes, há herança múltipla quando usamos interfaces.)

E como outras respostas:

  • Faça uma aula com métodos virtuais puros.
  • Use a interface criando outra classe que substitui esses métodos virtuais.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }

    Ou

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }

    E

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }

2
não há necessidade de herança virtual, pois você não possui nenhum membro de dados em uma interface.
Robocide

3
A herança virtual também é importante para os métodos. Sem ele, você terá ambigüidades com OverrideMe (), mesmo que uma das 'instâncias' seja pura virtual (tentei isso sozinho).
Knarf Navillus

5
@Avishay_ " não há necessidade de herança virtual, pois você não possui nenhum membro de dados em uma interface. " Errado.
precisa saber é o seguinte

Repare que herança virtual pode não funcionar em algumas versões gcc, como a versão 4.3.3 que é fornecido com WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu

-1 por ter um destruidor protegida não virtual, desculpe
lobo

10

No C ++ 11, você pode facilmente evitar completamente a herança:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

Nesse caso, uma interface possui semântica de referência, ou seja, você precisa garantir que o objeto sobreviva à interface (também é possível fazer interfaces com semântica de valores).

Esse tipo de interface tem seus prós e contras:

Finalmente, a herança é a raiz de todo mal no design de software complexo. Em Sean Parent, o valor da semântica e do polimorfismo baseado em conceitos (altamente recomendado, são explicadas melhores versões dessa técnica), o seguinte caso é estudado:

Digamos que eu tenha um aplicativo no qual trato de formas polimorficamente usando a MyShapeinterface:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

No seu aplicativo, você faz o mesmo com formas diferentes usando a YourShapeinterface:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

Agora diga que deseja usar algumas das formas que desenvolvi em seu aplicativo. Conceitualmente, nossas formas têm a mesma interface, mas para fazer com que minhas formas funcionem em seu aplicativo, você precisará estender minhas formas da seguinte maneira:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

Primeiro, modificar minhas formas pode não ser possível. Além disso, a herança múltipla leva o caminho para o código espaguete (imagine um terceiro projeto que use a TheirShapeinterface ... o que acontece se eles também chamarem sua função drawmy_draw ?).

Atualização: Há algumas novas referências sobre polimorfismo não baseado em herança:


5
A herança do TBH é muito mais clara do que a coisa do C ++ 11, que finge ser uma interface, mas é uma cola para vincular alguns projetos inconsistentes. O exemplo de formas é separado da realidade e a Circleclasse é um projeto pobre. Você deve usar Adapterpadrão nesses casos. Desculpe se vai parecer um pouco duro, mas tente usar alguma biblioteca da vida real como Qtantes de fazer julgamentos sobre herança. A herança torna a vida muito mais fácil.
doc

2
Não parece nada duro. Como o exemplo da forma é destacado da realidade? Você poderia dar um exemplo (talvez em ideone) de corrigir Circle usando o Adapterpadrão? Estou interessado em ver suas vantagens.
gnzlbg

OK, vou tentar encaixar nesta pequena caixa. Primeiro de tudo, você geralmente escolhe bibliotecas como "MyShape" antes de começar a escrever seu próprio aplicativo, para proteger seu trabalho. Caso contrário, como você poderia saber que Squareainda não está lá? Presciência? É por isso que está separado da realidade. E, na realidade, se você optar por confiar na biblioteca "MyShape", poderá adotar sua interface desde o início. No exemplo de formas, existem muitas bobagens (uma das quais é que você tem duas Circleestruturas), mas o adaptador seria algo parecido com isso -> ideone.com/UogjWk
doc

2
Não está separado da realidade então. Quando a empresa A compra a empresa B e deseja integrar a base de código da empresa B nas classes A, você tem duas bases de código completamente independentes. Imagine que cada um tenha uma hierarquia de formas de diferentes tipos. Você não pode combiná-los facilmente com herança e adicionar a empresa C e você terá uma enorme bagunça. Acho que você deveria assistir a essa conversa: youtube.com/watch?v=0I0FD3N5cgM Minha resposta é mais antiga, mas você verá as semelhanças. Você não precisa reimplementar tudo o tempo todo, pode fornecer uma implementação na interface e escolher uma função de membro, se disponível.
gnzlbg

1
Eu assisti parte do vídeo e isso está totalmente errado. Eu nunca uso dynamic_cast, exceto para fins de depuração. A transmissão dinâmica significa que há algo errado com seu design e os designs deste vídeo estão errados por design :). Guy até menciona Qt, mas mesmo aqui ele está errado - o QLayout não herda do QWidget nem o contrário!
doc

9

Todas as boas respostas acima. Uma coisa extra que você deve ter em mente - você também pode ter um destruidor virtual puro. A única diferença é que você ainda precisa implementá-lo.

Confuso?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

A principal razão pela qual você gostaria de fazer isso é se você deseja fornecer métodos de interface, como eu, mas faça a substituição deles opcional.

Para transformar a classe em uma classe de interface, é necessário um método virtual puro, mas todos os seus métodos virtuais têm implementações padrão; portanto, o único método que resta para tornar o virtual puro é o destruidor.

Reimplementar um destruidor na classe derivada não é grande coisa - eu sempre reimplemento um destruidor, virtual ou não, nas minhas classes derivadas.


4
Por que, ah, por que alguém iria querer tornar o dtor nesse caso virtual? Qual seria o ganho disso? Você acabou de forçar algo sobre as classes derivadas que elas provavelmente não precisam incluir - um dtor.
Johann Gerell 26/11/08

6
Atualizei minha resposta para responder sua pergunta. O destruidor virtual puro é uma maneira válida de obter (a única maneira de obter?) Uma classe de interface em que todos os métodos têm implementações padrão.
Rodyland

7

Se você estiver usando o compilador C ++ da Microsoft, poderá fazer o seguinte:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

Eu gosto dessa abordagem porque resulta em um código de interface muito menor e o tamanho do código gerado pode ser significativamente menor. O uso do novtable remove todas as referências ao ponteiro do vtable nessa classe, para que você nunca possa instancia-lo diretamente. Veja a documentação aqui - novtable .


4
Não entendo muito bem por que você usou novtableo padrãovirtual void Bar() = 0;
Flexo

2
É além disso (acabei de notar a falta = 0;que adicionei). Leia a documentação se não a entender.
Mark Ingram

Eu li sem o = 0;e assumi que era apenas uma maneira não-padrão de fazer exatamente o mesmo.
Flexo

4

Uma pequena adição ao que está escrito lá em cima:

Primeiro, verifique se o seu destruidor também é virtual

Segundo, você pode querer herdar virtualmente (em vez de normalmente) ao implementar, apenas por boas medidas.


Eu gosto de herança virtual porque, conceitualmente, significa que há apenas uma instância da classe herdada. É certo que a classe aqui não tem nenhum requisito de espaço, portanto pode ser supérflua. Eu não faço MI em C ++ há algum tempo, mas a herança não virtual não complicaria o upcasting?
Uri

Por que, oh, por que alguém iria querer tornar o dtor, neste caso, virtual? Qual seria o ganho disso? Você acabou de forçar algo nas classes derivadas que elas provavelmente não precisam incluir - um dtor.
Johann Gerell 26/11/08

2
Se houver uma situação que um objeto seria destruído através de um ponteiro para a interface, você deve se certificar de que o destruidor é virtual ...
Uri

Não há nada errado com um destruidor virtual puro. Não é necessário, mas não há nada de errado com isso. A implementação de um destruidor em uma classe derivada dificilmente representa um fardo enorme para o implementador dessa classe. Veja minha resposta abaixo para saber por que você faria isso.
Rodyland

+1 para herança virtual, porque com interfaces é mais provável que a classe derive a interface de dois ou mais caminhos. Opto por destruidores protegidos nas interfaces tho.
doc

4

Você também pode considerar as classes de contrato implementadas com o NVI (Non Virtual Interface Pattern). Por exemplo:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

Para outros leitores, este artigo do Dr. Dobbs "Conversations: Virtually Yours", de Jim Hyslop e Herb Sutter, elabora um pouco mais sobre por que alguém pode querer usar a NVI.
user2067021

E também este artigo "Virtualidade", de Herb Sutter.
precisa saber é o seguinte

1

Eu ainda sou novo no desenvolvimento de C ++. Comecei com o Visual Studio (VS).

No entanto, ninguém parece mencionar isso __interfaceno VS (.NET) . Estou não muito certo se esta é uma boa maneira de declarar uma interface. Mas parece fornecer uma aplicação adicional (mencionada nos documentos ). De modo que você não precisa especificar explicitamente o virtual TYPE Method() = 0;, pois ele será convertido automaticamente.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

No entanto, não o uso porque estou preocupado com a compatibilidade de compilação entre plataformas, pois ela está disponível apenas no .NET.

Se alguém tiver algo interessante sobre isso, compartilhe. :-)

Obrigado.


0

Embora seja verdade que esse virtualé o padrão de fato para definir uma interface, não vamos esquecer o padrão clássico do tipo C, que vem com um construtor em C ++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

Isso tem a vantagem de poder vincular novamente o tempo de execução de eventos sem precisar construir sua classe novamente (como o C ++ não possui uma sintaxe para alterar tipos polimórficos, é uma solução alternativa para as classes camaleão).

Dicas:

  • Você pode herdar disso como uma classe base (virtual e não virtual são permitidos) e preencher click o construtor do seu descendente.
  • Você pode ter o ponteiro de função como protectedmembro e ter umpublic referência e / ou getter.
  • Como mencionado acima, isso permite alternar a implementação em tempo de execução. Portanto, é uma maneira de gerenciar o estado também. Dependendo do número de ifalterações s vs. estado em seu código, isso pode ser mais rápido que switch()es ou ifs (espera-se uma recuperação em torno de 3-4if s, mas sempre meça primeiro.
  • Se você escolher std::function<>mais de ponteiros de função, você pode ser capaz de gerenciar todos os seus dados de objeto dentro IBase. A partir deste ponto, você pode ter esquemas de valores para IBase(por exemplo, std::vector<IBase>funcionará). Observe que isso pode ser mais lento, dependendo do seu compilador e código STL; também que as implementações atuais std::function<>tendem a ter uma sobrecarga quando comparadas com ponteiros de função ou até funções virtuais (isso pode mudar no futuro).

0

Aqui está a definição de abstract class padrão c ++

n4687

13.4.2

Uma classe abstrata é uma classe que pode ser usada apenas como classe base de alguma outra classe; nenhum objeto de uma classe abstrata pode ser criado, exceto como subobjetos de uma classe derivada dela. Uma classe é abstrata se tiver pelo menos uma função virtual pura.


-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

Resultado: área do retângulo: 35 área do triângulo: 17

Vimos como uma classe abstrata definiu uma interface em termos de getArea () e duas outras classes implementaram a mesma função, mas com algoritmos diferentes para calcular a área específica da forma.


5
Não é isso que é considerado uma interface! Essa é apenas uma classe base abstrata com um método que precisa ser substituído! As interfaces geralmente são objetos que contêm apenas definições de métodos - um "contrato" que outras classes precisam cumprir quando implementam a interface.
Guitarra # 26/14
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.