Estou um pouco confuso sobre como o princípio Aberto Fechado pode ser aplicado na vida real. Exigência em qualquer negócio muda ao longo do tempo. De acordo com o princípio Aberto-Fechado, você deve estender a classe, em vez de modificar a classe existente. Para mim, toda vez que estender uma aula não parece prático para cumprir o requisito. Deixe-me dar um exemplo com o sistema de reservas de trens.
No sistema de reservas de trens, haverá um objeto Ticket. Pode haver diferentes tipos de bilhetes Ticket Regular, Ticket de Concessão etc. Ticket é da classe abstrata e RegularTicket e ConcessionTickets são classes concretas. Como todos os tickets terão o método PrintTicket, que é comum, portanto, ele é escrito em Ticket, que é a classe abstrata base. Digamos que isso funcionou bem por alguns meses. Agora chega um novo requisito, que declara alterar o formato do ticket. Podem ser adicionados mais alguns campos ao ticket impresso ou o formato pode ser alterado. Para cumprir este requisito, tenho as seguintes opções
- Modifique o método PrintTicket () na classe abstrata Ticket. Mas isso violará o princípio Aberto-Fechado.
- Substitua o método PrintTicket () nas classes filho, mas isso duplicará a lógica de impressão, que é uma violação do princípio DRY (não se repita).
Então as perguntas são
- Como posso satisfazer os requisitos comerciais acima sem violar o princípio Aberto / Fechado.
- Quando a classe deve ser fechada para modificação? Quais são os critérios para considerar que a classe está fechada para modificação? É após a implementação inicial da classe ou pode ser após a primeira implantação na produção ou pode ser outra coisa.
fonte
Respostas:
Isso depende da maneira como o
PrintTicket
é implementado. Se o método precisar considerar informações de subclasses, precisará fornecer uma maneira de fornecer informações adicionais.Além disso, você pode substituir sem repetir seu código também. Por exemplo, se você chamar seu método de classe base a partir da implementação, evite repetições:
O padrão Template Method fornece uma terceira opção: implementar
PrintTicket
na classe base e confiar nas classes derivadas para fornecer detalhes adicionais conforme necessário.Aqui está um exemplo usando sua hierarquia de classes:
Não é a classe que deve ser fechada para modificação, mas a interface dessa classe (quero dizer "interface" no sentido amplo, isto é, uma coleção de métodos e propriedades e seu comportamento, não a construção da linguagem). A classe oculta sua implementação, para que seus proprietários possam modificá-la a qualquer momento, desde que seu comportamento visível externamente permaneça inalterado.
A interface da classe precisa permanecer fechada após a primeira vez que você a publica para uso externo. As classes de uso interno permanecem abertas à refatoração para sempre, porque você pode encontrar e corrigir todos os seus usos.
Além dos casos mais simples, não é prático esperar que sua hierarquia de classes cubra todos os cenários de uso possíveis após um certo número de iterações de refatoração. Os requisitos adicionais que exigem métodos inteiramente novos na classe base surgem regularmente, para que sua classe fique sempre aberta a modificações por você.
fonte
TicketPrinter
classe que é passada para o construtor.Vamos começar com uma linha simples de princípio Aberto-fechado -
"Open for extension Closed for modification"
. Considere o seu problema: eu projetaria as classes para que o Ticket base fosse responsável por imprimir uma informação comum do Ticket e outros tipos de Ticket fossem responsáveis pela impressão de seu próprio formato na parte superior da impressão base.Dessa forma, você está aplicando qualquer novo tipo de ticket que seria introduzido no sistema implementará seu próprio recurso de impressão sobre as informações básicas de impressão de tickets. Agora, o ticket base está fechado para modificação, mas aberto para extensão, fornecendo um método adicional para os tipos derivados.
fonte
O problema não é que você está violando o princípio de Aberto / Fechado, o problema é que você está violando o Princípio de Responsabilidade Única.
Este é literalmente um exemplo de livro escolar de um problema de SRP, ou como a Wikipedia diz:
As diferentes classes de ticket podem mudar porque você adiciona novas informações a elas. A impressão do ticket pode mudar porque você precisa de um novo layout, portanto, você deve ter uma classe TicketPrinter que aceite tickets como entrada.
Suas classes de ticket podem implementar interfaces diferentes para fornecer diferentes tipos de dados ou fornecer algum tipo de modelo de dados.
fonte
Isso não viola o principal aberto-fechado. O código muda o tempo todo, é por isso que o SOLID é importante, para que o código permaneça sustentável, flexível e mutável. Não há nada errado em alterar o código.
OCP é mais sobre classes externas que não podem violar a funcionalidade pretendida de uma classe.
Por exemplo, eu tenho uma classe
A
que pega uma String no construtor e verifica se essa String tem um determinado formato chamando um método para verificar a string. Esse método de verificação também precisa ser utilizável pelos clientesA
, portanto, na implementação ingênua, é feitopublic
.Agora eu posso 'quebrar' essa classe herdando dela e substituindo o método de verificação para sempre retornar sempre
true
. Devido ao polimorfismo, ainda posso usar minha subclasse em qualquer lugar em que possa usá-loA
, possivelmente criando um comportamento indesejado no meu programa.Parece uma coisa maliciosa silenciosa, mas em uma base de código mais complexa, isso pode ser feito como um "erro honesto". Para estar em conformidade com o OCP, a classe
A
deve ser projetada de forma que não seja possível cometer esse erro.Eu poderia fazer isso, por exemplo, criando o método de verificação
final
.fonte
A herança não é o único mecanismo que pode ser usado para atender aos requisitos do OCP e, na maioria dos casos, eu argumentaria que raramente é o melhor - geralmente existem maneiras melhores, desde que você planeje um pouco para considerar o tipo de alterações que você provavelmente precisará.
Nesse caso, eu argumentaria que, se você espera obter alterações frequentes de formato no seu sistema de impressão de ingressos (e isso me parece uma aposta bastante segura), o uso de um sistema de modelos faria muito sentido. Agora, não precisamos tocar no código: tudo o que precisamos fazer para atender a essa solicitação é alterar um modelo (desde que seu sistema de modelos possa acessar todos os dados necessários).
Na realidade, um sistema nunca pode ser totalmente fechado contra modificações, e até mesmo chegar perto disso exigiria uma enorme quantidade de esforço desperdiçado; portanto, você deve fazer julgamentos sobre que tipo de mudanças são prováveis e estruturar seu código de acordo.
fonte