How to refactor God objects in C#

Follow these best practices to eliminate God objects and design classes that are loosely coupled, cohesive, and easy to maintain

In object-oriented programming (OOP), so-called God objects are an anti-pattern because they violate a number of good design principles. God objects are objects of classes that know too much or do too much, and hence make your code tightly coupled and difficult to maintain. This article talks about this anti-pattern and how you can get rid of it.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.

Create a console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. Click Create.

This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project in the subsequent sections of this article.

What is a God object?

Writing code without following the best practices might lead to code that is hard to debug, extend, and maintain over time. Object-oriented programming has become extremely popular over the past few decades because it is adept at helping you to eliminate poorly written code from your application. However, anti-patterns might often emerge if you take shortcuts and your primary objective is getting the task done rather than getting it done the right way.

Whereas a design pattern is a proven solution to a common design problem in software development, an anti-pattern is an ineffective pattern that shows us how not to solve a problem, because doing so would result in poor design. In other words, an anti-pattern is a common solution to a given problem that can have negative consequences — a solution that is ineffective or counterproductive in practice. A God object is one such anti-pattern.

A God object contains cluttered code that is difficult to maintain, extend, use, test, and integrate with other parts of the application. God objects violate the single responsibility principle and the Law of Demeter. I will discuss the single responsibility principle below. The Law of Demeter, or principle of least knowledge, reduces dependencies between classes and helps build components that are loosely coupled.

Here is a list of the characteristics of a God object:

  • Too many functions in a class
  • Knows too much about the application and can interact with all the data
  • Difficult to maintain over time because changes to the class of the God object typically introduce problems or side effects
  • Extremely complicated since it maintains the state of most of the application

God objects and the single responsibility principle

When multiple responsibilities are assigned to a single class, the class violates the single responsibility principle. Again, such classes are difficult to maintain, extend, test, and integrate with other parts of the application.

The single responsibility principle states that a class (or subsystem, module, or function) should have one and only one reason for change. If there are two reasons for a class to change, the functionality should be split into two classes with each class handling one responsibility. 

Some of the benefits of the single responsibility principle include orthogonality, high cohesion, and low coupling. A system is orthogonal if changing a component changes the state of that component only, and doesn’t affect other components of the application. In other words, the term orthogonality means that operations change only one thing without affecting other parts of the code.

Coupling is defined as the degree of interdependence that exists between software modules and how closely they are connected to each other. When this coupling is high, then the software modules are interdependent, i.e., they cannot function without each other.

Cohesion denotes the level of intra-dependency among the elements of a software module. In other words, Cohesion is a measure of the degree to which the responsibilities of a single module or a component form a meaningful unit.

God object example in C#

The solution to the God object anti-pattern is to split related functionality into smaller and more manageable classes — i.e., to design classes that have strong cohesion and are loosely coupled with one another.

Let’s dig into some code to understand the God object anti-pattern better. Let’s say you are building a recruitment management system. Create the following class in the Program.cs file.

public class Candidate
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public DateTime InterviewDate { get; set; }
        public int RecruiterId { get; set; }
        public string Recruiter { get; set; }
        public int InterviewerId { get; set; }
        public string Interviewer { get; set; }
        public string InterviewerRemarks { get; set; }
        public decimal CurrentSalary { get; set; }
        public decimal ExpectedSalary { get; set; }
    }

The Candidate class shown here contains several properties that shouldn’t be part of it. In essence, this class is doing too many things, and thus violates the single responsibility principle. This is a good example of a God object. In other words, an instance of the Candidate class is a God object because it contains too much information.

Refactor a God object in C#

To fix a God object problem, you can follow these steps:

  1. Create unit tests. Create a comprehensive unit test suite before you start refactoring the classes. This would help you to know if any functionality in the application is broken due to a change.
  2. Group related methods and properties. Group the common methods and properties in a class while keeping the classes loosely coupled.
  3. Divide large classes and methods. If classes or methods have a lot of code or functionality in them, break them into simple, manageable, testable classes and methods.
  4. Remove the God object. Finally, delete or deprecate the God object.

Referring back to our example above, what if there are multiple rounds of interviews and each of those rounds are taken by a different interviewer? The current version of the Candidate class wouldn’t be able to support this.

Let’s now refactor this class to make it simple, testable, and more manageable. The following code snippet illustrates the new version of the Candidate class. Note that the new Candidate class contains information pertaining to the candidate only.

   public class Candidate
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }        
        public decimal CurrentSalary { get; set; }
        public decimal ExpectedSalary { get; set; }
    }

The Recruiter and Interviewer classes are given below.

    public class Recruiter
    {
        public int RecruiterId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    public class Interviewer
    {
        public int InterviewerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Finally, the Interview class contains the candidate, the recruiter, and other information related to the interview as shown below.

    public class Interview
    {
        public int Id { get; set; }
        public int CandidateId { get; set; }
        public int RecruiterId { get; set; }
        public DateTime InterviewDate { get; set; }
        public string InterviewerRemarks { get; set; }
    }

And that’s it! If a candidate attends several rounds of interviews, then you might want to create multiple instances of the Interview class. That would suffice for storing candidate information for multiple interview rounds.

God objects are common in poorly designed systems. The solution is to refactor either the source code or the design or both, and create types (classes, structs, and interfaces) that are highly cohesive and loosely coupled. Despite all of its pitfalls, a God object is a good design choice in micro-controllers because centralized control is of utmost importance rather than flexibility and ease of maintenance.

How to do more in C#:

Copyright © 2020 IDG Communications, Inc.