The goal is to create a Singleton and pass it a parameter that is required for the construction and initialization of the class, then preventing any changes to be made to the passed parameter (just like a readonly field being set by an argument passed to a constructor).
For instance:
- Sockets
- Hosts
- Databases
- Repositories
- (Any instance that requires at least one argument in order to construct)
I am having a tough time coming to terms with this design, and I am quite certain that there is a pitfall or a loose-end to this implementation of a Singleton combined with a Builder Pattern, to mimic readonly fields set by constructor arguments.
Example Implementation
In this example, I am trying to get a Singleton of Host
, where I would like the enum EnvironmentTypes
to be treated like a readonly
field usually found in classes that have parameters passed into the constructor.
EnvironmentTypes Enum
public enum EnvironmentTypes
{
Production,
Staging,
Development
}
IHost Interface
public interface IHost
{
string Name { get; set; }
}
Host Class
public sealed class Host : IHost
{
#region Singleton
private static readonly Lazy<Host> _instance = new Lazy<Host>(() => new Host());
public static Host Instance { get { return _instance.Value; } }
#endregion
private static bool _isInstantiated;
private static EnvironmentTypes _environment;
private string _name;
internal static EnvironmentTypes Environment
{
get { return _environment; }
internal set
{
if (_isInstantiated) throw new InvalidOperationException(nameof(_environment) +" cannot be set once an instance is created.");
_environment = value;
}
}
public string Name
{
get { return _name; }
set { _name = value; }
}
static Host()
{
_isInstantiated = false;
_environment = EnvironmentTypes.Production;
}
private Host()
{
_isInstantiated = true;
_name = "My Server";
}
}
HostBuilder Class
public sealed class HostBuilder
{
private readonly EnvironmentTypes _environment;
private string _name;
public HostBuilder(EnvironmentTypes environment)
{
_environment = environment;
}
public HostBuilder SetName(string name)
{
_name = name;
return this;
}
public IHost Build()
{
Host.Environment = _environment;
Host host = Host.Instance;
host.Name = _name;
return host;
}
}
Implementation
class Foo
{
void UsingTheBuilder()
{
// probably over-kill
HostBuilder builder = new HostBuilder(EnvironmentTypes.Development)
.SetName("Bingo");
IHost host = builder.Build();
//host.Environment is not available, great!
host.Name = "Renamed Server"; // works as expected.
}
void ManualConfiguration()
{
Host.Environment = EnvironmentTypes.Development;
Host host = Host.Instance;
host.Name = "Bingo";
Host.Environment = EnvironmentTypes.Staging; // throws! Hoped to prevent
// the developer from doing this.
}
}
Random Notes: It would be great if I could restrict access from getting to the static properties of Host
, so that I can totally avoid anyone trying to set the Host.Environment
static property and throwing an exception -- note how the HostBuilder
shields that from happening as it is a readonly
field.
Environment
can't be an instance property and also accessed via theInstance
? \$\endgroup\$ – t3chb0t May 24 '17 at 17:55Build
method can only be called once because the second call will throw an exception whenEnvironment
is set again. Is that intentional? \$\endgroup\$ – 404 May 24 '17 at 18:21