Quando pesquisado no Google, surgem muitas respostas para esse tópico. No entanto, não acho que nenhum deles faça um bom trabalho ilustrando a diferença entre esses dois recursos. Então, eu gostaria de tentar mais uma vez, especificamente ...
O que pode ser feito com tipos próprios e não com herança, e vice-versa?
Para mim, deve haver alguma diferença física quantificável entre os dois, caso contrário eles são apenas nominalmente diferentes.
Se o Traço A estender B ou os tipos próprios B, os dois não ilustram que ser B é um requisito? Onde está a diferença?
design-patterns
object-oriented-design
scala
Mark Canlas
fonte
fonte
Respostas:
Se a característica A estender B, a mistura de A fornecerá B precisamente mais o que A acrescenta ou estende. Por outro lado, se o traço A tiver uma auto-referência explicitamente digitada como B, a classe-mãe final também deverá misturar em B ou um tipo descendente de B (e misture-o primeiro , o que é importante).
Essa é a diferença mais importante. No primeiro caso, o tipo preciso de B é cristalizado no ponto A o estende. No segundo, o designer da classe pai decide qual versão de B é usada, no ponto em que a classe pai é composta.
Outra diferença é onde A e B fornecem métodos com o mesmo nome. Onde A estende B, o método de A substitui B. Onde A é misturado após B, o método de A simplesmente vence.
A auto-referência digitada oferece muito mais liberdade; o acoplamento entre A e B está solto.
ATUALIZAR:
Como você não tem certeza sobre o benefício dessas diferenças ...
Se você usa herança direta, cria a característica A que é B + A. Você definiu o relacionamento em pedra.
Se você usar uma auto-referência digitada, qualquer pessoa que queira usar sua característica A na classe C poderá
E esse não é o limite de suas opções, devido ao modo como o Scala permite instanciar uma característica diretamente com um bloco de código como seu construtor.
Quanto à diferença entre a vitória do método de A , porque A é o último da mistura, comparado a A que estende B, considere isso ...
Onde você mescla uma sequência de características, sempre que o método
foo()
é chamado, o compilador vai para a última característica misturada para procurar efoo()
, se não for encontrado, ele percorre a sequência para a esquerda até encontrar uma característica que implementefoo()
e use naquela. A também tem a opção de chamarsuper.foo()
, que também percorre a sequência para a esquerda até encontrar uma implementação e assim por diante.Portanto, se A tem uma auto-referência digitada para B e o escritor de A sabe que B implementa
foo()
, A pode chamarsuper.foo()
sabendo que, se nada mais fornecerfoo()
, B fará. No entanto, o criador da classe C tem a opção de remover qualquer outra característica na qual implementafoo()
, e A obterá isso.Novamente, isso é muito mais poderoso e menos limitativo do que A estendendo B e chamando diretamente a versão de B de
foo()
.fonte
Eu tenho algum código que ilustra algumas das diferenças de visibilidade e implementações "padrão" ao estender vs definir um tipo próprio. Ele não ilustra nenhuma das partes já discutidas sobre como as colisões de nomes reais são resolvidas, mas concentra-se no que é possível e não é possível fazer.
Uma diferença importante da IMO é que
A1
ela não expõe que é necessáriaB
para qualquer coisa que apenas a vejaA1
(como ilustrado nas partes pré-fabricadas). O único código que realmente verá que uma especialização específicaB
é usada é o código que sabe explicitamente sobre o tipo composto (comoThink*{X,Y}
).Outro ponto é que
A2
(com extensão) será realmente usadoB
se nada for especificado, enquantoA1
(o tipo próprio) não diz que será usado, aB
menos que seja substituído, um B concreto deve ser explicitamente fornecido quando os objetos forem instanciados.fonte