Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional Design Pattern, Advantages [closed]

So, it's pretty well known that the infamous NullReferenceException is the most common exception in software products. I've been reading some articles, and found myself with the Optional approach.

Its aim is to create some kind of encapsulation around a nullable value

public sealed class Optional<T> where T : class {

    private T value;

    private Optional(T value) {
        this.value = value;
    }

    //Used to create an empty container
    public static Optional<T> Empty() {
        return new Optional(null);
    }

    //Used to create a container with a non-null value
    public static Optional<T> For(T value) {
        return new Optional(value);
    }

    //Used to check if the container holds a non-null value
    public bool IsPresent {
        get { return value != null; }
    }

    //Retrieves the non-null value
    public T Value {
        get { return value; }
    }
}

Afterwards, the now optional value can be returned like this:

public Optional<ICustomer> FindCustomerByName(string name)
{
    ICustomer customer = null;

    // Code to find the customer in database

    if(customer != null) {
        return Optional.Of(customer);
    } else {
        return Optional.Empty();
    }
}

And handled like this:

Optional<ICustomer> optionalCustomer = repository.FindCustomerByName("Matt");

if(optionalCustomer.IsPresent) {
     ICustomer foundCustomer = optionalCustomer.Value;
     Console.WriteLine("Customer found: " + customer.ToString());
} else {
     Console.WriteLine("Customer not found");
}

I don't see any improvement, just shifted complexity. The programmer must remember to check if a value IsPresent, in the same way he must remember to check if a value != null.

And if he forgets, he would get a NullReferenceException on both approaches.

What am I missing? What advantages (if any) does the Optional pattern provide over something like Nullable<T> and the null coalescing operator?

like image 924
Matias Cicero Avatar asked Jul 30 '14 18:07

Matias Cicero


People also ask

What are the advantages of design patterns?

Patterns don't provide solutions, they inspire solutions. Patterns explicitly capture expert knowledge and design tradeoffs and make this expertise widely available. Ease the transition to object-oriented technology.

What are design patterns their types and their advantages?

Design patterns are programming language independent strategies for solving a common problem. That means a design pattern represents an idea, not a particular implementation. By using design patterns, you can make your code more flexible, reusable, and maintainable.

What are the advantages of observer pattern?

The Observer pattern provides you with the following advantages: It supports the principle of loose coupling between objects that interact with each other. It allows sending data to other objects effectively without any change in the Subject or Observer classes. Observers can be added/removed at any point in time.

What is a disadvantage of the decorator pattern?

Disadvantages. High degree of flexibility. High complexity of software (especially decorator interface) Expansion of function of classes without inheritance. Not beginner-friendly.


2 Answers

Free your mind

If you think of Option as Nullable by a different name then you are absolutely correct - Option is simply Nullable for reference types.

The Option pattern makes more sense if you view it as a monad or as a specialized collection that contain either one or zero values.

Option as a collection

Consider a simple foreach loop with a list that cannot be null:

public void DoWork<T>(List<T> someList) {
    foreach (var el in someList) {
        Console.WriteLine(el);
    }
}

If you pass an empty list to DoWork, nothing happens:

DoWork(new List<int>());

If you pass a list with one or more elements in it, work happens:

DoWork(new List<int>(1));
// 1

Let's alias the empty list to None and the list with one entry in it to Some:

var None = new List<int>();
var Some = new List(1);

We can pass these variables to DoWork and we get the same behavior as before:

DoWork(None);

DoWork(Some);
// 1

Of course, we can also use LINQ extension methods:

Some.Where(x => x > 0).Select(x => x * 2);
// List(2)
// Some -> Transform Function(s) -> another Some

None.Where(x => x > 0).Select(x => x * 2);
// List()
// None -> None

Some.Where(x => x > 100).Select(x => x * 2);
// List() aka None
// Some -> A Transform that eliminates the element -> None

Interesting side note: LINQ is monadic.

Wait, what just happened?

By wrapping the value that we want inside a list we were suddenly able to only apply an operation to the value if we actually had a value in the first place!

Extending Optional

With that consideration in mind, let's add a few methods to Optional to let us work with it as if it were a collection (alternately, we could make it a specialized version of IEnumerable that only allows one entry):

// map makes it easy to work with pure functions
public Optional<TOut> Map<TIn, TOut>(Func<TIn, TOut> f) where TIn : T {
    return IsPresent ? Optional.For(f(value)) : Empty();
}

// foreach is for side-effects
public Optional<T> Foreach(Action<T> f) {
    if (IsPresent) f(value);
    return this;
}

// getOrElse for defaults
public T GetOrElse(Func<T> f) {
    return IsPresent ? value : f();
}

public T GetOrElse(T defaultValue) { return IsPresent ? value: defaultValue; }

// orElse for taking actions when dealing with `None`
public void OrElse(Action<T> f) { if (!IsPresent) f(); }

Then your code becomes:

Optional<ICustomer> optionalCustomer = repository.FindCustomerByName("Matt");

optionalCustomer
    .Foreach(customer =>
        Console.WriteLine("Customer found: " + customer.ToString()))
    .OrElse(() => Console.WriteLine("Customer not found"));

Not much savings there, right? And two more anonymous functions - so why would we do this? Because, just like LINQ, it enables us to set up a chain of behavior that only executes as long as we have the input that we need. For example:

optionalCustomer
    .Map(predictCustomerBehavior)
    .Map(chooseIncentiveBasedOnPredictedBehavior)
    .Foreach(scheduleIncentiveMessage);

Each of these actions (predictCustomerBehavior, chooseIncentiveBasedOnPredictedBehavior, scheduleIncentiveMessage) is expensive - but they will only happen if we have a customer to begin with!

It gets better though - after some study we realize that we cannot always predict customer behavior. So we change the signature of predictCustomerBehavior to return an Optional<CustomerBehaviorPrediction> and change our second Map call in the chain to FlatMap:

optionalCustomer
    .FlatMap(predictCustomerBehavior)
    .Map(chooseIncentiveBasedOnPredictedBehavior)
    .Foreach(scheduleIncentiveMessage);

which is defined as:

public Optional<TOut> FlatMap<TIn, TOut>(Func<TIn, Optional<TOut>> f) where TIn : T {
    var Optional<Optional<TOut>> result = Map(f)
    return result.IsPresent ? result.value : Empty();
}

This starts to look a lot like LINQ (FlatMap -> Flatten, for example).

Further possible refinements

In order to get more utility out of Optional we should really make it implement IEnumerable. Additionally, we can take advantage of polymorphism and create two sub-types of Optional, Some and None to represent the full list and the empty list case. Then our methods can drop the IsPresent checks, making them easier to read.

TL;DR

The advantages of LINQ for expensive operations are obvious:

someList
    .Where(cheapOp1)
    .SkipWhile(cheapOp2)
    .GroupBy(expensiveOp)
    .Select(expensiveProjection);

Optional, when viewed as a collection of one or zero values provides a similar benefit (and there's no reason it couldn't implement IEnumerable so that LINQ methods would work on it as well):

someOptional
    .FlatMap(expensiveOp1)
    .Filter(expensiveOp2)
    .GetOrElse(generateDefaultValue);

Further suggested reading

  • Option (F#)
  • When null is not enough (C#)
  • The neophytes guide to Scala Part 5: The Option type
  • The Marvel of Monads (C#)
  • Eric Lippert's series on LINQ and monads
like image 160
Sean Vieira Avatar answered Sep 28 '22 01:09

Sean Vieira


it would probally make more sense if you used something like this

interface ICustomer {

    String name { get; }
}

public class OptionalCustomer : ICustomer {

     public OptionalCustomer (ICustomer value) {
          this.value = value;
     }
     public static OptionalCustomer Empty() {
          return new OptionalCustomer(null);
     }

     ICustomer value;

     public String name { get { 
         if (value == null ) {
             return "No customer found";
         }
         return value.Name;
      }
    }

}

now if your pass an "empty" optional customer object you can still call the .Name property (without getting nullpointers)

like image 26
Batavia Avatar answered Sep 28 '22 02:09

Batavia