I’m doing a little ‘brushing up on the basics’ at the moment and as part of that effort I am working up some pattern examples, starting with creational patterns. These include staples such as Factory Method, Abstract Factory, Prototype, Singleton and so on but there are other creational patterns which weren’t in the Gang of Four (GoF) Design Patterns book. One of these is the Multiton. I don’t know what it’s provenance is, but it is an extension to the Singleton pattern which provides centralised access to a single collection making keys unique within scope. In my example, the singleton is declared as a static
member so it has application domain scope.
I like to work up examples that feel (at least somewhat) real-world as I find that these are easier to remember later on. For the Multiton pattern I decided to use the Rolodex which is simply a collection of cards for my purposes:
public sealed class Card
{
internal Card(string key)
{
this.Key = key;
}
public string Information
{
get;
set;
}
public string Key
{
get;
set;
}
}
The pattern defines that item creation is handled by a static factory if the key does not exist in the collection:
using System;
using System.Collections.ObjectModel;
using System.Linq;
public sealed class Rolodex
{
private static Rolodex _rolodex = new Rolodex();
private Rolodex()
{
this.Cards = new Collection<Card>();
}
private Collection<Card> Cards
{
get;
set;
}
public static Card Open(string key)
{
Card result = null;
lock (_rolodex)
{
result = _rolodex.Cards
.Where(x => string.Equals(x.Key, key, StringComparison.Ordinal))
.FirstOrDefault();
if (null == result)
{
result = new Card(key);
_rolodex.Cards.Add(result);
}
}
return result;
}
}
Here is a test which verifies the expected behaviour:
Xunit;
public sealed class MultitonFacts
{
[Fact]
public void rolodex_card()
{
string key = "John Doe";
Card expected = Rolodex.Open(key);
expected.Information = "john.doe@example.com";
Card actual = Rolodex.Open(key);
Assert.Same(expected, actual);
}
}
It’s worth pointing out that, as with all Singleton patterns, the plain vanilla pattern doesn’t lend itself to unit testing as-is. The answer is to provide a wrapper for mocking purposes. Here is an example of doing so for DateTime.UtcNow:
using System;
public static class DateTimeFactory
{
[ThreadStatic]
private static DateTime? _mock;
public static DateTime Today
{
get
{
DateTime value = DateTime.Today;
if (null != _mock)
{
value = _mock.Value.Date;
}
return value;
}
}
public static DateTime UtcNow
{
get
{
DateTime value = DateTime.UtcNow;
if (null != _mock)
{
value = _mock.Value;
}
return value;
}
}
public static DateTime? Mock
{
get
{
return _mock;
}
set
{
_mock = value;
}
}
public static void Reset()
{
DateTimeFactory.Mock = null;
}
}