jeudi 1 octobre 2015

Prototype pattern: ensure clone is in a valid state

LogEvent represents information like log level, message, user, process name, ... Some of these properties' values require pretty much effort for generation, e. g. the process name. Those properties' generated values are usually not changed, BUT despite this fact it should be possible to change them.

I considered the prototype pattern starting with a protoype, whose generic properties are pre-allocated. The protoype stays the same object during the lifetime of the application, but its properties' values might change as described above. New LogEvent objects should use the current prototype's values, objects created before the change should continue using the old values, that means, referencing the prototype from the "real" LogEvent object is not an option.

However the "real" LogEvent requires some properties to be not null, whereas this requirement is not useful for the prototype. I would like to prevent invalid objects of LogEvent. However if I use usual protoype pattern I would have to add a constructor to create the prototype, but this constructor would not create a valid object and I want to avoid, that an invalid object (the prototype itself or a clone of it) is used accidentally.

I spent some time on searching a solution, but the approaches listed below are pretty ugly. I hope, that there is an elegant solution. Meanwhile I tend to option 3, because 1 and 2 do not seem to be clean.

General structure

public interface ILogEvent
{
    string PreAllocatedProperty1 { get; set; }
    string PreAllocatedProperty2 { get; set; }
    string IndividualProperty1 { get; set; }
    string IndividualProperty2 { get; set; }
}

Option 1

Pros - LogEventPrototype can not be used as ILogEvent. - properties do not have to be declared in multiple classes Cons - properties have to be mapped manually - static methods => interface for prototypes not possible

class LogEventPrototype
{
    public string PreAllocatedProperty1 { get; set; }
    public string PreAllocatedProperty2 { get; set; }
    public string IndividualProperty1 { get; set; }
    public string IndividualProperty2 { get; set; }
    public LogEventPrototype() { GeneratePreAllocatedProperties(); }

    private void GeneratePreAllocatedProperties()
    {
        // if you invoke the helper functions later again, 
        // they might return different results (e. g.: user identity, ...)
        PreAllocatedProperty1 = Helper.ComplexFunction();
        PreAllocatedProperty2 = Helper.AnotherComplexFunction();
    }
}

class LogEvent : LogEventPrototype, ILogEvent
{
    // just for creating the prototype, object will be in an INVALID state
    private LogEvent() : base() {}
    // object will be in a VALID state
    public LogEvent(string individualProperty2) : this()
    {
        if (individualProperty2 == null)
            throw new ArgumentNullException();

        IndividualProperty2 = individualProperty2;
    }
    public static LogEvent FromPrototype(LogEventPrototype prototype)
    {
        // clone manually
        return new LogEvent(prototype.IndividualProperty2)
            {
                IndividualProperty1 = prototype.IndividualProperty1,
                PreAllocatedProperty1 = prototype.PreAllocatedProperty1,
                PreAllocatedProperty2 = prototype.PreAllocatedProperty2
            };
    }
}

Option 2

Similar to option 1, but:

Pros

  • constructor of LogEventPrototype is protected
  • it is "ensured", that LogEventPrototype is never instantiated, it is just used as return type
  • no manual mapping

Cons: It seems to be hacky.

class LogEventPrototype
{
    // properties ... (same as in option 1)
    protected LogEventPrototype() 
    {
        GeneratePreAllocatedProperties();
    }
}
class LogEvent : LogEventPrototype, ILogEvent
{
    // constructors same as in option 1; FromPrototype() removed
    public static LogEventPrototype CreateProtoype()
    {
        return new LogEvent();
    }
    public static LogEvent FromPrototype(LogEventPrototype prototype)
    {
        if(prototype.IndividualProperty2 == null)
            throw new ArgumentException();
        return (LogEvent)prototype;

    }
    public static LogEventPrototype CreateProtoype()
    {
        return new LogEvent();
    }
}

Option 3

Do not use a dedicated class for prototypes, but make the LogEvent constructor public and risk invalid LogEvent objects. Use a Validate() method instead and hope, that a client does not forget to use it.

Aucun commentaire:

Enregistrer un commentaire