O princípio de aberto-fechado (OCP) afirma que um objeto deve estar aberto para extensão, mas fechado para modificação. Acredito que entendo e uso em conjunto com o SRP para criar classes que fazem apenas uma coisa. E tento criar muitos métodos pequenos que possibilitam extrair todos os controles de comportamento em métodos que podem ser estendidos ou substituídos em alguma subclasse. Assim, termino com classes que têm muitos pontos de extensão, seja através de: injeção e composição de dependências, eventos, delegação, etc.
Considere o seguinte uma classe simples e extensível:
class PaycheckCalculator {
// ...
protected decimal GetOvertimeFactor() { return 2.0M; }
}
Agora diga, por exemplo, que as OvertimeFactor
alterações para 1,5. Como a classe acima foi projetada para ser estendida, eu posso facilmente subclasses e retornar uma diferente OvertimeFactor
.
Mas ... apesar da classe ser projetada para extensão e aderência ao OCP, modificarei o método único em questão, em vez de subclassificar e substituir o método em questão e depois reconectar meus objetos no meu contêiner de IoC.
Como resultado, violei parte do que o OCP tenta realizar. Parece que estou apenas com preguiça, porque o exposto acima é um pouco mais fácil. Estou entendendo mal o OCP? Eu realmente deveria estar fazendo algo diferente? Você aproveita os benefícios do OCP de maneira diferente?
Atualização : com base nas respostas, parece que este exemplo artificial é ruim por várias razões diferentes. O principal objetivo do exemplo era demonstrar que a classe foi projetada para ser estendida, fornecendo métodos que, quando substituídos, alterariam o comportamento de métodos públicos , sem a necessidade de alterar o código interno ou privado. Ainda assim, eu definitivamente não entendi o OCP.
fonte
During the 1990s, the open/closed principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other.
en.wikipedia.org/wiki/Open/closed_principlePortanto, o Princípio Aberto Fechado é uma tarefa difícil ... especialmente se você tentar aplicá-lo ao mesmo tempo que o YAGNI . Como aderir a ambos ao mesmo tempo? Aplique a regra de três . Na primeira vez que você fizer uma alteração, faça-a diretamente. E a segunda vez também. Na terceira vez, é hora de abstrair essa mudança.
Outra abordagem é "me engane uma vez ...", quando precisar fazer uma alteração, aplique o OCP para se proteger contra essa mudança no futuro . Eu quase chegaria ao ponto de propor que alterar a taxa de horas extras é uma nova história. "Como administrador da folha de pagamento, desejo alterar a taxa de horas extras para estar em conformidade com as leis trabalhistas aplicáveis". Agora você tem uma nova interface do usuário para alterar a taxa de horas extras, uma maneira de armazená-la e GetOvertimeFactor () apenas pergunta ao seu repositório qual é a taxa de horas extras.
fonte
No exemplo que você postou, o fator horas extras deve ser uma variável ou uma constante. * (Exemplo Java)
OU
Então, quando você estender a classe, defina ou substitua o fator. Os "números mágicos" devem aparecer apenas uma vez. Isso é muito mais no estilo de OCP e DRY (não se repita), porque não é necessário criar uma nova classe para um fator diferente se estiver usando o primeiro método, e apenas precisar alterar a constante em um idiomático coloque no segundo.
Eu usaria o primeiro nos casos em que haveria vários tipos de calculadora, cada um precisando de valores constantes diferentes. Um exemplo seria o padrão da Cadeia de Responsabilidade, que geralmente é implementado usando tipos herdados. Um objeto que só pode ver a interface (ou seja
getOvertimeFactor()
) a utiliza para obter todas as informações necessárias, enquanto os subtipos se preocupam com as informações reais a serem fornecidas.O segundo é útil nos casos em que a constante provavelmente não será alterada, mas é usada em vários locais. Ter uma constante para alterar (no improvável caso) é muito mais fácil do que defini-lo em todo o lugar ou obtê-lo de um arquivo de propriedades.
O princípio Aberto-fechado é menos uma chamada para não modificar um objeto existente do que um cuidado para deixar a interface inalterada. Se você precisar de algum comportamento ligeiramente diferente de uma classe ou funcionalidade adicional para um caso específico, estenda e substitua. Mas se os requisitos para a própria classe mudarem (como mudar o fator), você precisará alterar a classe. Não há sentido em uma enorme hierarquia de classes, a maioria das quais nunca é usada.
fonte
Realmente não vejo seu exemplo como uma ótima representação do OCP. Eu acho que o que a regra realmente significa é isso:
Uma má implementação abaixo. Toda vez que você adiciona um jogo, você precisa modificar a classe GamePlayer.
A classe GamePlayer nunca deve precisar ser modificada
Agora, assumindo que meu GameFactory também cumpra o OCP, quando eu quiser adicionar outro jogo, eu precisaria criar uma nova classe que herda da
Game
classe e tudo deve funcionar.Com freqüência, classes como a primeira são criadas após anos de "extensões" e nunca são refatoradas corretamente da versão original (ou pior, o que deveriam ser várias classes continua sendo uma grande classe).
O exemplo que você fornece é OCP-ish. Na minha opinião, a maneira correta de lidar com alterações nas taxas de horas extras seria em um banco de dados com taxas históricas mantidas para que os dados pudessem ser reprocessados. O código ainda deve estar fechado para modificação, pois sempre carregava o valor apropriado da pesquisa.
Como um exemplo do mundo real, usei uma variante do meu exemplo e o Princípio Aberto-Fechado realmente brilha. É fácil acrescentar funcionalidade, porque eu apenas tenho que derivar de uma classe base abstrata e minha "fábrica" a pega automaticamente e o "player" não se importa com a implementação concreta que a fábrica retorna.
fonte
Neste exemplo em particular, você tem o que é conhecido como "Valor Mágico". Essencialmente, um valor codificado que pode ou não mudar ao longo do tempo. Vou tentar resolver o enigma que você expressa genericamente, mas este é um exemplo do tipo de coisa em que criar uma subclasse é mais trabalhoso do que alterar um valor em uma classe.
Digamos que temos o
PaycheckCalculator
. ÉOvertimeFactor
muito provável que essas informações sejam excluídas das informações sobre o funcionário. Um funcionário por hora pode desfrutar de um bônus de horas extras, enquanto um funcionário assalariado não receberia nada. Ainda assim, alguns funcionários assalariados terão direito a tempo devido ao contrato em que estavam trabalhando. Você pode decidir que existem certas classes conhecidas de cenários de pagamento, e é assim que você construiria sua lógica.Na
PaycheckCalculator
classe base, você o torna abstrato e especifica os métodos que você espera. Os cálculos principais são os mesmos, mas apenas alguns fatores são calculados de maneira diferente. VocêHourlyPaycheckCalculator
implementaria ogetOvertimeFactor
método e retornaria 1,5 ou 2,0, conforme o caso. VocêStraightTimePaycheckCalculator
implementaria ogetOvertimeFactor
para retornar 1.0. Finalmente, uma terceira implementação seria umaNoOvertimePaycheckCalculator
que implementaria ogetOvertimeFactor
retorno 0.A chave é descrever apenas o comportamento na classe base que se destina a ser estendido. Os detalhes de partes do algoritmo geral ou valores específicos seriam preenchidos por subclasses. O fato de você ter incluído um valor padrão para os
getOvertimeFactor
leads para a rápida e fácil "correção" de uma linha, em vez de estender a classe conforme desejado. Também destaca o fato de que há um esforço envolvido na extensão de classes. Também há um esforço envolvido no entendimento da hierarquia de classes no seu aplicativo. Você deseja projetar suas classes de forma a minimizar a necessidade de criar subclasses e ainda fornecer a flexibilidade necessária.Alimento para reflexão: quando nossas aulas englobam certos fatores de dados, como
OvertimeFactor
no exemplo, você pode precisar de uma maneira de extrair essas informações de alguma outra fonte. Por exemplo, um arquivo de propriedades (já que isso se parece com Java) ou um banco de dados conteria o valor e vocêPaycheckCalculator
usaria um objeto de acesso a dados para extrair seus valores. Isso permite que as pessoas certas alterem o comportamento do sistema sem a necessidade de reescrever o código.fonte