C#中具有流畅接口的多级继承

时间:2009-08-29 21:08:04

标签: c# fluent fluent-interface

给出下面的示例控制台应用程序:

问题#1 :为什么.Name()返回typeof OranizationBuilder,但.Write()会调用CorporationBuilder?

问题#2 :如何让.Name()返回类型的公司建设者?

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

修改/更新

这是我第一次尝试解决方案(见下文),虽然我被困在工厂内部并且不确定如何配置.Organization()和.Corporation()方法类型。

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

以下是具体问题区域:

/* 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());
    }
}

如何配置OrganizationBuilder和CorportationBuilder?

2 个答案:

答案 0 :(得分:4)

Name返回引用时,它会返回this - 所以当实例实际 CorporationBuilder的实例时,该引用将正常返回。仅仅因为声明该方法返回OrganizationBuilder并不意味着返回OrganizationBuilder引用。它返回对OrganizationBuilder实例或派生类(或null实例)的实例的引用。

当调用Write方法时,这是一个虚方法,因此检查对象的执行时类型以找到要使用的实现。执行时类型仍为CorporationBuilder,因此使用该类型中指定的覆盖。

至于如何使Name()返回适当的类型 - 基本上需要更多的泛型。它可以做到,但这很痛苦 - 我在Protocol Buffers中做了类似的事情,但这并不令人愉快。您也可以在OrganizationBuilderTContact中设置TBuilder通用,并使Name通过从TBuilder转换为this TBuilder 1}}。然后,CorporationBuilder也可以是通用的,或者只是从OrganizationBuilder<Corporation, CorporationBuilder>继承。

编辑:是的,我看到了问题(我之前忘记了这个问题)。您可能希望有一个名为CorporationBuilder的具体非泛型类,以避免递归泛型:

public class OrganizationBuilder :
    OrganizationBuilder<Organization, OrganizationBuilder>

您可能还想将OrganizationBuilder重命名为OrganizationBuilderBase以避免混淆:)

(如果它位于层次结构的底部,则不需要CorporationBuilder为通用本身。)

然而,这让非常变得复杂。您可能希望至少考虑避免继承。废弃泛型,并使OrganizationBuilder CorporationBuilder,而不是从中派生。

基本上这种模式在一段时间后总是会变得复杂 - 除了叶子节点之外,你最终需要每个级别都是通用的,叶子节点总是需要非通用的,以避免你已经看到的递归问题。这是一种痛苦。

答案 1 :(得分:0)

OrganizationBuilder中的.Name()函数具有返回OrganizationBuilder类型的签名 - 无论从哪个派生对象调用它。这就是为什么你看到它返回OrganizationBuilder。如果你在契约构建器中有覆盖Name()函数并将名称设置为其他东西,你会注意到Name()函数正在对你的运行时对象起作用。

现在,如果您想知道如何使Name()返回您想要的构建器,您应该遵循与ID()方法相同的技术。

修改/更新: 那么,现在我不明白你所面临的实际错误 - 使用新的更新。你能分享你所面临的确切错误吗?

旁注:我觉得这个设计完全令人费解。我不会仅仅为了支持返回适当的构建器对象的构建器方法的漂亮模式而向我的消费者提供此信息。我会坚持更简单的方法。