Multiton
The multiton pattern is a design pattern which generalizes the singleton pattern. Whereas the singleton allows only one instance of a class to be created, the multiton pattern allows for the controlled creation of multiple instances, which it manages through the use of a map.
- Description
- Task
Implement a basic Multiton class or other structure and test that it works as intended. If your language does not support the object oriented paradigm, then try to emulate a multiton as best you can with the tools available.
If your language supports multithreading, then you may optionally implement a thread safe variant as well.
- Related task
FreeBASIC
Dim Shared As String permitido(2)
permitido(0) = "zero"
permitido(1) = "one"
permitido(2) = "two"
Dim Shared As Any Ptr instancias(2)
Type multiton
id As String
Declare Constructor(id As String)
End Type
Constructor multiton(id As String)
Dim k As Integer = -1
For i As Integer = 0 To Ubound(permitido)
If permitido(i) = id Then
k = i
Exit For
End If
Next
If k = -1 Then
Print "not permitido"
End 1
End If
If instancias(k) = 0 Then
this.id = id & "_" & Str(Int(Rnd * 999))
instancias(k) = @This
Else
This = *Cast(multiton Ptr, instancias(k))
End If
End Constructor
Dim a As multiton = multiton("zero")
Dim b As multiton = multiton("one")
Dim c As multiton = multiton("two")
Dim d As multiton = multiton("zero")
' Dim e As multiton = multiton() ' crashes
' Dim f As multiton = multiton("three") ' crashes
Dim e As multiton = multiton("one")
Dim f As multiton = multiton("two")
Print a.id
Print b.id
Print c.id
Print d.id
Print e.id
Print f.id
Sleep
- Output:
zero_329 one_328 two_531 zero_329 one_328 two_531
Julia
A registry (just in memory, not on disk) is used below instead of an enum. The registry is protected by a lock on the Multiton constructor, to prevent two threads creating the same object at the same time.
struct Multiton{T}
data::T
function Multiton(registry, refnum, data)
lock(registry.spinlock)
if 0 < refnum <= registry.max_instances && registry.instances[refnum] isa Nothing
multiton = new{typeof(data)}(data)
registry.instances[refnum] = multiton
unlock(registry.spinlock)
return multiton
else
unlock(registry.spinlock)
error("Cannot create instance with instance reference number $refnum")
end
end
function Multiton(registry, refnum)
if 0 < refnum <= registry.max_instances && registry.instances[refnum] isa Multiton
return registry.instances[refnum]
else
error("Cannot find a Multiton in registry with instance reference number $refnum")
end
end
end
struct Registry
spinlock::Threads.SpinLock
max_instances::Int
instances::Vector{Union{Nothing, Multiton}}
Registry(maxnum) = new(Threads.SpinLock(), maxnum, fill(nothing, maxnum))
end
reg = Registry(3)
m0 = Multiton(reg, 1, "zero")
m1 = Multiton(reg, 2, 1.0)
m2 = Multiton(reg, 3, [2])
m3 = Multiton(reg, 1)
m4 = Multiton(reg, 2)
for m in [m0, m1, m2, m3, m4]
println("Multiton is $m")
end
# produce error
# m3 = Multiton(reg, 4, "three")
# produce error
m5 = Multiton(reg, 5)
- Output:
Multiton is Multiton{String}("zero") Multiton is Multiton{Float64}(1.0) Multiton is Multiton{Vector{Int64}}([2]) Multiton is Multiton{String}("zero") Multiton is Multiton{Float64}(1.0) ERROR: LoadError: Cannot find a Multiton in registry with instance reference number 5
Perl
# 20211215 Perl programming solution
use strict;
use warnings;
BEGIN {
package MultitonDemo ;
use Moo;
with 'Role::Multiton';
has [qw(attribute)] => ( is => 'rw');
$INC{"MultitonDemo.pm"} = 1;
}
use MultitonDemo;
print "We create several instances and compare them to see if multiton is in effect.\n";
print "\n";
print "Instance Constructor Attribute\n";
print "\n";
print "0 multiton 0\n";
print "1 multiton 1\n";
print "2 multiton 0\n";
print "3 new 0\n";
print "4 new 0\n";
my $inst0 = MultitonDemo->multiton (attribute => 0);
my $inst1 = MultitonDemo->multiton (attribute => 1);
my $inst2 = MultitonDemo->multiton (attribute => 0);
my $inst3 = MultitonDemo->new (attribute => 0);
my $inst4 = MultitonDemo->new (attribute => 0);
print "\n";
if ($inst0 eq $inst1) { print "Instance0 and Instance1 share the same object\n" };
if ($inst1 eq $inst2) { print "Instance1 and Instance2 share the same object\n" };
if ($inst0 eq $inst2) { print "Instance0 and Instance2 share the same object\n" };
if ($inst0 eq $inst3) { print "Instance0 and Instance3 share the same object\n" };
if ($inst3 eq $inst4) { print "Instance3 and Instance4 share the same object\n" };
- Output:
We create several instances and compare them to see if multiton is in effect. Instance Constructor Attribute 0 multiton 0 1 multiton 1 2 multiton 0 3 new 0 4 new 0 Instance0 and Instance2 share the same object
Phix
No attempt is made for thread safety, since multiple threads accessing these would need
their own locking anyway, not that it is difficult to invoke enter_cs() and leave_cs().
I thought about adding a get_multiton() function to avoid calling new() all the time, but it would (probably/almost certainly) just invoke new() itself anyway.
Put this (up to "end class") in a separate file, to keep allowed and instances private. Classes are not [yet] supported under pwa/p2js.
Technically I suppose this should really use new_dict()/getd()/setd(), but I'm quite sure you'll cope somehow..
without javascript_semantics sequence allowed = {"zero","one","two"}, instances = {NULL,NULL,NULL} public class Multiton public string id function Multiton(string id) integer k = find(id,allowed) if k=0 then crash("not allowed") end if if instances[k] = NULL then this.id = id&sprintf("_%d",rand(999)) instances[k] = this end if return instances[k] end function end class Multiton a = new({"zero"}), b = new({"one"}), c = new({"two"}), d = new({"zero"}), -- e = new(), -- crashes -- f = new({"three"}) -- crashes e = new({"one"}), f = new({"two"}) ?a.id ?b.id ?c.id ?d.id ?e.id ?f.id
- Output:
"zero_651" "one_111" "two_544" "zero_651" "one_111" "two_544"
Python
#!/usr/bin/python
import threading
class Multiton:
def __init__(self, registry, refnum, data=None):
with registry.lock:
if 0 < refnum <= registry.max_instances and registry.instances[refnum] is None:
self.data = data
registry.instances[refnum] = self
elif data is None and 0 < refnum <= registry.max_instances and isinstance(registry.instances[refnum], Multiton):
self.data = registry.instances[refnum].data
else:
raise Exception("Cannot create or find instance with instance reference number {}".format(refnum))
class Registry:
def __init__(self, maxnum):
self.lock = threading.Lock()
self.max_instances = maxnum
self.instances = [None] * maxnum
reg = Registry(3)
m0 = Multiton(reg, 1, "zero")
m1 = Multiton(reg, 2, 1.0)
m2 = Multiton(reg, 1)
m3 = Multiton(reg, 2)
for m in [m0, m1, m2, m3]:
print("Multiton is {}".format(m.data))
# produce error
#m2 = Multiton(reg, 3, [2])
# produce error
# m3 = Multiton(reg, 4, "three")
# produce error
# m5 = Multiton(reg, 5)
Raku
Tried to translate the C# example at WP but not sure if my interpretation/implementation is correct
# 20211001 Raku programming solution
enum MultitonType < Gold Silver Bronze >;
class Multiton {
my %instances = MultitonType.keys Z=> $ ⚛= 1 xx * ;
has $.type is rw;
method TWEAK { $.type = 'Nothing' unless cas(%instances{$.type}, 1, 0) }
}
race for ^10 -> $i {
Thread.start(
sub {
# sleep roll(^2);
my $obj = Multiton.new: type => MultitonType.roll;
say "Thread ", $i, " has got ", $obj.type;
}
);
}
- Output:
Thread 5 has got Bronze Thread 9 has got Gold Thread 7 has got Nothing Thread 8 has got Nothing Thread 3 has got Nothing Thread 2 has got Nothing Thread 1 has got Nothing Thread 0 has got Silver Thread 4 has got Nothing Thread 6 has got Nothing
Wren
This more or less follows the lines of the C# example in the linked Wikipedia article.
Although all Wren code runs within the context of a fiber (of which there can be thousands) only one fiber can run at a time and so the language is effectively single threaded. Thread-safety is therefore never an issue.
import "./dynamic" for Enum
var MultitonType = Enum.create("MultitonType", ["zero", "one", "two"])
class Multiton {
// private constructor
construct new_(type) {
_type = type
}
static getInstance(type) {
if (!(0...MultitonType.members.count).contains(type)) {
Fiber.abort("Invalid MultitonType member.")
}
if (!__instances) __instances = {}
if (!__instances.containsKey(type)) __instances[type] = new_(type)
return __instances[type]
}
type { _type }
toString { MultitonType.members[_type] }
}
var m0 = Multiton.getInstance(MultitonType.zero)
var m1 = Multiton.getInstance(MultitonType.one)
var m2 = Multiton.getInstance(MultitonType.two)
System.print(m0)
System.print(m1)
System.print(m2)
var m3 = Multiton.getInstance(3) // produces an error
- Output:
zero one two Invalid MultitonType member. [./multiton line 13] in getInstance(_) [./multiton line 33] in (script)