What is the Multiton Design Pattern?
The multiton pattern is a variation of the singleton pattern that allows a single class to have multiple instances, with each instance having a unique identifier. It is useful in cases where a singleton would be too limiting, but the number of instances is still limited and known ahead of time.
The multiton pattern is similar to the singleton pattern in that it has a private constructor and a static method that returns an instance of the class. However, the static method takes an argument that specifies the unique identifier for the desired instance. The method then checks if an instance with that identifier has already been created, and if so, returns it. If no such instance exists, it creates a new instance and stores it for future reference.
The multiton pattern is a way to make sure that a class only has a limited number of instances, and each instance has a unique identifier. This is useful when you want to make sure that there aren’t too many instances of a class, but you still want to be able to have more than one instance.
To use the multiton pattern, you create a class with a private constructor. This means that no other class can create an instance of your class directly. Instead, you create a static method that creates and returns an instance of your class. This static method takes an argument that specifies a unique identifier for the instance you want to get.
The first time you call the static method with a particular unique identifier, the method creates a new instance of your class and stores it. Then, every time you call the static method with the same unique identifier, it returns the same instance that it created the first time.
This way, you can make sure that there is only ever one instance of your class for each unique identifier. And you can use the unique identifier to tell different instances of your class apart.
Here is an example of a multiton class in C#:
public class Multiton
{
private static readonly Dictionary<string, Multiton> Instances = new Dictionary<string, Multiton>();
private readonly string _identifier;
private Multiton(string identifier)
{
_identifier = identifier;
}
public static Multiton GetInstance(string identifier)
{
if (!Instances.ContainsKey(identifier))
{
Instances[identifier] = new Multiton(identifier);
}
return Instances[identifier];
}
public string GetIdentifier()
{
return identifier;
}
}
To use this class, you would call Multiton.GetInstance
with a unique identifier, and it would return an instance of Multiton
with that identifier. If an instance with the given identifier has already been created, it would return the existing instance; otherwise, it would create a new instance and return it.
Here are some benefits to using the multiton design pattern:
There are several benefits to using the multiton pattern:
- It allows you to limit the number of instances of a class, while still allowing for multiple instances. This can be useful in cases where a singleton would be too limiting, but you don’t need an unbounded number of instances.
- It allows you to easily access specific instances of a class using a unique identifier. This can be useful in cases where you need to perform actions on a specific instance of a class, rather than all instances.
- It can improve the performance of an application, because it allows you to reuse existing instances rather than creating new ones. This can be especially beneficial if creating a new instance of a class is expensive in terms of memory or processing power.
- It can help to enforce the single responsibility principle, because it allows you to create separate instances of a class for different tasks or responsibilities. This can make it easier to design and maintain large, complex systems.
- It can make it easier to test an application, because it allows you to create specific instances of a class with known states for testing purposes.
Overall, the multiton pattern can be a useful tool in object-oriented programming, especially when used in combination with other design patterns.
By “combination with other design patterns,” I mean that the multiton pattern can be used in conjunction with other design patterns to achieve a specific design goal.
For example, the multiton pattern can be used in combination with the factory pattern to create a factory that produces multiton instances. This can be useful in cases where you want to create a limited number of instances of a class, but you want the flexibility to create different types of instances based on specific input.
The multiton pattern can also be used in combination with the prototype pattern, where the multiton instances serve as prototypes for creating new objects. This can be useful in cases where you want to create multiple instances of a class with similar characteristics, but you don’t want to create them from scratch each time.
Overall, the multiton pattern can be a useful addition to a designer’s toolkit, and can be used in a variety of situations to solve specific design problems.
Imagine that you are building an application to manage a fleet of vehicles for a transportation company. Each vehicle has a unique identifier, such as a license plate number, and the company has a limited number of vehicles in its fleet.
In this case, you could use the multiton pattern to create a Vehicle
class, with each instance representing a specific vehicle in the fleet. The static method for getting an instance of the Vehicle
class would take a license plate number as an argument, and it would return the corresponding Vehicle
instance.
Using the multiton pattern in this way would allow you to ensure that there is only ever one Vehicle
instance for each license plate number, while still allowing you to create multiple instances to represent the entire fleet of vehicles. It would also make it easy to access specific vehicles by their license plate number, and it would allow you to reuse existing Vehicle
instances rather than creating new ones each time.
public class Vehicle
{
private static readonly Dictionary<string, Vehicle> Instances = new Dictionary<string, Vehicle>();
private readonly string licensePlate;
private readonly int capacity;
private readonly string make;
private readonly string model;
private Vehicle(string licensePlate, int capacity, string make, string model)
{
this.licensePlate = licensePlate;
this.capacity = capacity;
this.make = make;
this.model = model;
}
public static Vehicle GetInstance(string licensePlate)
{
if (!Instances.ContainsKey(licensePlate))
{
// Load the vehicle's details from a database or some other source
int capacity = LoadCapacityFromDatabase(licensePlate);
string make = LoadMakeFromDatabase(licensePlate);
string model = LoadModelFromDatabase(licensePlate);
Instances[licensePlate] = new Vehicle(licensePlate, capacity, make, model);
}
return Instances[licensePlate];
}
public string GetLicensePlate()
{
return licensePlate;
}
public int GetCapacity()
{
return capacity;
}
public string GetMake()
{
return make;
}
public string GetModel()
{
return model;
}
}
To use this class, you would call Vehicle.GetInstance
with a license plate number, and it would return the corresponding Vehicle
instance. If an instance for that license plate has not yet been created, it would load the vehicle's details from a database or some other source and create a new Vehicle
instance. If an instance for that license plate has already been created, it would return the existing instance.
This way, you can ensure that there is only ever one Vehicle
instance for each license plate number, and you can easily access specific vehicles by their license plate.
There are several ways you can implement the multiton pattern, depending on your specific needs and constraints. Let me explain most common ways.
1. Using a private constructor and a static method
This is the most common way to implement the multiton pattern, as shown in the examples I provided earlier. In this approach, the static method serves as a factory for creating and returning instances of the class.
public class Multiton
{
private static readonly Dictionary<string, Multiton> Instances = new Dictionary<string, Multiton>();
private readonly string identifier;
private Multiton(string identifier)
{
this.identifier = identifier;
}
public static Multiton GetInstance(string identifier)
{
if (!Instances.ContainsKey(identifier))
{
Instances[identifier] = new Multiton(identifier);
}
return Instances[identifier];
}
public string GetIdentifier()
{
return identifier;
}
}
To use this class, you would call Multiton.GetInstance
with a unique identifier, and it would return an instance of Multiton
with that identifier. If an instance with the given identifier has already been created, it would return the existing instance; otherwise, it would create a new instance and return it.
This approach uses a private constructor to prevent other classes from creating instances of the Multiton
class directly. Instead, they must use the GetInstance
method to get an instance of the class. This allows you to control the number of instances that are created, and to ensure that each instance has a unique identifier.
2. Using a static constructor
Instead of a static method, you can use a static constructor to create and initialize instances of the multiton class. A static constructor is a special type of constructor that is called when the class is first loaded, and it is executed before any instance of the class is created.
public class Multiton
{
private static readonly Dictionary<string, Multiton> Instances = new Dictionary<string, Multiton>();
private readonly string identifier;
static Multiton()
{
// Initialize the list of instances here
// This could be done by reading from a database or some other source
Instances["A"] = new Multiton("A");
Instances["B"] = new Multiton("B");
Instances["C"] = new Multiton("C");
}
private Multiton(string identifier)
{
this.identifier = identifier;
}
public static Multiton GetInstance(string identifier)
{
return Instances[identifier];
}
public string GetIdentifier()
{
return identifier;
}
}
In this example, the static constructor is used to initialize the list of Multiton
instances. When the Multiton
class is first loaded, the static constructor is executed and creates the instances with identifiers "A", "B", and "C".
To use this class, you would call Multiton.GetInstance
with a unique identifier, and it would return the corresponding Multiton
instance. The GetInstance
method does not create any new instances; it simply returns the instance that was created by the static constructor.
This approach can be useful if you want to create and initialize all of the multiton instances at once, rather than creating them on demand. It can also be useful if you want to create instances with specific characteristics, rather than creating them all with the same default values.
3. Using a factory pattern
You can use the factory pattern in combination with the multiton pattern to create a factory that produces multiton instances. This can be useful in cases where you want to create instances of different types or with different characteristics, based on specific input.
public interface IMultitonFactory
{
Multiton GetInstance(string identifier);
}
public class MultitonFactory : IMultitonFactory
{
private static readonly Dictionary<string, Multiton> Instances = new Dictionary<string, Multiton>();
public Multiton GetInstance(string identifier)
{
if (!Instances.ContainsKey(identifier))
{
// Load the instance's details from a database or some other source
int value = LoadValueFromDatabase(identifier);
Instances[identifier] = new Multiton(identifier, value);
}
return Instances[identifier];
}
}
public class Multiton
{
private readonly string identifier;
private readonly int value;
public Multiton(string identifier, int value)
{
this.identifier = identifier;
this.value = value;
}
public string GetIdentifier()
{
return identifier;
}
public int GetValue()
{
return value;
}
}
In this example, the MultitonFactory
class is a factory that produces Multiton
instances. It has a GetInstance
method that takes an identifier as an argument, and it returns the corresponding Multiton
instance. If an instance with the given identifier has not yet been created, it loads the instance's details from a database or some other source and creates a new Multiton
instance. If an instance with the given identifier has already been created, it returns the existing instance.
To use this factory, you would create an instance of MultitonFactory
and call the GetInstance
method with the identifier of the Multiton
instance you want to get. The factory will either return an existing instance with the given identifier, or it will create a new instance and return it.
Using this approach, you can create a flexible and reusable factory that can produce shared Multiton
instances on demand. This can be useful in cases where you need to use a large number of Multiton
instances in your application, and you want to ensure that they are shared and thread-safe.
Overall, the combination of the factory pattern and the multiton pattern can be a useful way to create shared instances in a flexible and reusable way, while also ensuring that they are thread-safe and can be used safely by multiple threads.
4. Using lazy initialization
You can use lazy initialization to create multiton instances on demand, rather than all at once. This can be useful in cases where the number of instances is large, or where creating all instances at once would be too expensive in terms of memory or processing power.
public class Multiton
{
private static readonly Dictionary<string, Lazy<Multiton>> Instances = new Dictionary<string, Lazy<Multiton>>();
private readonly string identifier;
private Multiton(string identifier)
{
this.identifier = identifier;
}
public static Multiton GetInstance(string identifier)
{
if (!Instances.ContainsKey(identifier))
{
Instances[identifier] = new Lazy<Multiton>(() => new Multiton(identifier));
}
return Instances[identifier].Value;
}
public string GetIdentifier()
{
return identifier;
}
}
In this example, the Instances
dictionary is a collection of Lazy<Multiton>
objects, rather than Multiton
objects directly. The Lazy<T>
class is a built-in class in C# that provides lazy initialization of an object.
To use this class, you would call Multiton.GetInstance
with a unique identifier, and it would return an instance of Multiton
with that identifier. If an instance with the given identifier has not yet been created, it would create a new Lazy<Multiton>
object and store it in the Instances
dictionary. The first time you access the Value
property of the Lazy<Multiton>
object, it will create a new Multiton
instance and return it. On subsequent accesses, it will return the same instance.
This approach allows you to create multiton instances on demand, rather than creating them all at once. This can be useful in cases where the number of instances is large, or where creating all instances at once would be too expensive in terms of memory or processing power.
In the examples I provided, none of the multiton classes use explicit locking to synchronize access to the shared instances. This is because in C#, the Dictionary<TKey, TValue>
class is thread-safe for both reads and writes, as long as you do not modify the dictionary while you are enumerating over it (use lock statement if so). This means that you can safely access the Instances
dictionary from multiple threads without the need for explicit locking.
The problem with using lazy initialization in a multiton pattern is that it is not thread-safe. If multiple threads try to access the same instance concurrently, you may encounter race conditions or other threading issues.
To prevent this from happening, you need to use explicit synchronization techniques (such as a lock
statement) to synchronize access to the instances. This ensures that only one thread can execute the code that creates or retrieves the instances at a time, and it prevents race conditions or other threading issues from occurring.
Here is an example of how you might modify the forth example to use explicit locking:
public class Multiton
{
private static readonly Dictionary<string, Lazy<Multiton>> Instances = new Dictionary<string, Lazy<Multiton>>();
private static readonly object LockObject = new object();
private readonly string identifier;
private Multiton(string identifier)
{
this.identifier = identifier;
}
public static Multiton GetInstance(string identifier)
{
lock (LockObject)
{
if (!Instances.ContainsKey(identifier))
{
Instances[identifier] = new Lazy<Multiton>(() => new Multiton(identifier));
}
}
return Instances[identifier].Value;
}
public string GetIdentifier()
{
return identifier;
}
}
In this example, the LockObject
object is used to synchronize access to the Instances
dictionary. The lock
statement is used to ensure that only one thread can execute the code inside the block at a time. This prevents multiple threads from attempting to modify the dictionary simultaneously, which could cause race conditions or other threading issues.
Just in case, I recommend that to using lock statement to avoid any kind of threading issues.
Conclusion
The multiton pattern is a creational design pattern that allows you to create a fixed number of instances of a class, with each instance having a unique identifier. The multiton pattern is similar to the singleton pattern, but it allows you to create multiple instances instead of just one.
The main purpose of using the multiton pattern is to ensure that you have a limited number of instances of a class, and that each instance has a unique identifier. This can be useful in situations where you need to manage a fixed number of resources, such as database connections or network sockets.
By using the multiton pattern, you can ensure that you have a consistent and well-defined way of accessing these resources, and you can avoid the overhead of creating unnecessary instances of the class.
And surely, I recommend that using lock statement for any kind of threading issues.
Thanks for reading! If you found the article helpful, you can clap and follow. So you will notified of new articles.
# Reference
It was created with the help of ChatGPT AI.