Multiton
- Description
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.
- 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
Julia[edit]
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[edit]
# 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[edit]
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"
Raku[edit]
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[edit]
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)