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

发布于 2024-08-02 20:54:18 字数 7563 浏览 8 评论 0原文

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

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

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

编辑/更新

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

盛装女皇 2024-08-09 20:54:18

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

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

至于如何使 Name() 返回适当的类型 - 基本上,这需要更多的泛型。这是可以做到的,但这很痛苦——我在协议缓冲区中做了类似的事情,但这并不令人愉快。您还可以在 TContactTBuilder 中使 OrganizationBuilder 通用,并让 Name 返回 TBuilder< /code> 通过从 thisTBuilder 的转换。那么 CorporationBuilder 也将是通用的,或者只是从 OrganizationBuilder 继承。

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

public class OrganizationBuilder :
    OrganizationBuilder<Organization, OrganizationBuilder>

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

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

但是,这变得极其复杂。您可能至少要考虑在这里避免继承。废弃泛型,并让 OrganizationBuilder 拥有一个 CorporationBuilder 而不是从它派生。

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

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.

国际总奸 2024-08-09 20:54:18

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

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文