Quais são os padrões de problemas típicos que se beneficiam de um código projetado para fazer uso pesado de várias heranças?
Este é apenas um exemplo, mas acho inestimável para melhorar a segurança e atenuar as tentações de aplicar mudanças em cascata em todos os chamadores ou subclasses.
Onde eu achei várias heranças incrivelmente úteis, mesmo para as interfaces mais abstratas e sem estado, é o idioma da interface não virtual (NVI) no C ++.
Eles nem são realmente classes básicas abstratas, mas interfaces que têm apenas um pouco de implementação para impor os aspectos universais de seus contratos, pois não estão realmente estreitando a generalidade do contrato, mas sim aplicando-o melhor. .
Exemplo simples (alguns podem verificar se um identificador de arquivo passado está aberto ou algo parecido):
// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
// Pre: x should always be greater than or equal to zero.
void f(int x) /*final*/
{
// Make sure x is actually greater than or equal to zero
// to meet the necessary pre-conditions of this function.
assert(x >= 0);
// Call the overridden function in the subtype.
f_impl(x);
}
protected:
// Overridden by a boatload of subtypes which implement
// this non-virtual interface.
virtual void f_impl(int x) = 0;
};
Nesse caso, talvez f
seja chamado por mil lugares na base de código, enquantof_impl
é substituído por cem subclasses.
Seria difícil fazer esse tipo de verificação de segurança em todos os 1000 locais que ligam f
ou nos 100 locais que substituemf_impl
.
Ao fazer esse ponto de entrada para a funcionalidade não virtual, ele me fornece um lugar central para executar essa verificação. E essa verificação não está reduzindo a abstração nem um pouco, pois está simplesmente afirmando uma pré-condição necessária para chamar essa função. Em certo sentido, é possível reforçar o contrato fornecido pela interface e aliviar o ônus de verificar a x
entrada para garantir que ela esteja em conformidade com as pré-condições válidas em todos os 100 locais que a substituem.
É algo que eu gostaria que toda linguagem tivesse, e também desejasse, mesmo em C ++, que fosse um pouco mais de um conceito nativo (por exemplo: não exigindo que definíssemos uma função separada para substituir).
Isso é extremamente útil se você não fez isso assert
com antecedência e percebeu que precisava disso mais tarde, quando alguns lugares aleatórios na base de código encontravam valores negativos sendo passados para f
.