Parece que o Java teve o poder de declarar classes não deriváveis por eras, e agora o C ++ também. No entanto, à luz do princípio Abrir / Fechar no SOLID, por que isso seria útil? Para mim, a final
palavra - chave soa exatamente como friend
- é legal, mas se você a estiver usando, provavelmente o design está errado. Forneça alguns exemplos em que uma classe não derivável faria parte de uma ótima arquitetura ou padrão de design.
54
final
? Muitas pessoas (inclusive eu) acham que é um bom design criar todas as classes não abstratasfinal
.final
.Respostas:
final
expressa intenção . Diz ao usuário de uma classe, método ou variável "Este elemento não deve ser alterado e, se você quiser alterá-lo, não entendeu o design existente."Isso é importante porque a arquitetura do programa seria muito, muito difícil se você tivesse que antecipar que todas as classes e métodos que você escreve podem ser alterados para fazer algo completamente diferente por uma subclasse. É muito melhor decidir antecipadamente quais elementos devem ser alteráveis e quais não são, e impor a imutabilidade via
final
.Você também pode fazer isso por meio de comentários e documentos de arquitetura, mas é sempre melhor permitir que o compilador faça o que for possível do que esperar que os usuários futuros leiam e obedeçam à documentação.
fonte
Evita o problema frágil da classe base . Toda classe vem com um conjunto de garantias e invariáveis implícitas ou explícitas. O Princípio da Substituição de Liskov exige que todos os subtipos dessa classe também forneçam todas essas garantias. No entanto, é realmente fácil violar isso se não o usarmos
final
. Por exemplo, vamos ter um verificador de senha:Se permitirmos que essa classe seja substituída, uma implementação pode bloquear todos, outra pode dar acesso a todos:
Isso geralmente não é bom, já que as subclasses agora têm um comportamento muito incompatível com o original. Se realmente pretendemos que a classe seja estendida com outro comportamento, uma Cadeia de Responsabilidade seria melhor:
O problema se torna mais aparente quando uma classe mais complicada chama seus próprios métodos, e esses métodos podem ser substituídos. Às vezes, encontro isso ao imprimir bastante uma estrutura de dados ou ao escrever HTML. Cada método é responsável por algum widget.
Agora, crio uma subclasse que adiciona um pouco mais de estilo:
Agora, ignorando por um momento que essa não é uma maneira muito boa de gerar páginas HTML, o que acontece se eu quiser alterar o layout novamente? Eu teria que criar uma
SpiffyPage
subclasse que de alguma forma envolvesse esse conteúdo. O que podemos ver aqui é uma aplicação acidental do padrão do método de modelo. Os métodos de modelo são pontos de extensão bem definidos em uma classe base que devem ser substituídos.E o que acontece se a classe base mudar? Se o conteúdo HTML mudar muito, isso pode interromper o layout fornecido pelas subclasses. Portanto, não é realmente seguro alterar a classe base posteriormente. Isso não é aparente se todas as suas classes estiverem no mesmo projeto, mas muito perceptível se a classe base fizer parte de algum software publicado que outras pessoas desenvolvam.
Se essa estratégia de extensão foi planejada, poderíamos ter permitido ao usuário trocar a maneira como cada parte é gerada. Ou, pode haver uma estratégia para cada bloco que possa ser fornecida externamente. Ou, podemos aninhar decoradores. Isso seria equivalente ao código acima, mas muito mais explícito e muito mais flexível:
(O
top
parâmetro adicional é necessário para garantir que as chamadaswriteMainContent
passem pela parte superior da cadeia do decorador. Isso simula um recurso de subclasse denominado recursão aberta .)Se temos vários decoradores, agora podemos misturá-los mais livremente.
Muito mais frequentemente do que o desejo de adaptar levemente a funcionalidade existente, é o desejo de reutilizar parte de uma classe existente. Eu vi um caso em que alguém queria uma aula em que você pudesse adicionar itens e repetir todos eles. A solução correta teria sido:
Em vez disso, eles criaram uma subclasse:
De repente, isso significa que toda a interface do
ArrayList
se tornou parte da nossa interface. Os usuários podemremove()
coisas, ouget()
coisas em índices específicos. Isso foi planejado dessa maneira? ESTÁ BEM. Mas, muitas vezes, não pensamos cuidadosamente em todas as consequências.Portanto, é aconselhável
extend
uma aula sem reflexão cuidadosa.final
exceto, se você pretende que qualquer método seja substituído.Existem muitos exemplos em que essa "regra" precisa ser quebrada, mas geralmente o orienta para um design bom e flexível e evita bugs devido a alterações não intencionais nas classes base (ou usos não intencionais da subclasse como uma instância da classe base) )
Alguns idiomas têm mecanismos de aplicação mais rigorosos:
virtual
fonte
Estou surpreso que ninguém ainda tenha mencionado o Effective Java, 2nd Edition, de Joshua Bloch (que deveria ser leitura obrigatória para todos os desenvolvedores Java). O item 17 do livro discute isso em detalhes e tem o título: " Design e documento da herança ou então a proíbe ".
Não vou repetir todos os bons conselhos do livro, mas esses parágrafos em particular parecem relevantes:
fonte
Um dos motivos
final
é útil: garante que você não possa subclassificar uma classe de maneira a violar o contrato da classe pai. Essa subclasse seria uma violação do SOLID (acima de tudo "L") e criar uma classefinal
evita isso.Um exemplo típico é tornar impossível subclassificar uma classe imutável de uma maneira que tornaria a subclasse mutável. Em certos casos, essa mudança de comportamento pode levar a efeitos muito surpreendentes, por exemplo, quando você usa algo como chaves em um mapa, pensando que a chave é imutável, enquanto na realidade você está usando uma subclasse que é mutável.
Em Java, muitos problemas interessantes de segurança podem ser introduzidos se você puder subclassificar
String
e torná-lo mutável (ou fazer com que seja chamada de volta para casa quando alguém chama seus métodos, possivelmente retirando dados confidenciais do sistema) à medida que esses objetos são passados em torno de algum código interno relacionado ao carregamento e segurança da classe.Às vezes, final também é útil para evitar erros simples, como reutilizar a mesma variável para duas coisas dentro de um método, etc. No Scala, você é incentivado a usar apenas o
val
que corresponde aproximadamente às variáveis finais em Java e, na verdade, qualquer uso de um método.var
ou variável não final é encarada com suspeita.Finalmente, os compiladores podem, pelo menos em teoria, realizar algumas otimizações extras quando sabem que uma classe ou método é final, pois quando você chama um método em uma classe final, sabe exatamente qual método será chamado e não precisa ir. através da tabela de método virtual para verificar a herança.
fonte
SecurityManager
. A maioria dos programas não o utiliza, mas também não executa nenhum código não confiável. +++ Você deve assumir que as strings são imutáveis, caso contrário, você obtém zero de segurança e vários bugs arbitrários como bônus. Qualquer programador que suponha que as seqüências Java possam alterar o código produtivo é certamente insano.A segunda razão é desempenho . A primeira razão é porque algumas classes têm comportamentos ou estados importantes que não devem ser alterados para permitir que o sistema funcione. Por exemplo, se eu tenho uma classe "PasswordCheck" e para criar essa classe, contratei uma equipe de especialistas em segurança e essa classe se comunica com centenas de caixas eletrônicos com procedimentos bem estudados e definidos. Permitir que um novo contratado recém-saído da universidade faça uma classe "TrustMePasswordCheck" que estenda a classe acima pode ser muito prejudicial para o meu sistema; esses métodos não devem ser substituídos, é isso.
fonte
Quando eu precisar de uma aula, eu vou escrever uma aula. Se eu não precisar de subclasses, não me importo com subclasses. Certifico-me de que minha classe se comporte da maneira pretendida, e os locais onde eu uso a classe pressupõem que a classe se comporte da maneira pretendida.
Se alguém quiser subclassificar minha classe, quero negar totalmente qualquer responsabilidade pelo que acontece. Consigo isso tornando a aula "final". Se você deseja subclassificar, lembre-se de que eu não levei em consideração a subclasse enquanto escrevia a classe. Portanto, você deve pegar o código-fonte da classe, remover o "final" e, a partir de então, tudo o que acontecer é de sua inteira responsabilidade .
Você acha que isso "não é orientado a objetos"? Eu fui pago para fazer uma aula que faz o que deveria fazer. Ninguém me pagou por fazer uma aula que poderia ser subclassificada. Se você é pago para tornar minha classe reutilizável, você pode fazê-lo. Comece removendo a palavra-chave "final".
(Além disso, "final" geralmente permite otimizações substanciais. Por exemplo, em Swift "final" em uma classe pública ou em um método de uma classe pública, significa que o compilador pode conhecer completamente o código que uma chamada de método executará, e pode substituir o envio dinâmico pelo estático (pequeno benefício) e, muitas vezes, o estático pelo inlining (possivelmente enorme).
adelphus: O que é tão difícil de entender sobre "se você deseja subclasse, pegue o código-fonte, remova o 'final' e é sua responsabilidade"? "final" é igual a "aviso justo".
E eu estou não pago para tornar o código reutilizável. Sou pago para escrever um código que faça o que deve fazer. Se sou pago para criar dois bits de código semelhantes, extraio as partes comuns porque é mais barato e não sou pago para perder meu tempo. Tornar reutilizável o código que não é reutilizado é uma perda de tempo.
M4ks: Você sempre torna tudo privado que não deve ser acessado de fora. Novamente, se você deseja subclasse, pega o código fonte, muda as coisas para "protegido", se necessário, e assume a responsabilidade pelo que faz. Se você acha que precisa acessar as coisas que marquei como particulares, é melhor saber o que está fazendo.
Ambos: A subclassificação é uma parte minúscula do código de reutilização. Criar blocos de construção que podem ser adaptados sem subclassificação é muito mais poderoso e se beneficia enormemente de "final", porque os usuários dos blocos podem confiar no que recebem.
fonte
Vamos imaginar que o SDK de uma plataforma envie a seguinte classe:
Um aplicativo inclui subclasses dessa classe:
Tudo está bem, mas alguém que trabalha no SDK decide que passar um método para ele
get
é bobo e melhora a interface, garantindo a compatibilidade com versões anteriores.Tudo parece bem, até que o aplicativo acima seja recompilado com o novo SDK. De repente, o método get anulado não está mais sendo chamado e a solicitação não está sendo contada.
Isso é chamado de problema frágil da classe base, porque uma mudança aparentemente inócua resulta em uma quebra de subclasse. A qualquer momento, a alteração na qual os métodos são chamados dentro da classe pode causar uma subclasse. Isso tende a significar que quase qualquer alteração pode causar uma subclasse.
Final impede que alguém subclasse sua classe. Dessa forma, quais métodos dentro da classe podem ser alterados sem a preocupação de que alguém dependa exatamente de quais chamadas de método são feitas.
fonte
Final significa efetivamente que sua classe pode ser alterada com segurança no futuro sem afetar nenhuma classe baseada em herança downstream (porque não há nenhuma) ou qualquer problema relacionado à segurança de thread da classe (acho que há casos em que a palavra-chave final em um campo impede alguns high-jinx baseados em threads).
Final significa que você é livre para mudar a forma como sua classe funciona, sem que mudanças indesejadas de comportamento se arrastem para o código de outras pessoas que se baseia no seu como base.
Como exemplo, escrevo uma classe chamada HobbitKiller, o que é ótimo, porque todos os hobbits são complicados e provavelmente devem morrer. Risque isso, todos eles definitivamente precisam morrer.
Você usa isso como uma classe base e adiciona um novo método impressionante para usar um lança-chamas, mas usa minha classe como base porque eu tenho um ótimo método para direcionar os hobbits (além de serem traiçoeiros, eles são rápidos), o que você usa para ajudar a apontar seu lança-chamas.
Três meses depois, altero a implementação do meu método de segmentação. Agora, em algum momento futuro, quando você atualizar sua biblioteca, sem o seu conhecimento, a implementação real do tempo de execução da sua classe mudou fundamentalmente devido a uma alteração no método da superclasse da qual você depende (e geralmente não controla).
Para que eu seja um desenvolvedor consciente e garanta uma morte suave do hobbit no futuro usando minha classe, tenho que ter muito, muito cuidado com as alterações que fizer em qualquer classe que possa ser estendida.
Ao remover a capacidade de estender, exceto nos casos em que pretendo especificamente estender a classe, economizo para mim (e espero que outros) muitas dores de cabeça.
fonte
final
O uso de
final
não é de forma alguma uma violação dos princípios do SOLID. Infelizmente, é extremamente comum interpretar o Princípio Aberto / Fechado ("entidades de software devem estar abertas para extensão, mas fechadas para modificação") como significando "em vez de modificar uma classe, subclassificá-la e adicionar novos recursos". Isso não é o que originalmente significava, e geralmente não é a melhor abordagem para alcançar seus objetivos.A melhor maneira de cumprir o OCP é projetar pontos de extensão em uma classe, fornecendo especificamente comportamentos abstratos que são parametrizados ao injetar uma dependência no objeto (por exemplo, usando o padrão de design da estratégia). Esses comportamentos devem ser projetados para usar uma interface para que novas implementações não confiem na herança.
Outra abordagem é implementar sua classe com sua API pública como uma classe abstrata (ou interface). Em seguida, você pode produzir uma implementação totalmente nova que pode ser conectada aos mesmos clientes. Se sua nova interface exigir um comportamento amplamente semelhante ao original, você pode:
fonte
Para mim, é uma questão de design.
Vamos supor que eu tenha um programa que calcula salários para os funcionários. Se eu tiver uma turma que retorne o número de dias úteis entre duas datas com base no país (uma turma para cada país), colocarei essa final e fornecerei um método para que toda empresa forneça um dia gratuito apenas para seus calendários.
Por quê? Simples. Digamos que um desenvolvedor queira herdar a classe base WorkingDaysUSA de uma classe WorkingDaysUSAmyCompany e modifique-a para refletir que sua empresa será fechada por greve / manutenção / por qualquer motivo no dia 2 de março.
Os cálculos para pedidos e entrega de clientes refletirão o atraso e funcionarão adequadamente quando em tempo de execução eles chamam WorkingDaysUSAmyCompany.getWorkingDays (), mas o que acontece quando calculo o tempo de férias? Devo adicionar o dia 2 de março como feriado para todos? Não. Mas como o programador usou herança e eu não protegi a classe, isso pode levar a uma confusão.
Ou digamos que eles herdam e modificam a classe para refletir que esta empresa não trabalha aos sábados, onde no país trabalha meio período no sábado. Em seguida, um terremoto, crise de eletricidade ou alguma circunstância faz o presidente declarar três dias não úteis, como aconteceu recentemente na Venezuela. Se o método da classe herdada já subtraído todos os sábados, minhas modificações na classe original podem levar à subtração duas vezes no mesmo dia. Eu precisaria ir a cada subclasse de cada cliente e verificar se todas as alterações são compatíveis.
Solução? Torne a aula final e forneça um método addFreeDay (companyID minhaempresa, Date freeDay). Dessa forma, você tem certeza de que, quando chama uma classe WorkingDaysCountry, é sua classe principal e não uma subclasse
fonte