【问题标题】:Multi-level inheritance with fluent interface in C#C#中具有流畅接口的多级继承
【发布时间】:2009-08-29 21:08:04
【问题描述】:

鉴于下面的示例控制台应用程序:

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

问题 #2:如何让 .Name() 返回 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());
        }
    }
}

编辑/更新

这是我第一次尝试解决方案(见下文),尽管我被困在 Factory 中并且不知道如何配置 .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?

【问题讨论】:

  • 您应该在名称和日期这些方法中返回 TBuilder - 对吧?
  • @Charles Prakash Dasari:很好的收获。我修改了第二个样本。工厂返回类型和 Builder ctor 仍然存在问题。
  • 查看下面答案的更新...

标签: c# fluent fluent-interface


【解决方案1】:

Name 返回一个引用时,它返回this - 因此,当实例实际上CorporationBuilder 的一个实例时,该引用将正常返回。仅仅因为该方法被声明为返回OrganizationBuilder 并不意味着它 返回一个OrganizationBuilder 引用。它返回对OrganizationBuilder 实例或派生类(当然也可以是null)实例的引用。

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

至于如何让Name() 返回适当的类型——基本上这需要更多的泛型。可以做到,但很痛苦——我在 Protocol Buffers 中做过类似的事情,但这并不令人愉快。你也可以在TContactTBuilder 中使OrganizationBuilder 通用,并通过从thisTBuilder 的转换使Name 返回TBuilder。那么CorporationBuilder 要么是通用的,要么只是继承自OrganizationBuilder&lt;Corporation, CorporationBuilder&gt;

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

public class OrganizationBuilder :
    OrganizationBuilder<Organization, OrganizationBuilder>

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

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

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

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

【讨论】:

  • @Jon Skeet - 我已经更新了原始帖子,第一次尝试更精细的解决方案,但存在一些问题。我被困在因子方法上?有什么建议吗?
  • 发布编辑:是的,你已经搞定了。我得出了完全相同的结论,并确定了 OrganizationBuilder“是一个”OrganizationBuilderBase 层次结构。叶(OrganizationBuilder)“是一个”非泛型(化)类,它从泛型(化)OrganizationBuilderBase 分支分支。似乎效果很好,所以我将运行这个场景,看看它会把我带到哪里。
【解决方案2】:

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

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

编辑/更新: 好吧,现在我不明白您面临的实际错误 - 新更新。你能分享你所面临的确切错误吗?

附带说明:我觉得这个设计非常复杂。我不会为了支持返回适当构建器对象的构建器方法的良好模式而将其提供给我的消费者。我会坚持更简单的方法。

【讨论】:

  • convoluted.. 也许我同意示例业务类不是最好的例子,但我试图做一个通用的例子。实际用例将受益于 fluent API。 Builder 类确实看起来很复杂,但最终用户 API 会产生一个干净流畅的界面。我面临的 实际错误 是上面的第二个应用示例无法编译。以下是错误消息:错误 1 ​​使用泛型类型 'MyCompany.OrganizationBuilder' 需要 '2' 类型参数 错误很明显,但我只是不确定使其工作的正确语法。跨度>
  • 我正在努力完成这项工作,因为我见过的所有 C# fluent interface/api 示例都只适用于 Visual Studio intellisense 中的一级继承。
  • 我坚持的是如何创建 CorporationBuilder 的新实例? minimal reproducible example return new CorporationBuilder(new Corporation());
猜你喜欢
  • 1970-01-01
  • 2014-09-07
  • 1970-01-01
  • 2011-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-25
  • 1970-01-01
相关资源
最近更新 更多