DEV Community

Cover image for Validation and the Single Responsibility Principle in Object Orientated Programming
Daniel Benzie
Daniel Benzie

Posted on

Validation and the Single Responsibility Principle in Object Orientated Programming

When writing software, you will spend a large portion of time creating code that takes an object, validates it and then performs some actions. One of the core tenets of SOLID code is the Single Responsibility Principle, and this means there should be a separation of the code that validates the object and the code that executes the action.

An example scenario might be: User A submits payment of £100 to User B.

On the surface, this seems very simple and can be implemented using the following logic. The examples in this post will be PHP but apply to any object-orientated language

We use a PaymentManager to transfer the requested funds between two users from the above code. In this scenario, the PaymentRequest object holds the information about the origin/destination and funds sent.

Whilst this meets the criteria of moving the funds, there are checks that the system needs to conduct before executing such a request in the real world. Some of the checks could include:

  • Is the origin account active?
  • Is the destination account active?
  • Does the origin account have available funds to cover the payment amount?
  • Is the PaymentRequest amount valid? e.g. it is not logical to pay an amount of zero (or less)

The criteria listed are crucial to the application, and as a result, it may be tempting to conduct these checks similar to the below:

This code works, but it violates SRP as the function is now validating the request and calling the payment manager. As the business requirements get more complex, this class will continue to grow and become difficult to maintain. 

For example - functionality has been added to the platform, enabling users to block one another. As a result, a user should not be able to send funds to a user who has blocked them. This would require an additional, conditional statement to be added to the function.

Validation Classes

A much better way of handling this scenario is to abstract the validation logic away from the execution. This can be achieved by implementing the below:

The validation logic has been extracted to its own class. This separates the code nicely and ensures we will not be forced to update this class every time rules are changed or added. It also has the benefit of making our unit tests much easier to write. To test the MakePaymentJob we can mock the validate() method as true or false and test whether our paymentManager->makePayment() is called.

The validation class itself functions as below:

In addition to extracting the rules to a validation class, I moved each rule to a private method. The descriptive naming of the method provides better context and improves readability whilst creating an excellent base for writing unit tests. I wrote a post about how we might test code like this previously - you can check it out here.

Improvements

The validation pattern above is vastly improved over the original code; however, it could still do with a few adjustments. For example, returning false from a failed validation attempt provides no context for the calling client why the request failed. If the paymentRequest failed because the user did not have enough funds, this is something we would want to communicate to the user.

We can achieve this by making minor adjustments to the PaymentValidator class.

The above is a crude demonstration - when running validations across a codebase it would be better to create validation methods for common assertions. In the example above it is a simple greater than conditional. There could easily be an agnostic method in a validation namespace to handle standard rules. Classes wishing to implement rules can then inject the required rules.

Summary

There are plenty of ways to handle the actual validation of the objects, but the important part is to ensure this logic is separate. It will make extending the rules much easier in the future and increase the testability of your code.

Discussion (0)