How to Reduce Cyclomatic Complexity Part 2: Special Case Pattern

by Zoran Horvat

In the previous article we have discussed Null Object pattern and its effect on avoiding null reference returned to the caller (see How to Reduce Cyclomatic Complexity: Null Object Pattern ).

The whole issue boils down to the statement the client has to implement when calling a method which just might return null to it:

public class HomeController
{
    ...
    private ActionResult SelectView(ReceiptDto receipt)
    {

        if (receipt == null)
            return new FailedPurchaseView();
        else
            return new SuccessfulPurchaseView(receipt);
    }
}

It is always like this. If null, do one thing, otherwise do the other thing.

Null Object pattern helps us remove this branching logic and treat all results uniformly. In the same example above, if the result of the operation which produces purchase receipt is guaranteed to be non-null, the controller could select appropriate view for any object it gets.

Pros and Cons of Null Object

Obvious benefit brought in by the Null Object is that control flow becomes the same for positive and negative branch. There are no branches based on null tests. If client wants to invoke some behavior on the result, it just invokes it. Real object performs real behavior. Null Object typically performs nothing.

On the flip side, Null Object carries no more information than null reference itself. If we had an e-commerce application in which purchase request returned Null Object receipt, we are left clueless about what went wrong. Is it insufficient funds in the user’s account, or the item is out of stock?

Negative consequences of Null Object pattern are recognized and treated by the Special Case pattern, and that is what we will demonstrate in this article.

Special Case Pattern

As the business operation progresses, more and more things may go wrong and prevent it from successfully producing a resulting object. In each of the steps along the way, we may decide to give up and return an object which indicates failure.

But not all failures are the same. Early on, user might not be eligible to perform a purchase. But if eligible, the desired item might be in stock. If in stock, though, user’s account might not have enough funds.

Instead of returning Null Object in all these cases, we could refine the result and basically return a different object every time. Those would still be a kind of null objects, but they would carry different meanings with them. One would be “insufficient funds”. The other one “site is down for maintenance”. Yet another one would be “out of stock”.

Such objects are referred to as Special Cases. We can construct and return special case objects as actual results of the operation. Only if all the business checks have passed and operation completed successfully all the way through, we return the real result object.

An Example of Implementing Special Cases

Let’s get back to the e-commerce example. This is part of the interface implemented by the application services:

public interface IApplicationServices
{
    ...
    IReceiptViewModel LoggedInUserPurchase(string itemName);
}

Presentation layer expects application layer to produce certain view model. Now we have a successful scenario, in which receipt view model contains actual data from the purchase, and a couple of failure scenarios.

Purchase may fail if any of these conditions is met:

  • Site is down for maintenance
  • User is not registered or not active
  • Item is out of stock or does not exist
  • User’s balance is low

For each of these cases we will create one specific class that will implement the IReceiptViewModel interface.

public class DownForMaintenance: IReceiptViewModel
{
}

This is the view model which indicates that site is down for maintenance. It carries no additional information right now, though we could add some features later. For example, this view model might carry the estimated time when the site will be up again. Such information is strictly tied to maintenance and makes no sense in any other failed purchase.

public class InvalidUser: IReceiptViewModel
{

    public string UserName { get; private set; }

    public InvalidUser(string userName)
    {
        this.UserName = userName;
    }
}

And here is the second Special Case implementation, this time dealing with an attempt to purchase an item and charge an inactive or nonexistent user for it. This time, the Special Case object carries additional information – username under which the purchase has failed.

Notice that InvalidUser Special Case may be generated after the test for DownForMaintenance has passed successfully. This is precisely the moment in which our application knows who the logged in user is. Therefore, we are using additional information to construct a more informative Special Case.

That is the general rule in Special Case design pattern. As you progress through the domain logic, more and more information is collected. All that can be used to produce a more informative Special Case object.

public class OutOfStock: IReceiptViewModel
{

    public string UserName { get; private set; }
    public string ItemName { get; private set; }

    public OutOfStock(string userName, string itemName)
    {
        this.UserName = userName;
        this.ItemName = itemName;
    }
}

And here is out of stock view model. This time, we know both the (valid) username and the item name. This object carries the message that specified item requested by the registered user does not exist in stock.

public class InsufficientFunds: IReceiptViewModel
{
    public string UserName { get; private set; }
    public decimal Amount { get; private set; }
    public string ItemName { get; private set; }

    public InsufficientFunds(string userName, decimal amount, string itemName)
    {
        this.UserName = userName;
        this.Amount = amount;
        this.ItemName = itemName;
    }
}

Finally, this is the Special Case view model which carries the information that certain user did not have sufficient money to pay the requested item which costs as much as it costs. Once again, you can see that the amount of information piles up as we progress through the business transaction.

Returning this particular Special Case object instead of plain Null Object, which contains no additional information, lets us build much more informative view in response to user’s action.

An Example of Using Special Cases

By this point, we have implemented a number of Special Case classes, one for each of the negative scenarios in the application. Using Special Cases is then straight-forward. In each case where we used to return null or Null Object instance, we just create and return appropriate Special Case instance.

public class ApplicationServices: IApplicationServices
{
    ...
    public IReceiptViewModel LoggedInUserPurchase(string itemName)
    {
        if (IsDownForMaintenance())
            return new DownForMaintenance();
        return this.domain.Purchase(Session.LoggedInUserName, itemName);
    }

    private bool IsDownForMaintenance()
    {
        return File.Exists("maintenance.lock");
    }
}

This is the application service implementation which can return DownForMaintenance Special Case in cases when application is down. If application is not down, application services just forward the call to the domain services and return whichever object was produced there.

Down the stream, inside the domain services, things may be much more complicated. Here is the implementation:

public class DomainServices: IDomainServices
{
    ...
    public IReceiptViewModel Purchase(string userName, string itemName)
    {

        User user = this.userRepository.Find(userName);
        if (user == null)
            return new InvalidUser(userName);

        Account account = this.accountRepository.FindByUser(user);

        return this.Purchase(user, account, itemName);

    }

    private IReceiptViewModel Purchase(User user, Account account, string itemName)
    {

        Product item = this.productRepository.Find(itemName);

        if (item == null)
            return new OutOfStock(user.UserName, itemName);

        ReceiptDto receipt = user.Purchase(item);
        MoneyTransaction transaction = account.Withdraw(receipt.Price);

        if (transaction == null)
            return new InsufficientFunds(user.UserName, receipt.Price, itemName);

        return receipt;

    }
}

In this case, domain services are returning Special Case instances whenever something goes wrong. Only at the very end of execution, if everything went fine, the domain service will return an actual receipt.

In this way, application and domain services have joined their efforts to tell the presentation layer what really happened when the user has requested a purchase. If purchase has failed, presentation layer will receive an object carrying all the available data about reasons of the failure. If purchase went fine, presentation layer will receive a receipt and will be able to present it to the user.

Conclusion

Special Case design pattern is an extension of the Null Object pattern idea. In many cases, Null Object is really not applicable, because it lacks information about why no more specific object could be produced. Special Case is a more specific object and it carries additional information.

Ultimate caller doesn’t have to treat Null Object or Special Case instances in any way special. Both of these design patterns are meant to provide a non-null object which fully implements the interface expected by the caller.

Consequence on code complexity is that the caller will not have to perform an if-then-else based on whether the object reference is null or non-null. References will always be non-null. The only significant difference between Null Object and Special Case design patterns is that the latter may carry more complicated behavior.

Hence, the Special Case pattern can be applied to more complicated business scenarios and still protect the caller from unnecessary if-then-else logic.


If you wish to learn more, please watch my latest video courses

About

Zoran Horvat

Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.

  1. Pluralsight
  2. Udemy
  3. Twitter
  4. YouTube
  5. LinkedIn
  6. GitHub