Let’s create the PriceTable interface to represent the abstraction discountCalculation(), regardless of the product payment method.
Also, let’s create the freightCalculation() abstraction in the FreightService interface.
After these changes, the PriceTableSimplePayment and PriceTablePaymentInInstallments classes will begin to implement the PriceTable interface and the Freight class starts to implement the FreightService interface.
PriceTable interface exampleFreightService interface examplePriceTablePaymentInInstallments class refactored examplePriceTableSimplePayment class refactored exampleFreight class refactored exampleWith this, we were able to wipe the PriceTable class, no longer needing to know the behavior of the various tables.
That is, we will CLOSE the PriceCalculator, Freight, and PriceTable classes for changes and if other rules arise to be used in the PriceCalculator, we are implementing new classes to represent them and receiving them by the constructor.
PriceCalculator class refactored exampleIn summary, this principle talks about maintaining good abstractions.
In addition, when using the STRATEGY pattern, we are obeying the OCP.
Liskov Substitution Principle (LSP)Derived classes must be substitutable for their base classes.
It says “subtypes should be replaceable by their base types,” pondering the care to use inheritance in your software project.
Despite inheritance is a powerful mechanism, it must be used contextualized and moderating, avoiding cases of classes being extended only by having something in common.
This principle was described by the researcher Barbara Liskov in her 1988 paper, which explains that before choosing to inherit, we need to think about the preconditions and postconditions of her class.
To be clearer, let’s observe the example of the CommonBankAccount and PayrollAccount classes.
CommonBankAccount represents any bank account within our simplified context.
It has the methods deposit(), getBalance(), cashOut(), and income().
CommonBankAccount class exampleA PayrollAccount is identical to the CommonBankAccount class, except for the income() method.
A payroll account has no income, it is only for receiving a salary.
PayrollAccount class exampleThat way, we can solve this problem by extending the CommonBankAccount class, as shown above, and making the income() method throw an exception, right?As expressed by our dear boss Michael Scott, this is not a good idea!.If we were to try to access the income() method of all bank accounts in a loop, for example, and one of them is a PayrollAccount, our application doesn’t work, because for any payroll account an exception is thrown.
Bank class exampleIn this scenario, we should refactor and use composition.
Let’s create an AccountManager class and this class that will control the accounts movements.
The CommonBankAccount and PayrollAccount classes now have an AccountManager, removing the unnecessary parent-child relationship between them.
AccountManager class exampleCommonBankAccount class refactored examplePayrollAccount class refactored exampleNotice that in the refactored version of the PayrollAccount class, we don’t need to implement the income() method.
We only use the manager in the behaviors that the class has.
Uncle Bob explains that LSP is the enabling principle of the Open/Closed Principle since the possibility of substituting subtypes allows a module to be extensible without modifications.
Interface Segregation Principle (ISP)Make fine grained interfaces that are client specific.
It says “many specific interfaces are better than a general interface”.
This principle deals with cohesion in interfaces, the construction of lean modules, that is, with few behaviors.
Interfaces that have many behaviors are difficult to maintain and evolve.
So, should be avoided.
For a better understanding, let’s go back to the Employee example and turn this class into an abstract class, with two other abstract methods: getSalary() and getCommission().
Example of Employee as an abstract classAnd in our company, we have two positions, which will extend this class Employee: the Seller and the Developer.
Seller class exampleDeveloper class exampleHowever, notice that the Employee class has behavior that doesn’t make sense for the Developer position: getCommission().
The developer’s salary is calculated based on hours worked and contracted, having no relation to total sales in a period.
So, how to solve this problem?.Refactoring the code to break these behaviors into two interfaces: Commissionable and Conventional.
Thus, the Employee starts to implement the Conventional interface, so that the Developer class doesn’t even need to exist since the Developer is an Employee with a Conventional payment regime.
In the same way, the Seller class starts to implement the Commissionable interface, with getCommission() method, which is specific to this type of Employee.
Conventional interface exampleCommissionable interface exampleEmployee class refactored exampleSeller class refactored exampleThe ISP alerts us to the “fat” classes, which cause bizarre and damaging couplings to business rules.
We just have to be careful, and this tip is good for the other principles as well: do not overdo it!.Thoroughly check for cases where segregation is necessary, preventing hundreds of different interfaces from being created improperly.
Dependency Inversion Principle (DIP)Depend on abstractions, not on concretions.
It says that we must “depend on abstractions and not on concrete classes.
” Uncle Bob breaks the definition of this principle into two sub-items:“High-level modules should not depend on low-level modules.
Both should depend on abstractions.
”“Abstractions should not depend on details.
Details should depend on abstractions.
”And this is because abstractions change less and make it easier to change behaviors and future code evolutions.
When we speak of OCP, we also saw an example of DIP, but don’t speak exclusively of it.
When we do the refactoring of the PriceCalculator class, instead of having direct dependence of PriceTableSimplePayment and PriceTablePaymentInInstallments concrete classes to calculate the discount of the product and the freight, we inverted the relationship to depend on two interfaces: PriceTable and Freight.
Thus, we continue to evolve and maintain only concrete classes.
The idea of applying the principles in software projects is to take advantage of the benefits of using object-oriented paradigm correctly, avoiding problems such as: lack of code standardization, duplication (remember “Do not repeat yourself”?), to isolate features that may be common to various code snippets and maintenance (very fragile code, where a modification can break an already tested functionality).
And if we can follow all these tips, we will have an easy code to maintain, test, reuse, extend and evolve, without a doubt.
An important tip, besides reading the main bibliographies on the subject, such as the books Agile Principles, Patterns, and Practices in C# and Principles of Ood is to start training on personal, small and simpler projects.
You can start by making changes at specific classes, avoiding code smells.
So you begin to train your brain to think more maturely when faced with more complex development situations.
Full examples that have been used in the article can be found in the artigo-solid-medium project on Github.
I hope you enjoyed the post!ReferencesClean Code: A Handbook of Agile Software Craftsmanship (Robert C.
Martin, 2011)Agile Principles, Patterns, and Practices in C# (Robert C.
Martin; Micah Martin, 2011)Alura — Cursos Online de Tecnologia — Design Patterns Java I: Boas práticas de programaçãoAlura — Cursos Online de Tecnologia — Design Patterns Java II: Boas práticas de programaçãoAlura — Cursos Online de Tecnologia — SOLID com Java: Orientação a Objetos com Java.