Class name and its scope

In projects I often encounter classes with a lot of functionalities. Examples of those class names are CustomerService or BookingService. Those classes implement mainly business features. During application development those classes can get quickly overweight. Their responsibility scope can grow infinitely. One of the reason of that can be too general class name that does not precisely define its scope. For instance let’s look at CustomerService class definition:

class CustomerService 
{
    public IEnumerable<Customer> GetCustomers()
    {
        /* return instances of Customer class */
    }

    public Customer GetCustomer(int id)
    {
        /* return instance of Customer class */
    }

    public int AddCustomer(Customer customer)
    {
        /* add new instance of Customer class */
    }

    public void UpdateCustomer(Customer customer)
    {
        /* update instance of Customer class */
    }

    public void RemoveCustomer(int id)
    {
        /* delete instance of Customer class */
    }
}

From the above class definition you can guess that it allows to manage objects of Customer type. However the class name itself says little about responsibility boundaries. In the future there can appear next requirements to add new functions related to Customer type. So there is a hazard that those new functions could be added to CustomerService type. That scenario can be repeated continuously. The result of that can be hard maintainable class which breaks most of the clean code and design rules.

To decrease the hazard the bigger class can be divided into smaller classes and for each class the name can be assigned more accurately:

class CustomerReader 
{
    public IEnumerable<Customer> GetCustomers()
    {
        /* return instances of Customer class */
    }

    public Customer GetCustomer(int id)
    {
        /* return instance of Customer class */
    }
}

class CustomerWriter 
{
    public int AddCustomer(Customer customer)
    {
        /* add new instance of Customer class */
    }

    public void UpdateCustomer(Customer customer)
    {
        /* update instance of Customer class */
    }

    public void RemoveCustomer(int id)
    {
        /* delete instance of Customer class */
    }
}

For instance CustomerService was divided into two classes: CustomerReader and CustomerWriter. First is used for fetching Customer objects and second is used for updating set of Customer. That solution helps to increase level of granularity so the classes contain less methods. However, the hazard was only decreased because divided classes can still increase their scopes. Maximum level of granularity can be achieved introducing CQRS pattern. Then for each operation a separate class will be responsible:

class GetCustomersQueryHandler
{
    public QueryResult Execute(GetCustomersQuery command)
    {
        /* return instances of Customer class */
    }
}

class GetCustomerQueryHandler
{
    public QueryResult Execute(GetCustomerQuery command)
    {
        /* return instance of Customer class */
    }
}

class AddCustomerCommandHandler
{
    public CommandResult Execute(AddCustomerCommand command)
    {
        /* add new instance of Customer class */
    }
}

class UpdateCustomerCommandHandler
{
    public CommandResult Execute(UpdateCustomerCommand command)
    {
        /* update instance of Customer class */
    }
}

class RemoveCustomerCommandHandler
{
    public CommandResult Execute(RemoveCustomerCommand command)
    {
        /* delete instance of Customer class */
    }
}

This way the classes have exactly defined scopes.  The class names describe precisely what the classes are responsible for. New functionalities will go into new types without risk to modify old ones. Of course, that solution is not perfect and it creates other issues, e.g.: maintaining huge number of classes or a lot of endpoints.

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

To sum up, class naming is not trivial task to solve. If class name describes class scope precisely we will not end up with god type that know and can do everything. So divide and rename classes in order to keep them always neat.

Leave a Reply

Your email address will not be published. Required fields are marked *