carloscastilla - Fotolia
How to refactor the God object class antipattern
Loose coupling can inadvertently create a God object problem in an object-oriented software system. Here's how to get rid of the issue and simplify the design in five easy steps.
It's not good enough to simply write code that works. That code must be easily maintained, enhanced and debugged when problems happen. One of the reasons why object-oriented programming is so popular is because it delivers on these requirements.
But antipatterns often appear when developers take shortcuts or focus more on the need to get things done instead of done right. One of those common antipatterns is the God object.
One of the main concepts in object-oriented programming is that every component has a single purpose, and that component is only responsible for the properties and fields that allow it to perform its pertinent functions.
For example, there are times when Agile sprints are too short and milestone dates quickly approach, and developers take liberties with their components. Too many responsibilities get packed into a single component, and the separation of responsibilities between components starts to blur.
Good object-oriented design sometimes takes a back seat to a need to get things done, and the single responsibility model gets thrown out the window. Then, out of nothingness, the God object emerges.
In simple terms, the God object is a single Java file that violates the single-responsibility design pattern because it:
- performs multiple tasks;
- declares many unrelated properties; and
- maintains a collection of methods that have no logical relationship to one another, other than performing operations pivotal to the application function.
What can you do to fix a God object in an object-oriented system? Refactor it. Follow these five steps to refactor the God object and declutter code.
1. Create a comprehensive unit test suite
Build a suite of unit tests that properly validate the God object functions. This way you can refactor the God object with confidence. If you happen to break something, unit tests will alert you to wayward code.
2. Identify the clients
Before you refactor the God class, determine where the God object's methods are invocated from. Is the God object used equally throughout the application, or are there method calls from a specific set of sub-components?
Identifying the most relied upon components for the God object serves multiple purposes:
- Prioritize which parts of the application get refactored. Consider refactoring areas that get used infrequently first, before you make code commits that affect critical systems.
- If a given set of sub-components routinely invoke a common set of methods, break the God object into a smaller set of objects based on the subcomponent usage as a possible decomposition strategy.
- The common set of methods invoked by a given set of clients represents an opportunity to define a RESTful or microservices-based interface that is both loosely coupled and introduces an opportunity to make the application cloud-native.
3. Factor static methods into a utility class
Instances are the foundation of object-oriented programming. Static methods are ones that aren't passed in any instance variables. As such, if you move static methods and static variables into a single utility class, you can look at the remaining properties and variables and begin to create smaller, single-responsibility objects.
However, utility classes aren't particularly object-oriented. After you handle the static methods, consider factoring the utility classes in the system into something more object-oriented as well. But, a few utility classes in a design is much less problematic than a God object.
4. Group common methods and properties
At this point, there are instance variables and instance methods that remain in the God object. Refactor them into objects. Use common object-oriented principles such as inheritance, aggregation and association to express relationships between the objects you create. At the same time, respect the Law of Demeter to ensure you still have a loosely coupled system.
5. Delete or deprecate the God object
If you have successfully refactored the God object, delete the component from the design. The system should function in the same manner as before.
After you complete this step, feel free to quote Friedrich Nietzsche, "God is dead. God remains dead. And we have killed him. How shall we comfort ourselves, the murderers of all murderers?"
Don't fret too much if you created compilation problems in client and subcomponent code after you deleted the God object. You can change the God object into an interface, or at the very least, turn it into a facade object in which there are no instance variables. The methods in the God object then simply delegate work to the various utility classes and smaller objects that were created as the God object was refactored.
If you do go with the facade approach, make sure you mark each method as deprecated so that future development knows to invoke the factored out objects, not the God object itself.
Five steps to refactor a God object
In review, follow these five steps to reverse the God object antipattern:
- Build a comprehensive unit test suite.
- Identify the most relied upon God object components.
- Move static methods into a utility class.
- Group common methods and properties but maintain a loosely coupled system.
- Delete the God object from the system design.