Today we will learn about the Factory Pattern and we will see that how beautifully a design pattern can reduce coupling and helps us writing maintainable code.
The Scenario
Assume that we are building an application where we are supposed to have multiple payment types. When the user tries to make an order the order module will provide us the amount that we need to change from the user. Now the user can have multiple payment methods that we need to pass to the Payment module for processing the order by deducting the charges from the payment method user has already provided.
We will first code the solution without using the Factory Pattern.
1 | interface Payment { |
Explanation
In the above code we have created an interface with a method Pay() returning void. It can throw exception if things go wrong like invalid details or insufficient balance. We have used the interface to facilitate polymorphism so that we can pass any entity having a Pay() method can be used for making payments. Now let’s see the main code of the Payment Service on how the payment is being taken.
1 | class PaymentService { |
Highlighting Issues
I think you have already sensed the issues in the above code. Let me highlight them:
- We are exposing a lot of details to the
PaymentServiceclass unnecessarily. - The
PaymentServiceclass is tightly coupled with the payment options. - In future if we want to support more payment options we have to update the
PaymentServiceclass method(s). - The
PaymentService‘sProcessPaymentshould only focus on deducting payments not initiating the payment methods. - Also it’s anti-pattern (violation of open-close principle) to expose the initiation methods of cards to the
PaymentServiceclass.
Using Factory Pattern
To reduce the coupling and avoid exposing the details of the cards we will create a PaymentFactory class which will get the type of the payment user has selected and then return the payment type object after properly imitating it. Let’s do it.
Improved Code
1 | interface Payment { |
Explanation
Now that we have the PaymentFactory with the static method to get the payment object, the PaymentService class can simply call it without taking the burden of object initialization. Also we are not exposing any details to the payment service class. On adding a new payment method there will be no need of modifying the code of the PaymentService class at all.
Let’s test the implementation by adding a new payment type - Wallet.
Adding Wallets
We will see the beauty of the Factory pattern by adding a new payment method called wallet. The steps will be to:
- Update the
enumto support wallet. - Add the concrete wallet class.
- Wire the wallet with the payment factory
1 | enum PaymentTypes { |
The concrete wallet class:
1 | class Wallet implements Payment { |
Wiring up the wallet payment option with the payment factory class:
1 | class PaymentFactory { |
Note: As the types will increase the switch block will become bulky and error prone, we can use a map instead as the types grows.
We can now use the wallet method of payment in the main function as well:
1 | function main() { |
The logs on execution will be:
1 | payment using Credit Card |
Enums
You can see that we have done a bit of refactoring like using the enum PaymentTypes and also throwing error if the payment type was passed something else (This should not ideally happen because the enum will only have the types which are implemented in the Payment Factory). That’s how the use of enums make the code more robust.
Conclusion
The Factory pattern helps us create objects without exposing the creation logic to the business code. By using a factory, the code becomes cleaner, easier to maintain, and less tightly coupled. When new payment methods are added, we only need to update the factory, not the payment service. This makes the application more flexible and easier to extend in the future.