3

I have a large class with complex properties. I'd like to introduce a default implementation, allow the user to override part of the default implementation and also make sure the user calls a sync method so that the object is always at a consistent state.

public class Complex {
    public int Id { get; set; }
    public CustomComplex1 Custom1 { get; set; }
    public CustomComplex2 Custom2 { get; set; }

    public Complex()
    {
        Custom1 = new CustomComplex1() { Name = "Bla", Cost = 55, etc..} // provide default values
    }

    //this method must always be called!
    public void RegisterObject()
    {

    }     

}

Class could be used like this:

  var a = new Complex(); // all default values set
  a.Custom1.Name = "newName"; // I'd like to use the default values, but just change something..
  a.RegisterObject(); // problem - client might forget to call this! 

I thought about using the builder pattern, but then I'll have to provide methods to modify each variation of CustomComplex1 type - too much work & maintenance. From the other hand if I expose Custom1 as a public property it looks like I'm doing the exact opposite of the builder pattern's intent. So what is a good way to A.allow the user modify any property of my complex object and B.force him to call the RegisterObject on compile time?

BornToCode
  • 1,273
  • 2
  • 13
  • 16
  • "I thought about using the builder pattern, but then I'll have to provide methods to modify each variation of CustomComplex1 type - too much work & maintenance." I don't understand this. Can you explain this in more detail? To me, it seems you misunderstand Builder patter. – Euphoric Dec 10 '17 at 17:21
  • @Euphoric - I don't want to provide 100 methods - "WithName, WithCost, WithAge, WithBla", they're all properties of the CustomComplex1 type and I want to provide the user of the class a default CustomComplex1, but allow him to override ANY property of that object that he wishes. – BornToCode Dec 10 '17 at 17:24
  • You should read up on Builder Patter. As what you say is completely irrelevant to core purpose of Builder Pattern. What you describe is called Fluent Interface, and it is unrelated concept. – Euphoric Dec 10 '17 at 17:26
  • Do you have a way to detect that an object is not registered? If so, you could put an assertion somewhere that says, "Hey! This object hasn't been registered! Please make sure it calls `RegisterObject()` before using." – user1118321 Dec 10 '17 at 17:31
  • 1
    @user1118321 It is clear to me that OP wants to make this compile-time invariant, not runtime check. Which is what you are suggesting. – Euphoric Dec 10 '17 at 17:32
  • Possible duplicate of [Derive from a base class but not include a condition in the base class's method](https://softwareengineering.stackexchange.com/questions/271457/derive-from-a-base-class-but-not-include-a-condition-in-the-base-classs-method) – gnat Dec 10 '17 at 19:39
  • 1
    @gnat That doesn't seem to apply here, as there is no inheritance in play. And the problem is on creation of object, not calling a method. – Euphoric Dec 10 '17 at 20:31

3 Answers3

9

You can use builder pattern. The "WithX" stuff is not really main purpose of builder and is just syntax sugar to make some usage simpler.

Your code, using builder, could very well look like this :

var build = new ComplexBuilder(); // all default values set
build.Custom1.Name = "newName";
Complex a = build.Build(); // register inside here

This is valid and normal builder pattern.

Euphoric
  • 36,735
  • 6
  • 78
  • 110
2

If you don't want to use builder pattern, and you want to get fancy, you could use setting-changing delegate. I saw something similar in configuration of ASP.NET Core.

public class Complex {
    public int Id { get; set; }
    public CustomComplex1 Custom1 { get; set; }
    public CustomComplex2 Custom2 { get; set; }

    public Complex(Action<CustomComplex1, CustomComplex2> configure)
    {
        Custom1 = new CustomComplex1() { Name = "Bla", Cost = 55, etc..} // provide default values
        configure(Custom1, Custom2);
        RegisterObject();
    }

    private void RegisterObject()
    {

    }
}

The caller can then pass the delegate that changes the Custom objects.

And if you don't like doing this in a constructor, make a static Create method, make the constructor private and just call the delegate and RegisterObject from there.

Euphoric
  • 36,735
  • 6
  • 78
  • 110
0

Set the final Build() method to a different interface, so that the client code is forced to call that method right before building the actual concrete class.

public class House
{
}

public interface IHouseBuilderWithMandatoryMethod
{
    House Build();
}

public interface IHouseBuilder
{
    IHouseBuilder AddBalcony();

    IHouseBuilder AddPool();

    IHouseBuilderWithMandatoryMethod AddBarbecueStation();
}

public class HouseBuilder: IHouseBuilder, IHouseBuilderWithMandatoryMethod
{
    public IHouseBuilder AddBalcony() { return this; }

    public IHouseBuilder AddPool() { return this; }

    public IHouseBuilderWithMandatoryMethod AddBarbecueStation() { return this; }

    public House Build() { return new House(); }
}

internal class Program
{
    static void Main(string[] args)
    {
        IHouseBuilder builder = new HouseBuilder();
        var house = builder
                        .AddBalcony()
                        .AddPool()
                        .AddBarbecueStation()
                        .Build();
    }
}

Another solution would be to check if the desired method has been called throughout the calling chain (inside the Build() method) and throw an exception if not:

public class HouseBuilder
{
    private mandatoryMethodHasBeenCalled = false;

    AddBarbecueStation()
    {
       mandatoryMethodHasBeenCalled = true;
    }
    
    public House Build()
    {
       if(!mandatoryMethodHasBeenCalled)
         throw new Exception("...");
    }
}
Rzassar
  • 131
  • 4