É verdade que substituir métodos concretos é um cheiro de código? Porque eu acho que se você precisar substituir métodos concretos:
public class A{
public void a(){
}
}
public class B extends A{
@Override
public void a(){
}
}
pode ser reescrito como
public interface A{
public void a();
}
public class ConcreteA implements A{
public void a();
}
public class B implements A{
public void a(){
}
}
e se B quiser reutilizar um () em A, ele pode ser reescrito como:
public class B implements A{
public ConcreteA concreteA;
public void a(){
concreteA.a();
}
}
que não requer herança para substituir o método, isso é verdade?
virtual
palavra-chave para dar suporte a substituições. Portanto, a questão é tornada mais (ou menos) ambígua pelos detalhes da linguagem.final
modificador existe exatamente para situações como estas. Se o comportamento do método não for projetado para ser substituído, marque-o comofinal
. Caso contrário, espere que os desenvolvedores optem por substituí-lo.Respostas:
Não, não é um cheiro de código.
Cabe às responsabilidades de cada classe considerar cuidadosamente se a subclasse é apropriada e quais métodos podem ser substituídos .
A classe pode definir a si mesma ou qualquer método como final ou pode colocar restrições (modificadores de visibilidade, construtores disponíveis) sobre como e onde está subclassificada.
O caso usual para substituir métodos é uma implementação padrão na classe base que pode ser personalizada ou otimizada na subclasse (especialmente no Java 8 com o advento dos métodos padrão nas interfaces).
Uma alternativa para substituir o comportamento é o Padrão de Estratégia , onde o comportamento é abstraído como uma interface e a implementação pode ser definida na classe.
fonte
A
implementos não deveriamDescriptionProvider
?A
poderia implementarDescriptionProvider
e, portanto, ser o provedor de descrição padrão. Mas a idéia aqui é a separação de preocupações:A
precisa da descrição (algumas outras classes também podem), enquanto outras implementações as fornecem.Sim, em geral, substituir métodos concretos é um cheiro de código.
Como o método base tem um comportamento associado a ele que os desenvolvedores geralmente respeitam, alterações que levarão a erros quando sua implementação fizer algo diferente. Pior, se eles mudarem o comportamento, sua implementação correta anteriormente pode exacerbar o problema. Em geral, é bastante difícil respeitar o Princípio da Substituição de Liskov por métodos concretos não triviais.
Agora, há casos em que isso pode ser feito e é bom. Métodos semi-triviais podem ser substituídos com segurança. Métodos de base que existem apenas como um "default sane" tipo de comportamento que é significou ser substituído, têm bons usos.
Portanto, isso me parece um cheiro - às vezes bom, geralmente ruim, dê uma olhada para ter certeza.
fonte
Number
classe com umincrement()
método que define o valor como seu próximo valor válido. Se você a subclassificar emEvenNumber
whereincrement()
adiciona duas em vez de uma (e o setter reforça a uniformidade), a primeira proposta do OP exige implementações duplicadas de todos os métodos da classe e a segunda exige a escrita de métodos de invólucro para chamar os métodos no membro. Parte do propósito da herança é que as classes filhas deixem claro como elas diferem dos pais, fornecendo apenas os métodos que precisam ser diferentes.Se puder ser reescrito dessa maneira, significa que você tem controle sobre as duas classes. Mas então você sabe se você projetou a classe A para ser derivada dessa maneira e, se criou, não é um cheiro de código.
Se, por outro lado, você não tiver controle sobre a classe base, não poderá reescrever dessa maneira. Portanto, ou a classe foi projetada para ser derivada dessa maneira; nesse caso, vá em frente, não é um cheiro de código, ou não era, nesse caso, você tem duas opções: procure outra solução completamente ou vá em frente de qualquer maneira , porque o trabalho supera a pureza do design.
fonte
abstract
; mas há muitos casos em que não precisa ser. 2) se deve não ser substituído, deve serfinal
(não virtual). Mas ainda existem muitos casos em que os métodos podem ser substituídos. 3) se o desenvolvedor a jusante não ler os documentos, eles dispararão no próprio pé, mas farão isso independentemente das medidas que você colocar em prática.Primeiro, deixe-me salientar que esta questão é apenas aplicável em certos sistemas de digitação. Por exemplo, em tipagem estrutural e tipagem pato sistemas - uma classe simplesmente ter um método com o mesmo nome e assinatura que significa que foi classe tipo compatível (pelo menos para que o método em particular, no caso de tipagem pato).
Isso é (obviamente) muito diferente do domínio das linguagens estaticamente tipadas , como Java, C ++, etc. Assim como a natureza da digitação Strong , usada em Java e C ++, além de C # e outras.
Suponho que você seja proveniente de Java, onde os métodos devem ser explicitamente marcados como finais se o designer do contrato pretender que eles não sejam substituídos. No entanto, em idiomas como C #, o oposto é o padrão. Os métodos são (sem nenhuma palavra-chave especial de decoração) " selados " (versão do C #
final
) e devem ser explicitamente declarados comovirtual
se eles pudessem ser substituídos.Se uma API ou biblioteca foi projetada nessas linguagens com um método concreto que pode ser substituído, então (pelo menos em alguns casos) faz sentido que ela seja substituída. Portanto, não é um cheiro de código substituir um
final
método não selado / virtual / não concreto. (Isso pressupõe, é claro, que os designers da API estejam seguindo as convenções e marcando comovirtual
(C #) ou comofinal
(Java) os métodos apropriados para o que estão projetando.)Veja também
fonte
Sim, é porque pode levar a chamar super anti-padrão. Também pode desnaturar o objetivo inicial do método, introduzir efeitos colaterais (=> bugs) e fazer com que os testes falhem. Você usaria uma classe em que os testes de unidade são vermelhos (falsos)? Não vou.
Vou ainda mais longe, dizendo que o cheiro inicial do código é quando um método não é
abstract
nemfinal
. Porque permite alterar (substituindo) o comportamento de uma entidade construída (esse comportamento pode ser perdido ou alterado). Comabstract
efinal
a intenção é inequívoca:A maneira mais segura de adicionar comportamento a uma classe existente é o que seu último exemplo mostra: usando o padrão decorador.
fonte
Object.toString()
em Java ... Se fosse assimabstract
, muitas coisas padrão não funcionariam e o código nem seria compilado quando alguém não substituísse otoString()
método! Se assim fossefinal
, as coisas seriam ainda piores - sem capacidade de permitir que objetos sejam convertidos em string, exceto pelo modo Java. Como a resposta de @Peter Walser fala, implementações padrão (comoObject.toString()
) são importantes. Especialmente nos métodos padrão do Java 8.toString()
fosseabstract
oufinal
seria uma dor. Minha opinião pessoal é que esse método está quebrado e seria melhor não tê-lo (como equals e hashCode ). O objetivo inicial dos padrões Java é permitir a evolução da API com retrocompatibilidade.