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?

7
  • "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, 2017 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, 2017 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, 2017 at 17:26
  • 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, 2017 at 17:32
  • 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, 2017 at 20:31

3 Answers 3

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.

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.

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("...");
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.