4
\$\begingroup\$

I'm creating many builders where I use inheritance.

My resources have many properties and are stored in a Repository. In rare case I need to update them. When I update, I need to keep the same objectId (I track update on the repository).

I use the same Builder/Updater as it's easier for my users (there are more than 10 concrete builders).

The code with only one builder (and properties removed):

     public interface IBuilder<T, P>
        {
            P Build();
            P Update();
        }
    public abstract class BuilderBase<T, P>: IBuilder<T, P> 
        where T : BuilderBase<T, P>
        where P : class, new()
        {
            protected P Result;
            protected P Source;
            protected T This;

            protected BuilderBase()
            {
                Result = new P();
                This = (T) this;
            }

            protected BuilderBase(P source):this()
            {
                Source = source;
            }

            public P Build()
            {
                var result = Result;
                Result = null;
                return result;
            }

            public abstract P Update();
    }
   public abstract class ResourceBuilder<T, P> : BuilderBase<T, P>
        where T : ResourceBuilder<T, P>
        where P : ResourceBase, new()
        {

            protected ResourceBuilder():base(){}

            protected ResourceBuilder(P resource):base(resource)
            {
                Result.Name = Source.Name;
            }

            public new P Build()
            {
                var result = Result;
                return base.Build();
            }

            public override P Update()
            {
                if (Source != null)
                    Result.ResourceId = Source.ResourceId; 
                return Build();
            }

            public T Name(string name)
            {
                Result.Name = name;
                return This;
            }

        }
    public abstract class ColorResourceBuilder<T, P> : ResourceBuilder<T, P>
           where T : ColorResourceBuilder<T, P>
           where P : ColorResource, new()
    {

        public T Color(Color color)
        {
            Result.Color = color;
            return This;
        }

        protected ColorResourceBuilder(P colorResource) : base(colorResource)
        {
            Result.Color = Source.Color;
        }

        protected ColorResourceBuilder() : base()
        {
        }
    }
       public class ColorResourceBuilder : ColorResourceBuilder<ColorResourceBuilder, ColorResource> {
        public ColorResourceBuilder(ColorResource colorResource) : base(colorResource)
        {
        }

        public ColorResourceBuilder()
        {
        }
    }

The main problem with this approach is for the Update():

var colorResource2 = new ColorResourceBuilder(colorResource1).Name("Blue").Update();

colorResource2 != colorResource1 => two different object have the same Id...

hypothetical improvement:

var colorResource2 = new ColorResourceBuilder(ref colorResource1).Name("Blue").Update();
var colorResource2 = new ColorResourceBuilder(ref colorResource1).Name("Blue").Build();

It could be OK for the Update(). But weird with Build() as ref suggest update.

Another hypothetical improvement:

var colorResource2 = new ColorResourceBuilder().Name("Blue").Update(ref colorResource1);
var colorResource2 = new ColorResourceBuilder(colorResource1).Name("Blue").Build();

Will incur another usage inconsistency problem.

I'm looking for any advice to make it more clean and natural. Or any other pattern which could do the job.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Unfortunately, hypothetical code is off-topic on Code Review, you might find better luck over on Software Engineering, but make sure to read their on-topic guide before posting. If you have any questions or concerns, join us at our CR Help Desk. \$\endgroup\$ – Quill Jan 9 '16 at 8:15
  • 2
    \$\begingroup\$ @Quill Is this hypothetical code? It looks like complete code, with some brief analysis including a couple of small hypothetical changes in order to explain it. But the code (everything before to "The main problem") looks like real code that can be reviewed. \$\endgroup\$ – Ben Aaronson Jan 9 '16 at 10:57
  • 1
    \$\begingroup\$ It's real code. I just removed what is not interesting in this case. I can add 10 builder and 50 properties but it will not change anything on the update problem. I just tried to clean my code to make it more understandable. I add hypothetical code for context and explain why I didn't choose this option. I can move it to programmer if you thing it is more appropriate. \$\endgroup\$ – Kevin Jan 9 '16 at 14:22
  • 1
    \$\begingroup\$ @Kevin That makes the question off-topic here. \$\endgroup\$ – Mast Jan 9 '16 at 14:22
  • \$\begingroup\$ @Mast Sorry but what does it make it off-topic ? I'm looking for "Best practices and design pattern usage". In my case it's for the Updater. It's unfortunately my first question and I'm not able to join CR Help Desk. \$\endgroup\$ – Kevin Jan 9 '16 at 14:26
1
\$\begingroup\$

Comment on the builder pattern

I don't think that Joshua Bloch discovered any new version of the builder pattern. I suggest to follow the standard way and purposes from the GoF.

The purposes of the builder pattern are:

  1. avoid having a lot of constructor parameters
  2. provide a default and preserve consistency to be able to ALWAYS build an object
  3. make the code fluently readable

I think here the pattern is not appropriate and not implemented in the way it should be. The Update()-method for example has nothing to do with any builder pattern.

A guess on the intention

As you mentioned a "repository" you are dealing with some kind of persistence context. In your implementation the build()-method will create a new object in the repository and the update()-method wil update an existing object in the repository so far I understand.

Proposal, creation and result

The design I would follow is a natural approach.

If you want to create a new resource then you create a proposal object that contains data that may be considered during a creation process. The proposal object itself may have been constructed by a builder pattern. That depends on the purposes mentioned in the first place.

After that the proposal object is passed into the creation process. It will be validated and checked against internal system consistency. The creator object is well defined as the responsible for the creation of the wished object (SRP).

If no validation errors occured and it is not violating the systems consistency the object ist created AND persisted. The object may be internally (in the responsible object) constructed through the builder pattern. But that has nothing to do with persistence.

Proposal, update and result

The same with the update. The only difference is that you have an update proposal, an existing object to update and a responsible object that will to the update process.

Now it's getting interesting. The result of the update can be a versioned new object xor a cached old object. This depends on if you want to work with immutability or not. As you have an "id" I suggest to have the objects cached and mutable.

Nevertheless you have to override equals and hashcode in both scenarios. But be careful: two objects of any version are equal if their id is equal.

Conclusion

  1. Do not make the builder pattern return mutable objects, always return new objects. The "builder" pattern is meant to "build" something not to update something
  2. Only build transient, never involve persistence in the builder pattern
  3. Have a proper design for CRUD-Operations for your persistent objects (business objects??) and well defined responsibilities
  4. Distinguish between proposal, creator/updater/deleter and result

I am sorry, this is more a design issue of your problem domain than a pattern issue.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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