views:

458

answers:

2

Given the sample console application below:

QUESTION #1: Why does .Name() return typeof OranizationBuilder, but .Write() calls CorporationBuilder?

QUESTION #2: How to get .Name() to return typeof CorporationBuilder?

namespace MyCompany
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Environment.NewLine);

            Factory.Organization()
                    .ID(33)
                    .Name("Oranization A")
                    .Write();

            Console.WriteLine("\n----------------------------\n");

            Factory.Corporation()
                    .Date(DateTime.Today)     // Pass
                    .ID(44)
                    .Name("Company B")
                    // .Date(DateTime.Today)  // Fail
                    .Write();

            // QUESTION #1: Why does .Name() return typeof OranizationBuilder, 
            //              but .Write() calls CorporationBuilder?

            // QUESTION #2: How to get .Name() to return typeof CorporationBuilder?


            Console.ReadLine();
        }
    }

    /* Business Classes */

    public abstract class Contact
    {
        public int ID { get; set; }
    }

    public class Organization : Contact
    {
        public string Name { get; set; }
    }

    public class Corporation : Organization
    {
        public DateTime Date { get; set; }
    }


    /* Builder */

    public abstract class ContactBuilder<TContact, TBuilder>
        where TContact : Contact
        where TBuilder : ContactBuilder<TContact, TBuilder>
    {
        public ContactBuilder(TContact contact)
        {
            this.contact = contact;
        }

        private TContact contact;

        public TContact Contact
        {
            get
            {
                return this.contact;
            }
        }

        public virtual TBuilder ID(int id)
        {
            this.Contact.ID = id;
            return this as TBuilder;
        }

        public virtual void Write()
        {
            Console.WriteLine("ID   : {0}", this.Contact.ID);
        }
    }

    public class OrganizationBuilder : ContactBuilder<Organization, OrganizationBuilder>
    {
        public OrganizationBuilder(Organization contact) : base(contact) { }

        public virtual OrganizationBuilder Name(string name)
        {
            (this.Contact as Organization).Name = name;
            return this;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Name : {0}", this.Contact.Name);
        }
    }

    public class CorporationBuilder : OrganizationBuilder
    {
        public CorporationBuilder(Corporation contact) : base(contact) { }

        public virtual CorporationBuilder Date(DateTime date)
        {
            // Cast is required, but need this.Contact to be typeof 'C'
            (this.Contact as Corporation).Date = date;
            return this;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Date : {0}", (this.Contact as Corporation).Date.ToShortDateString());
        }
    }

    /* Factory */

    public class Factory
    {
        public static OrganizationBuilder Organization()
        {
            return new OrganizationBuilder(new Organization());
        }

        public static CorporationBuilder Corporation()
        {
            return new CorporationBuilder(new Corporation());
        }
    }
}

EDIT/UPDATE

Here's my first attempt at a solution (see below), although I'm stuck inside the Factory and not sure how to configure the .Organization() and .Corporation() method types.

namespace MyCompany
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Environment.NewLine);

            Factory.Organization()
                    .ID(33)
                    .Name("Oranization A")
                    .Write();

            Console.WriteLine("\n----------------------------\n");

            Factory.Corporation()
                    .ID(44)
                    .Name("Company B")
                    .Date(DateTime.Today)
                    .Write();

            Console.ReadLine();
        }
    }


    /* Business Classes */

    public abstract class Contact
    {
        public int ID { get; set; }
    }

    public class Organization : Contact
    {
        public string Name { get; set; }
    }

    public class Corporation : Organization
    {
        public DateTime Date { get; set; }
    }


    /* Builder */

    public abstract class ContactBuilder<TContact, TBuilder>
        where TContact : Contact
        where TBuilder : ContactBuilder<TContact, TBuilder>
    {
        public ContactBuilder(TContact contact)
        {
            this.contact = contact;
        }

        private TContact contact;

        public TContact Contact
        {
            get
            {
                return this.contact;
            }
        }

        public virtual TBuilder ID(int id)
        {
            this.Contact.ID = id;
            return this as TBuilder;
        }

        public virtual void Write()
        {
            Console.WriteLine("ID   : {0}", this.Contact.ID);
        }
    }

    public class OrganizationBuilder<TOrganization, TBuilder> : ContactBuilder<TOrganization, TBuilder> where TOrganization : Organization where TBuilder : OrganizationBuilder<TOrganization, TBuilder>
    {
        public OrganizationBuilder(TOrganization contact) : base(contact) { }

        public virtual TBuilder Name(string name)
        {
            this.Contact.Name = name;
            return this as TBuilder;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Name : {0}", this.Contact.Name);
        }
    }

    public class CorporationBuilder<TCorporation, TBuilder> : OrganizationBuilder<TCorporation, TBuilder> where TCorporation : Corporation where TBuilder : CorporationBuilder<TCorporation, TBuilder>
    {
        public CorporationBuilder(TCorporation contact) : base(contact) { }

        public virtual TBuilder Date(DateTime date)
        {
            this.Contact.Date = date;
            return this as TBuilder;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Date : {0}", this.Contact.Date.ToShortDateString());
        }
    }


    /* Factory */

    public class Factory
    {
        public static OrganizationBuilder<Organization, OrganizationBuilder> Organization()
        {
            return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization());
        }

        public static CorporationBuilder<Corporation, CorporationBuilder> Corporation()
        {
            return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation());
        }
    }
}

Here's the specific problem area:

/* Factory */

public class Factory
{
    public static OrganizationBuilder<Organization, OrganizationBuilder> Organization()
    {
        return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization());
    }

    public static CorporationBuilder<Corporation, CorporationBuilder> Corporation()
    {
        return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation());
    }
}

How to configure the OrganizationBuilder and CorportationBuilder?

A: 

The .Name() function in the OrganizationBuilder has a signature to return OrganizationBuilder type - no matter on which derived object it is called from. That is why you see it returning OrganizationBuilder. If you would have override Name() function in your contract builder and set the name to something else, you will notice that the Name() function is acting on your runtime object.

Now if you want to know how to make Name() return the builder that you want, you should follow the same technique as you used for ID() method.

EDIT/UPDATE: Well, now I don't understand the actual error you are facing - with the new updates. Can you share the exact error you are facing?

On a side note: I feel this design is totally convoluted. I wouldn't give this to my consumers just to support a nice pattern of a builder method returning the appropriate builder object. I'd stick to much simpler approach.

Charles Prakash Dasari
convoluted.. maybe I agree that the sample business classes are not the best example, but I was trying to make a generic example. The actual usecase would benefit from the fluent API. The Builder classes do appear convoluted, but the end-user API results in a clean fluent interface. The *actual error* I'm facing is the second app sample above does not compile. Here's the error message: Error 1 Using the generic type 'MyCompany.OrganizationBuilder<TOrganization,TBuilder>' requires '2' type argumentsThe error is obvious, but I'm just not sure about the correct syntax to make it work.
jlang
I'm trying to make this work because all C# fluent interface/api examples I've seen only work with one level of inheritance in Visual Studio intellisense.
jlang
The thing I'm stuck on is how to create a new instance of CorporationBuilder?? [Example]return new CorporationBuilder<Corporation, ??>(new Corporation());
jlang
+2  A: 

When Name returns a reference, it returns this - so when the instance is actually an instance of CorporationBuilder, that reference is returned as normal. Just because the method is declared to return OrganizationBuilder doesn't mean it only returns an OrganizationBuilder reference. It returns a reference to an instance of OrganizationBuilder instance or a derived class (or null, of course).

When the Write method is then called, that's a virtual method so the execution-time type of the object is checked to find the implementation to use. The execution-time type is still CorporationBuilder, so the override specified in that type is used.

As for how to make Name() return the appropriate type - that would require more generics, basically. It can be done, but it's a pain - I've done something similar in Protocol Buffers, but it's not pleasant. You'd make OrganizationBuilder generic in TContact and TBuilder as well, and make Name return TBuilder via a cast from this to TBuilder. Then CorporationBuilder would either be generic too, or just inherit from OrganizationBuilder<Corporation, CorporationBuilder>.

EDIT: Yes, I see the problem (which I'd forgotten about before). You may want to have a concrete non-generic class called CorporationBuilder as well, to avoid the recursive generics:

public class OrganizationBuilder :
    OrganizationBuilder<Organization, OrganizationBuilder>

You might also want to rename OrganizationBuilder to OrganizationBuilderBase to avoid confusion :)

(You don't need CorporationBuilder to be generic itself, if it's at the bottom of the hierarchy.)

However, this is getting extremely complicated. You might want to at least consider avoiding inheritance here. Scrap the generics, and make OrganizationBuilder have a CorporationBuilder instead of deriving from it.

Basically this pattern always gets complicated after a while - you end up needing every level to be generic apart from the leaf nodes, which always need to be nongeneric to avoid the recursion problem you've already seen. It's a pain.

Jon Skeet
@Jon Skeet - I've updated the original post with my first attempt at a more elaborate solutions, but there are issues. I'm stuck on the Factor Methods? any tips?
jlang
post EDIT: ya, you've nailed it. I came to exact same conclusions and settled on the OrganizationBuilder "is a" OrganizationBuilderBase hierarchy. The leaf (OrganizationBuilder) "is a" non-generic(ified) class which branches from the generic(ified) OrganizationBuilderBase branch. Seems to work well, so I'm going to run with this scenario and see where it takes me.
jlang