Faz sentido definir uma interface se eu já tiver uma classe abstrata?

12

Eu tenho uma classe com algumas funcionalidades padrão / compartilhadas. Eu uso abstract classpara isso:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

Como você pode ver, eu também tenho a interface ITypeNameMapper. Faz sentido definir essa interface se eu já tenho uma classe abstrata TypeNameMapperou abstract classé apenas o suficiente?

TypeDefinition neste exemplo mínimo também é abstrato.

Konrad
fonte
4
Apenas FYI - no OOD, verificar tipos no código é uma indicação de que sua hierarquia de classes não está correta - está dizendo para você criar classes derivadas do tipo que está verificando. Portanto, nesse caso, você deseja uma interface ITypeMapper que especifique um método Map () e implemente essa interface nas classes TypeDefinition e ClassDefinition. Dessa forma, seu ClassAssembler simplesmente percorre a lista de ITypeMappers, chamando Map () em cada um, e obtém a sequência desejada dos dois tipos, porque cada um tem sua própria implementação.
Rodney P. Barbati
1
O uso de uma classe base no lugar de uma interface, a menos que você precise, é chamado de "queimando a base". Se isso puder ser feito com uma interface, faça-o com uma interface. A classe abstrata se torna um extra útil. artima.com/intv/dotnet.html Especificamente, a comunicação remota exige que você derive de MarshalByRefObject, portanto, se você quiser ser remotado, não poderá derivar de mais nada.
22718 Ben Ben
@ RodneyP.Barbati não funcionará se eu quiser ter muitos mapeadores para o mesmo tipo que eu posso alternar, dependendo da situação #
311 Konrad
1
@ Ben queimando a base que é interessante. Obrigado pela jóia!
219 Konrad Konrad
@ Konrad Isso está incorreto - não há nada impedindo você de implementar a interface chamando outra implementação da interface; portanto, cada tipo pode ter uma implementação conectável que você expõe por meio da implementação no tipo.
Rodney P. Barbati

Respostas:

31

Sim, porque o C # não permite herança múltipla, exceto com interfaces.

Portanto, se eu tiver uma classe que seja um TypeNameMapper e SomethingelseMapper, eu posso fazer:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}
Ewan
fonte
4
@ Liath: A responsabilidade única é realmente um fator que contribui para o porquê da herança ser necessária. Considere três traços possíveis: ICanFly, ICanRun, ICanSwim. Você precisa criar 8 classes de criatura, uma para cada combinação de características (YYY, YYN, YNY, ..., NNY, NNN). O SRP determina que você implemente cada comportamento (voe, corra, nade) uma vez e depois os implemente reutilizável nas 8 criaturas. A composição seria ainda melhor aqui, mas, ignorando a composição por um segundo, você já deve ver a necessidade de herdar / implementar vários comportamentos reutilizáveis separados devido ao SRP.
Flater
4
@ Konrad é isso que eu quero dizer. Se você escreve a interface e nunca precisa dela, o custo é de 3 LoC. Se você não escrever a interface e achar que precisa, o custo poderá estar refatorando todo o aplicativo.
Ewan
3
(1) Implementar interfaces é herança? (2) A pergunta e a resposta devem ser qualificadas. "Depende." (3) A herança múltipla precisa de um adesivo de aviso. (4) Eu poderia mostrar a você os K de más interfacepráticas ilícitas formais . Implementações redundantes que devem estar na base - que estão evoluindo de forma independente! O meu favorito: classes de 1 método, cada uma implementando seu próprio método 1 interface, cada uma com uma única instância. ... etc, etc, e etc #
radarbob
3
Existe um coeficiente de atrito invisível no código. Você sente isso quando forçado a ler interfaces supérfluas. Em seguida, ele se mostra aquecendo como o ônibus espacial colidindo com a atmosfera enquanto uma implementação de interface luna a classe abstrata. É a Zona do Crepúsculo e este é o ônibus espacial Challenger. Aceitando meu destino, assisto o plasma furioso com uma admiração destemida e desapegada. O atrito sem remorso rasga a fachada da perfeição, depositando o casco quebrado na produção com um barulho estrondoso.
Radarbob
4
@radarbob Poetic. ^ _ ^ Eu concordo; Embora o custo das interfaces seja baixo, não existe. Não é muito necessário escrevê-las, mas em uma situação de depuração, há mais 1-2 etapas para chegar ao comportamento real, o que pode aumentar rapidamente quando você obtém meia dúzia de níveis de abstração. Em uma biblioteca, geralmente vale a pena. Mas em um aplicativo, a refatoração é melhor que a generalização prematura.
Errorsatz
1

Interfaces e classes abstratas servem a diferentes propósitos:

  • As interfaces definem APIs e pertencem aos clientes e não às implementações.
  • Se as classes compartilharem implementações, você poderá se beneficiar de uma classe abstrata .

No seu exemplo, interface ITypeNameMapperdefine as necessidades dos clientes e abstract class TypeNameMappernão agrega nenhum valor.

Geoffrey Hale
fonte
2
As classes abstratas também pertencem aos clientes. Não sei o que você quer dizer com isso. Tudo o que publicpertence ao cliente. TypeNameMapperestá agregando muito valor porque evita que o implementador escreva alguma lógica comum.
Konrad
2
Se uma interface é apresentada como interfaceou não é relevante apenas porque o C # proíbe a herança múltipla completa.
Deduplicator
0

Todo o conceito de interfaces foi criado para suportar uma família de classes com uma API compartilhada.

Isso está explicitamente dizendo que qualquer uso de uma interface implica que exista (ou se espera que exista) mais de uma implementação de sua especificação.

As estruturas de DI obscureceram as águas aqui, pois muitas delas exigem uma interface, mesmo que haja apenas uma implementação - para mim isso é uma sobrecarga irracional para o que, na maioria dos casos, é apenas um meio mais complexo e mais lento de chamar de novo, mas é o que é

A resposta está na própria pergunta. Se você tem uma classe abstrata, está se preparando para a criação de mais de uma classe derivada com uma API comum. Assim, o uso de uma interface é claramente indicado.

Rodney P. Barbati
fonte
2
"Se você tem uma classe abstrata, ... o uso de uma interface está claramente indicado." - Minha conclusão é o oposto: se você tem uma classe abstrata, use-a como se fosse uma interface (se o DI não jogar com ela, considere uma implementação diferente). Somente quando realmente não funcionar, crie a interface - você poderá fazê-lo a qualquer momento depois.
maaartinus 26/07
1
O mesmo vale para @maaartinus. E nem mesmo "... como se fosse uma interface". Os membros públicos de uma classe são uma interface. O mesmo vale para "você pode fazer isso a qualquer momento depois"; este vai para o coração dos fundamentos de design 101.
radarbob
@ maaartinus: Se alguém cria uma interface desde o início, mas usa referências do tipo de classe abstrata em todo o lugar, a existência da interface não ajuda em nada. Se, no entanto, o código do cliente usar o tipo de interface em vez do tipo de classe abstrata sempre que possível, isso facilitará bastante as coisas se for necessário produzir uma implementação que seja internamente muito diferente da classe abstrata. Alterar o código do cliente nesse ponto para usar a interface será muito mais penoso do que projetar o código do cliente para fazê-lo desde o início.
Supercat 26/07
1
@ supercat Eu poderia concordar assumindo que você está escrevendo uma biblioteca. Se for um aplicativo, não vejo problema em substituir todas as ocorrências de uma só vez. Meu Eclipse pode fazer isso (Java, não C #), mas se não puder, é como find ... -exec perl -pi -e ...e possivelmente consertar erros triviais de compilação. O que quero dizer
26418 maaartinus
A primeira frase estaria correta, se você marcasse interfacecomo código (você está falando sobre C # - interfaces, não como uma interface abstrata, como qualquer conjunto de classes / funções / etc), e a encerrasse "... mas ainda proibisse várias herança." Não há necessidade de interfaces se você tiver MI.
Deduplicator
0

Se queremos responder explicitamente à pergunta, o autor diz: "Faz sentido definir essa interface se eu já tenho uma classe abstrata TypeNameMapper ou classe abstrata é apenas o suficiente?"

A resposta é sim e não - Sim, você deve criar a interface mesmo que já tenha a classe base abstrata (porque você não deve estar se referindo à classe base abstrata em nenhum código do cliente) e não, porque você não deveria ter criado a classe base abstrata na ausência de uma interface.

É evidente que você não pensou bastante na API que está tentando criar. Crie a API primeiro e forneça uma implementação parcial, se desejado. O fato de sua classe base abstrata digitar o código de verificação é suficiente para dizer que não é a abstração correta.

Como na maioria das coisas em OOD, quando você está no ritmo e seu modelo de objetos é bem executado, seu código o informará sobre o que vem a seguir. Comece com uma interface, implemente essa interface nas classes que você precisa. Se você estiver escrevendo código semelhante, extraia-o em uma classe base abstrata - é a interface que é importante, a classe base abstrata é apenas um auxiliar e pode haver mais de uma.

Rodney P. Barbati
fonte
-1

Isso é bastante difícil de responder sem conhecer o restante do aplicativo.

Supondo que você esteja usando algum tipo de modelo de DI e tenha projetado seu código para ser o mais extensível possível (para que você possa adicionar novos TypeNameMapperfacilmente), eu diria que sim.

Razões para:

  • Você efetivamente o obtém gratuitamente, pois a classe base implementa o que interfacevocê não precisa se preocupar com isso em nenhuma implementação filho
  • A maioria das estruturas de IoC e bibliotecas de zombaria espera interfaces. Embora muitos deles trabalhem com classes abstratas, isso nem sempre é um dado, e eu acredito muito em seguir o mesmo caminho que os outros 99% dos usuários de uma biblioteca sempre que possível.

Razões contra:

  • Estritamente falando (como você apontou), você REALMENTE não precisa
  • Se class/ interfacemudar, haverá um pouco de sobrecarga adicional

Todas as coisas consideradas, eu diria que você deve criar o interface. As razões contra são bastante insignificantes, mas, embora seja possível usar resumos em zombaria e IoC, muitas vezes é muito mais fácil com interfaces. Em última análise, porém, eu não criticaria o código de um colega se ele seguisse o caminho contrário.

Liath
fonte