Por que usar 'final' em uma aula é realmente tão ruim?


34

Estou refatorando um site herdado do PHP OOP.

Estou tão tentado a começar a usar 'final' nas aulas para " make it explicit that the class is currently not extended by anything". Isso pode economizar muito tempo se eu for para uma classe e me perguntar se posso renomear / excluir / modificar uma protectedpropriedade ou método. Se eu realmente quero estender uma classe, basta remover a palavra-chave final para desbloqueá-la e estendê-la.

Ou seja, se eu for para uma turma que não tem turmas filhas, posso registrar esse conhecimento marcando a turma como final. Na próxima vez que eu chegar lá, não precisaria pesquisar novamente a base de código para ver se ela tem filhos. Economizando tempo durante refatorações.

Tudo parece uma idéia sensata para economizar tempo ... mas muitas vezes li que as aulas deveriam ser feitas apenas 'finais' em ocasiões raras / especiais.

Talvez estrague a criação do objeto Mock ou tenha outros efeitos colaterais nos quais não estou pensando.

o que estou perdendo?


3
Se você tem controle total do seu código, suponho que não seja terrível, mas um pouco do lado do TOC. E se você perder uma aula (por exemplo, ela não tem filhos, mas não é final)? É melhor deixar uma ferramenta escanear o código e dizer se alguma classe tem filhos. Assim que você empacota isso como uma biblioteca e o entrega a outra pessoa, lidar com 'final' se torna uma dor.

Por exemplo, o MbUnit é uma estrutura aberta para testes de unidade .Net e o MsTest não é (surpresa surpresa). Como resultado, você TEM QUE desembolsar 5k para o MSFT apenas porque deseja executar alguns testes da maneira MSFT. Outros corredores de teste não podem executar a dll de teste criada com o MsTest - tudo por causa das classes finais usadas pelo MSFT.
Job

3
Alguns post sobre o tema: Classes Finais: Open de Extensão, Fechado para Inheritance (maio de 2014; por Mathias Verraes)
hakre

Bom artigo. Fala meus pensamentos sobre isso. Eu não sabia dessas anotações, o que foi útil. Felicidades.
JW01

"O PHP 5 introduz a palavra-chave final, o que impede que as classes filhas substituam um método prefixando a definição por final. Se a própria classe estiver sendo definida final, ela não poderá ser estendida." php.net/manual/en/language.oop5.final.php Não é nada ruim.
Preto

Respostas:


54

Eu sempre li que as aulas só devem ser feitas 'finais' em ocasiões raras / especiais.

Quem escreveu isso está errado. Use finalliberalmente, não há nada de errado nisso. Ele documenta que uma classe não foi projetada com herança em mente, e isso geralmente é verdade para todas as classes por padrão: projetar uma classe que possa ser herdada de forma significativa requer mais do que apenas remover um finalespecificador; é preciso muito cuidado.

Portanto, usar finalpor padrão não é ruim. De fato, muitas pessoas propõem que esse seja o padrão, por exemplo, Jon Skeet .

Talvez estrague a criação de objetos simulados ...

Isso é realmente uma ressalva, mas você sempre pode recorrer a interfaces se precisar zombar de suas classes. Certamente isso é superior a tornar todas as classes abertas à herança apenas com a finalidade de zombar.


1
1+: uma vez que estraga a zombaria; considere criar interfaces ou classes abstratas para cada classe "final".
Spoike 14/07/11

Bem, isso coloca uma chave inglesa nos trabalhos. Essa chegada tardia contradiz todas as outras respostas. Agora não sei em que acreditar: o. (Desmarcado a minha resposta aceita e adiar decisões durante re-trial)
JW01

1
Você pode considerar a própria API Java e com que frequência encontrará o uso da palavra-chave 'final'. De fato, não é usado com muita frequência. Ele é usado nos casos em que o desempenho pode ficar ruim devido à ligação dinâmica que ocorre quando os métodos "virtuais" são chamados (em Java, todos os métodos são virtuais se não forem declarados como 'finais' ou 'privados') ou ao introduzir customizações. subclasses de uma classe de API Java podem surgir problemas de consistência, como a subclasse 'java.lang.String'. Uma Java String é um objeto de valor por definição e não deve alterar seu valor posteriormente. Uma subclasse pode quebrar essa regra.
Jonny Dee

5
@ Jonny A maior parte da API Java é mal projetada. Lembre-se de que a maior parte tem mais de dez anos e muitas práticas recomendadas foram desenvolvidas nesse meio tempo. Mas não aceite minha palavra: os próprios engenheiros de Java dizem isso, antes de tudo Josh Bloch, que escreveu em detalhes sobre isso, por exemplo, em Java eficaz . Tenha certeza de que, se a API Java fosse desenvolvida hoje, ela pareceria muito diferente e finaldesempenharia um papel muito maior.
Konrad Rudolph

1
Ainda não estou convencido de que usar 'final' com mais frequência tenha se tornado uma prática recomendada. Na minha opinião, o 'final' realmente deve ser usado apenas se os requisitos de desempenho o exigirem, ou se você deve garantir que a consistência não possa ser quebrada pelas subclasses. Em todos os outros casos, a documentação necessária deve entrar na documentação (que precisa ser escrita de qualquer maneira) e não no código-fonte como palavras-chave que reforçam certos padrões de uso. Para mim, isso é meio que brincar de deus. Usar anotações seria uma solução melhor. Pode-se adicionar uma anotação '@final' e o compilador pode emitir um aviso.
Jonny Dee

8

Se você deseja deixar uma nota para si mesmo de que uma classe não tem subclasses, faça-o de qualquer maneira e use um comentário, é para isso que servem. A palavra-chave "final" não é um comentário, e usar palavras-chave no idioma apenas para sinalizar algo para você (e somente você saberia o que isso significa) é uma má idéia.


17
Isso é completamente falso! “Usar palavras-chave do idioma apenas para sinalizar algo para você [...] é uma má idéia.” - Pelo contrário, é exatamente para isso que as palavras-chave do idioma existem. Sua advertência provisória, “e somente você saberia o que isso significa”, está obviamente correta, mas não se aplica aqui; o OP está usando finalconforme o esperado. Não há nada de errado nisso. E usar um recurso de idioma para impor uma restrição é sempre superior a usar um comentário.
21911 Konrad Rudolph

8
@ Konrad: Exceto finaldeve indicar "Nenhuma subclasse desta classe deve ser criada" (por razões legais ou algo assim), não "esta classe atualmente não tem filhos, por isso ainda estou seguro de mexer com seus membros protegidos". A intenção de finalé a própria antítese de "livremente editável", e uma finalclasse nem deveria ter nenhum protectedmembro!
SF.

3
@ Konrad Rudolph Não é minúsculo. Se outras pessoas mais tarde quiserem estender suas aulas, há uma grande diferença entre um comentário que diz "não estendido" e uma palavra-chave que diz "pode ​​não estender".
Jeremy

2
@ Konrad Rudolph Parece uma ótima opinião para fazer uma pergunta sobre o design da linguagem OOP. Mas sabemos como o PHP funciona, e adota a outra abordagem de permitir a extensão, a menos que seja selada. Fingir que não há problema em confundir palavras-chave porque "é assim que deve ser" é apenas defensivo.
Jeremy

3
@ Jeremy Não estou combinando palavras-chave. A palavra-chave finalsignifica "essa classe não deve ser estendida [por enquanto]". Nada mais, nada menos. Se o PHP foi projetado com essa filosofia em mente é irrelevante: ele possui a finalpalavra - chave, afinal. Em segundo lugar, argumentar a partir do design do PHP está fadado ao fracasso, dado o modo como o patchworky e o PHP são mal projetados.
11139 Konrad Rudolph

5

Há um bom artigo sobre "Quando declarar aulas finais" . Algumas citações:

TL; DR: Crie sempre suas classes final, se implementarem uma interface e nenhum outro método público for definido

Por que eu tenho que usar final?

  1. Impedindo uma cadeia maciça de destruição
  2. Incentivando a composição
  3. Forçar o desenvolvedor a pensar na API pública do usuário
  4. Forçar o desenvolvedor a reduzir a API pública de um objeto
  5. Uma finalclasse sempre pode ser extensível
  6. extends quebra o encapsulamento
  7. Você não precisa dessa flexibilidade
  8. Você é livre para alterar o código

Quando evitar final:

As aulas finais só funcionam efetivamente sob as seguintes premissas:

  1. Existe uma abstração (interface) que a classe final implementa
  2. Toda a API pública da classe final faz parte dessa interface

Se uma dessas duas pré-condições estiver ausente, você provavelmente chegará a um ponto em que tornará a classe extensível, pois seu código não depende realmente de abstrações.

PS Agradecemos a @ocramius pela ótima leitura!


5

"final" para uma classe significa: Você quer uma subclasse? Vá em frente, exclua a subclasse "final" o quanto quiser, mas não reclame se não funcionar. Você esta por sua conta.

Quando uma classe pode ser subclassificada, é um comportamento em que outras pessoas confiam, devem ser descritas em termos abstratos que as subclasses obedecem. Os chamadores devem ser escritos para esperar alguma variabilidade. A documentação deve ser escrita com cuidado; você não pode dizer às pessoas "olhe o código-fonte" porque o código-fonte ainda não existe. Isso é todo esforço. Se eu não espero que uma classe seja subclassificada, é um esforço desnecessário. "final" diz claramente que esse esforço não foi feito e dá um aviso justo.


-1

Uma coisa que você talvez não tenha pensado é o fato de QUALQUER mudança de classe significa que ela precisa passar por novos testes de controle de qualidade.

Não marque as coisas como finais, a menos que você realmente as queira.


Obrigado. Você poderia explicar isso mais? Você quer dizer que, se eu marcar uma turma como final(uma mudança), só preciso testá-la novamente?
JW01 02/07

4
Eu diria isso mais fortemente. Não marque as coisas como finais, a menos que uma extensão não possa ser executada devido ao design da classe.
S.Lott 2/07

Obrigado S.Lott - Estou tentando entender como o mau uso do final afeta o código / projeto. Você já teve alguma história de horror ruim que o levou a ser tão dogmático quanto ao uso poupador de final? Esta é a experiência em primeira mão?
JW01 02/07

1
@ JW01: Uma finalclasse tem um caso de uso primário. Você tem classes polimórficas que não deseja estender porque uma subclasse pode quebrar o polimorfismo. Não use a finalmenos que você deva impedir a criação de subclasses. Fora isso, é inútil.
31511 S.Lott

@ JW01, em um ambiente de produção, você pode não ser PERMITIDO para remover uma final, porque este não é o código do cliente testado e aprovado. No entanto, você pode adicionar código extra (para teste), mas talvez não consiga fazer o que deseja, porque não pode estender sua classe.

-1

Usar 'final' tira a liberdade de outras pessoas que desejam usar seu código.

Se o código que você escreve é ​​apenas para você e nunca será divulgado ao público ou a um cliente, você poderá fazer com o seu código o que quiser, é claro. Caso contrário, você evita que outros desenvolvam seu código. Muitas vezes tive que trabalhar com uma API que seria fácil de estender para minhas necessidades, mas fui impedida por 'final'.

Além disso, geralmente existe um código que não deveria ser melhor criado private, mas protected. Claro, privatesignifica "encapsulamento" e oculta coisas consideradas detalhes de implementação. Porém, como programador de API, eu também poderia documentar o fato de que o método xyzé considerado um detalhe da implementação e, portanto, pode ser alterado / excluído na versão futura. Portanto, todo mundo que confiar nesse código, apesar do aviso, está fazendo isso por seu próprio risco. Mas ele pode realmente fazer isso e reutilizar o código (espero que já tenha sido testado) e chegar mais rapidamente com uma solução.

Obviamente, se a implementação da API for de código aberto, basta remover o 'final' ou tornar os métodos 'protegidos', mas você alterou o código e precisa acompanhar suas alterações na forma de patches.

No entanto, se a implementação for de código fechado, você ficará para trás com a localização de uma solução alternativa ou, na pior das hipóteses, com a mudança para outra API com menos restrições em relação às possibilidades de customização / extensão.

Observe que eu não acho que 'final' ou 'private' sejam ruins, mas acho que eles são usados ​​com muita frequência porque o programador não pensou em seu código em termos de reutilização e extensão de código.


2
"Herança não é reutilização de código".
Quant_dev 02/07

2
Ok, se você insiste em uma definição perfeita, está certo. A herança expressa um relacionamento 'é-a' e é esse fato que permite polimorfismo e fornece uma maneira de estender uma API. Mas, na prática, a herança é muito frequentemente usada para reutilizar o código. Este é especialmente o caso com herança múltipla. E assim que você chama o código de uma superclasse, está realmente reutilizando o código.
Jonny Dee

@quant_dev: Reutilização no OOP significa antes de tudo polimorfia. E a herança é um ponto crítico para o comportamento polimórfico (compartilhamento da interface).
Falcon

@ Interface de compartilhamento Falcon! = Código de compartilhamento. Pelo menos não no sentido usual.
22131 quant_dev

3
@quant_dev: Você tem capacidade de reutilização no sentido OOP errado. Isso significa que um único método pode operar em muitos tipos de dados, graças ao compartilhamento de interface. Essa é uma parte importante da reutilização de código OOP. E a herança automaticamente vamos compartilhar interfaces, aumentando assim a reutilização do código. É por isso que falamos sobre reutilização no OOP. Apenas criar bibliotecas com rotinas é quase tão antigo quanto a própria programação e pode ser feito com quase todas as linguagens, é comum. A reutilização de código no OOP é mais do que isso.
Falcon
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.