2018-03-25

A valid usage of Singleton Pattern (with Null object Pattern)

programming, oop, tips

banner

title: A valid usage of Singleton Pattern (with Null object Pattern) date: "2018-03-25" banner: ./images/featured-image-3.jpg published_at: "2018-03-26T02:30:33.000Z" tags: "programming, oop, tips" author: Sung M. Kim

Photo by Chetan Menaria on Unsplash

Singleton has a bad rep for being an anti-pattern. You might have been burned bad with it.

I learned from Michael Outlaw from Coding Blocks podcast episode 16 that Singleton pattern can come in handy in conjunction with Null object pattern.

Let's dive in.

Why implement a Null object as a singleton?

42 minutes into the Coding Blocks Episode 16, Michael Outlaw explains two reasons.

  1. "If you have two versions of null objects, they should be identical, why waste memory to have same nothingness repeated".
  2. For equality checks - Each null object can be compared by reference.

Depending on a situation you might not even need to do a equality check as a null object usually does nothing (as you can see in the example section below.)

TL;DR - You can stop reading here.

Example

Here is an example of how you can use a Singleton pattern with a Null object pattern.

Suppose that there is a simple factory (PaymentStrategyFactory) that returns a strategy object instance for processing a payment given a provider name.

public static class PaymentStrategyFactory
{
public static IPaymentStrategy Create(string paymentProvider)
{
switch (paymentProvider)
{
case "Paypal": return new PaypalPaymentStrategy();
case "ApplePay": return new ApplePayPaymentStrategy();
default: return NullPaymentStrategy.Instance;
}
}
}

PayPal & ApplePay strategies return "Successful" or "Failed" process status at random.

public abstract class RandomPaymentStrategy : IPaymentStrategy
{
public ProcessStatus Process(double amount)
{
var random = new Random();
var randomValue = random.Next(0, 2);
return (ProcessStatus)Math.Min(randomValue, 2);
}
}
public class PaypalPaymentStrategy : RandomPaymentStrategy { }
public class ApplePayPaymentStrategy : RandomPaymentStrategy { }
// "NoOp" means No Operation: https://en.wikipedia.org/wiki/NOP
// First value is assigned a value of 0 by default.
public enum ProcessStatus { Successful, Failed, NoOp }

If a provider name is not supported, the PaymentStrategyFactory simply returns an object of type NullPaymentStrategy, which implements IPaymentStrategy.

Singleton instance is achieved with a private constructor and a static Instance property.

public class NullPaymentStrategy : IPaymentStrategy
{
public static readonly IPaymentStrategy Instance = new NullPaymentStrategy();
private NullPaymentStrategy() { }
public ProcessStatus Process(double amount)
{
return ProcessStatus.NoOp;
}
}
public interface IPaymentStrategy
{
ProcessStatus Process(double amount);
}

Let's put them together and process payments.

private static void ProcessPayments()
{
var paymentProviders = new[] {
"Paypal", "ApplePay", "GooglePay",
"Braintree", "PayPal", "ApplePay",
"ApplePay", "BadPaymentProvider" };
foreach (var paymentProvider in paymentProviders)
{
var paymentProcessor = PaymentStrategyFactory.Create(paymentProvider);
WriteLine($"===== Processing with '{paymentProvider}' Provider =====");
// There is no need to check if the payment processor is null here
// Reduced Cyclomatic Complexity.
var paymentStatus = paymentProcessor.Process(111);
WriteLine($"--> {paymentStatus}");
}
}

Results of ProcessPayments().

===== Processing with 'Paypal' Provider =====
--> Successful
===== Processing with 'ApplePay' Provider =====
--> Failed
===== Processing with 'GooglePay' Provider =====
--> Successful
===== Processing with 'Braintree' Provider =====
--> NoOp
===== Processing with 'PayPal' Provider =====
--> Successful
===== Processing with 'ApplePay' Provider =====
--> Successful
===== Processing with 'ApplePay' Provider =====
--> Failed
===== Processing with 'BadPaymentProvider' Provider =====
--> NoOp

Conclusion

Googling Singleton Anti Pattern results in many reasons why Singleton pattern is bad.

But when used judiciously, it can improve your code quality/memory/speed.

Source code is available on GitHub.