Ao ler várias perguntas do Stack Overflow e o código de outras pessoas, o consenso geral de como projetar classes é fechado. Isso significa que, por padrão em Java e C #, tudo é privado, os campos são finais, alguns métodos são finais e, às vezes, as classes são até finais .
A idéia por trás disso é ocultar os detalhes da implementação, o que é uma boa razão. No entanto, com a existência protected
na maioria das linguagens e polimorfismo OOP, isso não funciona.
Toda vez que desejo adicionar ou alterar a funcionalidade de uma classe, geralmente sou prejudicada por particulares e finais colocados em todos os lugares. Aqui os detalhes da implementação são importantes: você está pegando a extensão e estendendo-a, sabendo muito bem quais são as consequências. No entanto, como não consigo acessar campos e métodos particulares e finais, tenho três opções:
- Não estenda a classe, apenas contorne o problema que leva ao código mais complexo
- Copie e cole toda a classe, eliminando a reutilização do código
- Bifurcar o projeto
Essas não são boas opções. Por que não é protected
usado em projetos escritos em idiomas que o suportam? Por que alguns projetos proíbem explicitamente a herança de suas classes?
fonte
Respostas:
Projetar classes para funcionar corretamente quando estendidas, especialmente quando o programador que estende a extensão não entende completamente como a classe deve funcionar, exige um esforço extra considerável. Você não pode simplesmente pegar tudo o que é privado e torná-lo público (ou protegido) e chamar isso de "aberto". Se você permitir que outra pessoa altere o valor de uma variável, você deve considerar como todos os valores possíveis afetarão a classe. (E se eles definirem a variável como nula? Uma matriz vazia? Um número negativo?) O mesmo se aplica ao permitir que outras pessoas chamam um método. É preciso um pensamento cuidadoso.
Portanto, não é tanto que as aulas não devam ser abertas, mas às vezes não vale a pena o esforço para torná-las abertas.
Obviamente, também é possível que os autores da biblioteca estejam apenas sendo preguiçosos. Depende de qual biblioteca você está falando. :-)
fonte
Tornar tudo privado por padrão parece duro, mas olhe para o outro lado: quando tudo é privado por padrão, tornar público algo (ou protegido, que é quase a mesma coisa) é uma escolha consciente; é o contrato do autor da classe com você, consumidor, sobre como usar a classe. Esta é uma conveniência para vocês dois: o autor é livre para modificar o funcionamento interno da classe, desde que a interface permaneça inalterada; e você sabe exatamente em quais partes da turma você pode confiar e quais estão sujeitas a alterações.
A idéia subjacente é 'acoplamento flexível' (também conhecido como 'interfaces estreitas'); seu valor reside em manter a complexidade baixa. Ao reduzir o número de maneiras pelas quais os componentes podem interagir, a quantidade de dependência cruzada entre eles também é reduzida; e a dependência cruzada é um dos piores tipos de complexidade quando se trata de manutenção e gerenciamento de mudanças.
Em bibliotecas bem projetadas, as classes que valem a pena estender por herança protegeram e publicaram os membros nos lugares certos e ocultaram todo o resto.
fonte
Tudo o que não é privado deve existir mais ou menos com comportamento inalterado em todas as versões futuras da classe. De fato, pode ser considerado parte da API, documentada ou não. Portanto, expor muitos detalhes provavelmente está causando problemas de compatibilidade posteriormente.
Quanto resp "final". classes "seladas", um caso típico são classes imutáveis. Muitas partes da estrutura dependem da imutabilidade das strings. Se a classe String não fosse final, seria fácil (até tentador) criar uma subclasse String mutável que causasse todos os tipos de erros em muitas outras classes quando usada em vez da classe String imutável.
fonte
final
e / ouprivate
está bloqueado e nunca pode ser alterado .public
membros;protected
os membros formam um contrato apenas entre a classe que os expõe e suas classes derivadas imediatas; por outro lado,public
membros de contratos com todos os consumidores em nome de todas as classes herdadas presentes e futuras possíveis.No OO, existem duas maneiras de adicionar funcionalidade ao código existente.
O primeiro é por herança: você pega uma aula e deriva dela. No entanto, a herança deve ser usada com cuidado. Você deve usar a herança pública principalmente quando tiver um relacionamento isA entre a base e a classe derivada (por exemplo, um retângulo é uma forma). Em vez disso, você deve evitar a herança pública para reutilizar uma implementação existente. Basicamente, a herança pública é usada para garantir que a classe derivada tenha a mesma interface que a classe base (ou uma maior), para que você possa aplicar o princípio de substituição de Liskov .
O outro método para adicionar funcionalidade é usar delegação ou composição. Você escreve sua própria classe que usa a classe existente como um objeto interno e delega parte do trabalho de implementação.
Não sei qual foi a ideia por trás de todas as finais da biblioteca que você está tentando usar. Provavelmente, os programadores queriam ter certeza de que você não herdaria uma nova classe da classe deles, porque essa não é a melhor maneira de estender seu código.
fonte
A maioria das respostas tem razão: as classes devem ser seladas (final, NotOverridable, etc) quando o objeto não for projetado ou pretendido para ser estendido.
No entanto, eu não consideraria simplesmente fechar tudo o design de código adequado. O "O" no SOLID é para "Princípio Aberto-Fechado", afirmando que uma classe deve ser "fechada" para modificação, mas "aberta" para extensão. A ideia é que você tenha código em um objeto. Funciona muito bem. Adicionar funcionalidade não deve exigir a abertura desse código e a realização de alterações cirúrgicas que podem interromper o comportamento anterior do trabalho. Em vez disso, deve-se usar a injeção de herança ou dependência para "estender" a classe para fazer outras coisas.
Por exemplo, uma classe "ConsoleWriter" pode obter texto e enviá-lo para o console. Isso faz bem. No entanto, em um determinado caso, você TAMBÉM precisa da mesma saída gravada em um arquivo. Abrir o código do ConsoleWriter, alterar sua interface externa para adicionar um parâmetro "WriteToFile" às funções principais e colocar o código de gravação de arquivo adicional ao lado do código de gravação do console geralmente seria considerado uma coisa ruim.
Em vez disso, você pode fazer uma de duas coisas: você pode derivar do ConsoleWriter para formar ConsoleAndFileWriter e estender o método Write () para chamar primeiro a implementação base e, em seguida, também gravar em um arquivo. Ou, você pode extrair uma interface IWriter do ConsoleWriter e reimplementar essa interface para criar duas novas classes; um FileWriter e um "MultiWriter", que podem receber outros IWriters e transmitirão qualquer chamada feita aos seus métodos a todos os gravadores fornecidos. Qual deles você escolhe depende do que você precisará; A derivação simples é, bem, mais simples, mas se você tiver uma previsão fraca de eventualmente precisar enviar a mensagem para um cliente de rede, três arquivos, dois pipes nomeados e o console, vá em frente e se esforce para extrair a interface e criar o "Adaptador Y"; isto'
Agora, se a função Write () nunca foi declarada virtual (ou foi selada), agora você está com problemas se não controlar o código-fonte. Às vezes, mesmo que você faça. Geralmente, essa é uma posição ruim para a empresa e frustra os usuários de APIs de código fechado ou de fonte limitada sem fim quando isso acontece. Mas, há razões legítimas pelas quais Write () ou toda a classe ConsoleWriter são seladas; pode haver informações confidenciais em um campo protegido (substituído por sua vez a partir de uma classe base para fornecer essas informações). Pode haver validação insuficiente; ao escrever uma API, você deve assumir que os programadores que consomem seu código não são mais inteligentes ou benevolentes que o "usuário final" médio do sistema final; Dessa forma, você nunca fica desapontado.
fonte
Eu acho que é provavelmente de todas as pessoas que usam uma linguagem oo para programação c. Eu estou olhando para você, java. Se o seu martelo tiver a forma de um objeto, mas você desejar um sistema processual e / ou realmente não entender as classes, seu projeto precisará ser bloqueado.
Basicamente, se você vai escrever em um idioma oo, seu projeto precisa seguir o paradigma oo, que inclui extensão. Obviamente, se alguém escreveu uma biblioteca em um paradigma diferente, talvez você não deva estendê-la de qualquer maneira.
fonte
Porque eles não sabem melhor.
Os autores originais provavelmente estão apegados a um mal-entendido dos princípios do SOLID que se originaram em um mundo C ++ confuso e complicado.
Espero que você note que os mundos ruby, python e perl não têm os problemas que as respostas aqui alegam ser o motivo da vedação. Observe que sua digitação ortogonal à dinâmica. Os modificadores de acesso são fáceis de trabalhar na maioria dos idiomas (todos?). Os campos C ++ podem ser modificados ao transmitir para outro tipo (C ++ é mais fraco). Java e C # podem usar reflexão. Os modificadores de acesso tornam as coisas difíceis o suficiente para impedir que você faça isso, a menos que você realmente queira.
O selamento de classes e a marcação de qualquer membro como particular viola explicitamente o princípio de que coisas simples devem ser simples e coisas difíceis devem ser possíveis. De repente, coisas que deveriam ser simples, não são.
Eu o encorajo a tentar entender o ponto de vista dos autores originais. Muito disso vem de uma idéia acadêmica de encapsulamento que nunca demonstrou sucesso absoluto no mundo real. Eu nunca vi uma estrutura ou biblioteca em que algum desenvolvedor em algum lugar não desejasse que funcionasse um pouco diferente e não tivesse boas razões para alterá-lo. Há duas possibilidades que podem ter atormentado os desenvolvedores de software originais que selaram e tornaram os membros privados.
Penso que, na estrutura corporativa, o nº 2 é provavelmente o caso. Essas estruturas C ++, Java e .NET precisam ser "concluídas" e precisam seguir certas diretrizes. Essas diretrizes geralmente significam tipos fechados, a menos que o tipo tenha sido explicitamente projetado como parte de uma hierarquia de tipos e membros privados para muitas coisas que podem ser úteis para outras pessoas usarem. Mas, como eles não se relacionam diretamente a essa classe, eles não são expostos . Extrair um novo tipo seria muito caro para suportar, documentar, etc ...
A idéia por trás dos modificadores de acesso é que os programadores devem ser protegidos de si mesmos. "A programação C é ruim porque permite que você atire no próprio pé." Não é uma filosofia com a qual concordo como programador.
Eu prefiro muito a abordagem confusa do nome do python. Você pode facilmente (muito mais fácil do que refletir) substituir substitutos, se necessário. Uma excelente descrição está disponível aqui: http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/
O modificador privado de Ruby é mais como protegido em C # e não tem um privado como modificador de C #. Protegido é um pouco diferente. Há uma grande explicação aqui: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
Lembre-se de que sua linguagem estática não precisa estar em conformidade com os estilos antiquados do código escrito naquele idioma.
fonte
EDIT: Eu acredito que as aulas devem ser projetadas para serem abertas. Com algumas restrições, mas não deve ser fechado para herança.
Por que: "Aulas finais" ou "aulas seladas" me parecem estranhas. Nunca preciso marcar uma de minhas próprias classes como "final" ("selada"), porque talvez precise herdar essas classes mais tarde.
Comprei / baixei bibliotecas de terceiros com classes (controles mais visuais) e realmente grato que nenhuma dessas bibliotecas tenha usado "final", mesmo se suportada pela linguagem de programação, na qual elas foram codificadas, porque eu acabei de estender essas classes, mesmo se eu compilei bibliotecas sem o código fonte.
Às vezes, eu tenho que lidar com algumas classes "seladas" ocasionais e acabei fazendo um novo "wrapper" de classe contendo a classe dada, que tem membros semelhantes.
Os classificadores de escopo permitem "selar" alguns recursos sem restringir a herança.
Quando mudei do Structured Progr. para OOP, comecei a usar membros da classe privada , mas, depois de muitos projetos, acabei usando o protected e, eventualmente, promovi essas propriedades ou métodos para o público , se necessário.
PS: Eu não gosto de "final" como palavra-chave em C #; prefiro usar "selado" como Java, ou "estéril", seguindo a metáfora da herança. Além disso, "final" é uma palavra ambígua usada em vários contextos.
fonte