In the first part of this series I wrote code for a very simple use case that violates some of the best practices for writing clean code. In this second part, I’m going to rewrite the code using the Factory Pattern and we are going to look at why this code is much better.
Keep in mind that this series is not to teach you how to write good clean code, but just to highlight why it is so important to write good clean code.
Before we use the Factory Pattern, let’s have a quick recap of the Factory Pattern.
The Factory Pattern
The Factory Pattern is part of the Creational Design Pattern collection because it is used to create or instantiate objects.
Other types of design patterns are Structural Patterns which help with class design and Behavioral Patterns which help with how classes or objects communicate with each other.
The Factory Pattern is an analogy of a real-world factory. For example, say you have a stationary factory that builds products. The products can be anything physical (concrete) like pens, pencils, rulers etc. You just tell the factory which physical or concrete product to build. The notion that the factory builds this abstract concept of products is very important. We want to be able to do all sorts of similar things with our products like ship them, sell them etc. irrespective of which product came out of the factory. We implement the concept of a product with an interface so that all concrete products can be handled in the same way.
Let’s look at a simple class diagram for our stationary factory.
In the example above the StationaryFactory class or object can be used by calling its createProduct function, telling it what concrete product you want the factory to “build” and then receiving that product. When you receive the product, you can do anything with that product that you can also do with any other product from that factory, like shipping it or selling it, because all the products conform to the same interface.
The Legalese Factory
Now that we know what the Factory Pattern is, let’s build our factory to produce legal documents for our countries.
The product that our factory will produce is a collection of legal documents per country. We will tell our factory to manufacture a set of legal documents for a specific country.
Let’s look at the class diagram for our implementation.
So again we have a factory, the LegaleseFactory. We tell the factory to manufacture a set of legalese for us and also for which country we want them. We will receive the concrete implementation of legalese for that country.
Enough talk 🙂 let’s write some code.
Above is our “product” that will be manufactured by our factory. The interface ensures that all the legalese produced by our factory can be treated in the same way no matter for which countries the legalese is intended.
Below are the three concrete implementations of each country’s legalese. It is up to these implementations to return the correct String Arrays for each legal document.
And lastly, here’s our factory implementation. We can ask our factory to create legalese for a specific country by calling the createLegalese function with the country’s code.
Is the code easy to understand and read? Although the code might be a bit more complicated than just the simple if statements previously shown, it is definitely not difficult to read. If you are familiar with the Factory Pattern it is easy to read and understand. If you are not familiar with the design pattern it’s anyway best to become familiar with the design pattern.
Just looking at each concrete implementation for a country’s legalese, it is all bundled together without any other logic or any other clutter like if then else statements and logic. It is just the code for this specific country’s legalese. All this makes the code extremely readable and understandable, contained and clean.
Is the code easy to change? The answer is again a definite yes. Let’s first look at how you would be using this factory in the rest of your code.
Above are a few examples of how to get each type of legal document from our factory and they can sit anywhere in the codebase.
If you need to add a country, you just add the country check to the factory’s createLegalese function and implement the concrete implementation for the country legalese. Wherever the code for the Declaration, the Disclaimer or the Terms and Conditions are used in the app, it will just continue to work. So a change does not impact your codebase all over the place as I have shown with the if statement implementation in part one of this series, only where the factory code is.
If you need to remove a country, just remove it from the factory and preferably remove the concrete implementation of the country’s legalese class. We don’t want obsolete code to clutter our codebase. Again, the rest of the app will just continue to work, no need to change the code all over the place.
If you need to add another type of legal document to the legalese, say a contract. All you need to do is update the interface and then update the concrete implementations for each country. If you forgot to update a concrete implementation for one of the countries, the compiler will let you know. So it is not possible to forget to update something like it was with the if statements in part one of this series, where the compiler could not help you.
Above is an example of the compiler informing you that the South African concrete implementation did not implement the getContract() function.
Is this code testable? Yes, the code is easily testable. The legalese is now packaged in a stand-alone factory class with no dependencies. Here’s an example of a simple test to check that the South African Disclaimer is returned when requested.
In this series, we looked at the advantages of writing Clean Code by using best practices such as Design Patterns. We looked at the advantages of Clean Code for developers but even more importantly, for our customers.
It becomes easier for developers to develop applications because they can just use a tried and tested recipe to solve a problem, easier and safer to implement changes, and easier for new developers to be onboarded on a project or take over a project.
This all translates into more robust, maintainable, testable and higher quality applications for our customers, and shorter development times which also translates into cost savings for our customers.