Entendo a intenção do princípio aberto-fechado. Ele visa reduzir o risco de quebrar algo que já funciona ao modificá-lo, solicitando que você tente estender sem modificar.
No entanto, tive alguns problemas para entender como esse princípio é aplicado na prática. No meu entender, existem duas maneiras de aplicá-lo. Antes e depois de uma possível mudança:
Antes: programe para abstrações e 'preveja o futuro' o máximo que puder. Por exemplo, um método
drive(Car car)
precisará ser alterado seMotorcycle
s forem adicionados ao sistema no futuro, portanto, provavelmente viola o OCP. Masdrive(MotorVehicle vehicle)
é menos provável que o método precise mudar no futuro, por isso adere ao OCP.No entanto, é bastante difícil prever o futuro e saber com antecedência quais mudanças serão feitas no sistema.
Depois: quando uma alteração for necessária, estenda uma classe em vez de modificar o código atual.
A prática nº 1 não é difícil de entender. No entanto, é a prática número 2 que estou tendo problemas para entender como me inscrever.
Por exemplo (a tirei de um vídeo no YouTube): vamos dizer que temos um método em uma classe que aceita CreditCard
objetos: makePayment(CraditCard card)
. Um dia Voucher
s são adicionados ao sistema. Este método não os suporta, portanto, ele deve ser modificado.
Ao implementar o método em primeiro lugar, falhamos em prever o futuro e o programa em termos mais abstratos (por exemplo makePayment(Payment pay)
, agora precisamos alterar o código existente.
A prática nº 2 diz que devemos adicionar a funcionalidade estendendo ao invés de modificar. O que isso significa? Devo subclassificar a classe existente em vez de simplesmente alterar o código existente? Devo fazer algum tipo de wrapper para evitar a reescrita do código?
Ou o princípio nem mesmo se refere a 'como modificar / adicionar corretamente a funcionalidade', mas se refere a 'como evitar a necessidade de fazer alterações em primeiro lugar (isto é, programa para abstrações)?
fonte
Respostas:
Os princípios de design sempre precisam ser equilibrados entre si. Você não pode prever o futuro, e a maioria dos programadores faz isso horrivelmente quando tenta. É por isso que temos a regra dos três , que trata principalmente da duplicação, mas também se aplica à refatoração para quaisquer outros princípios de design.
Quando você tem apenas uma implementação de uma interface, não precisa se preocupar muito com o OCP, a menos que esteja claro onde as extensões ocorreriam. De fato, você freqüentemente perde a clareza ao tentar projetar demais nessa situação. Quando você o estende uma vez, refatora para torná-lo amigável ao OCP, se essa é a maneira mais fácil e clara de fazê-lo. Ao estendê-lo para uma terceira implementação, certifique-se de refatorá-lo levando em conta o OCP, mesmo que exija um pouco mais de esforço.
Na prática, quando você tem apenas duas implementações, refatorar quando você adiciona uma terceira geralmente não é muito difícil. É quando você deixa crescer depois desse ponto que fica difícil de manter.
fonte
Eu acho que você está olhando muito longe no futuro. Resolva o problema atual de uma maneira flexível que adere ao aberto / fechado.
Digamos que você precise implementar um
drive(Car car)
método. Dependendo do seu idioma, você tem algumas opções.Para idiomas que suportam sobrecarga (C ++), use apenas
drive(const Car& car)
Em algum momento depois, você pode precisar
drive(const Motorcycle& motorcycle)
, mas isso não interferirádrive(const Car& car)
. Sem problemas!Para os idiomas que não suportam a sobrecarga (Objective C), em seguida, incluir o nome do tipo no método
-driveCar:(Car *)car
.Em algum momento depois, você pode precisar
-driveMotorcycle:(Motorcycle *)motorcycle
, mas, novamente, isso não interferirá.Isso permite
drive(Car car)
que você seja fechado para modificação, mas está aberto a se estender a outros tipos de veículos. Esse planejamento futuro minimalista, que permite que você faça o trabalho hoje, mas evita que você se bloqueie no futuro.Tentar imaginar os tipos mais básicos necessários pode levar a uma regressão infinita. O que acontece quando você deseja dirigir um Segue, uma bicicleta ou um jato Jumbo. Como você constrói um único tipo abstrato genérico que pode ser responsável por todos os dispositivos que as pessoas acessam e usam para mobilidade?
fonte
Trata-se também de não quebrar todos os objetos que dependem desse método, não alterando o comportamento de objetos já existentes. Depois que um objeto anuncia a mudança de comportamento, é arriscado, pois você altera o comportamento conhecido e esperado do objeto sem saber exatamente o que outros objetos esperam desse comportamento.
Sim.
"Somente aceita cartões de crédito" é definido como parte do comportamento dessa classe, por meio de sua interface pública. O programador declarou ao mundo que o método desse objeto aceita apenas cartões de crédito. Ela fez isso usando um nome meio não claro, mas está pronto. O resto do sistema está contando com isso.
Isso pode ter feito sentido na época, mas agora, se precisar mudar, você deve criar uma nova classe que aceite outras coisas que não cartões de crédito.
Novo comportamento = nova classe
Como um aparte - Uma boa maneira de prever o futuro é pensar no nome que você deu a um método. Você forneceu um nome de método de som realmente geral, como makePayment, a um método com regras específicas no método sobre exatamente qual pagamento ele pode fazer? Isso é um cheiro de código. Se você tiver regras específicas, elas deverão ficar claras no nome do método - makePayment deve ser makeCreditCardPayment. Faça isso quando estiver escrevendo o objeto pela primeira vez e outros programadores agradecerão por isso.
fonte