C# 中具有流畅接口的多级继承
给出下面的示例控制台应用程序:
问题 #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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当
Name
返回引用时,它返回this
- 因此当实例实际上是CorporationBuilder
的实例时,引用正常返回。仅仅因为该方法被声明为返回OrganizationBuilder
并不意味着它仅返回OrganizationBuilder
引用。它返回对OrganizationBuilder
实例或派生类的实例的引用(当然,或者null
)。当调用
Write
方法时,这是一个虚拟方法,因此会检查对象的执行时类型以查找要使用的实现。执行时类型仍然是CorporationBuilder,因此使用该类型中指定的覆盖。至于如何使
Name()
返回适当的类型 - 基本上,这需要更多的泛型。这是可以做到的,但这很痛苦——我在协议缓冲区中做了类似的事情,但这并不令人愉快。您还可以在TContact
和TBuilder
中使OrganizationBuilder
通用,并让Name
返回TBuilder< /code> 通过从
this
到TBuilder
的转换。那么CorporationBuilder
也将是通用的,或者只是从OrganizationBuilder
继承。编辑:是的,我看到了这个问题(我之前已经忘记了)。您可能还希望有一个名为
CorporationBuilder
的具体非泛型类,以避免递归泛型:您可能还希望将
OrganizationBuilder
重命名为OrganizationBuilderBase 以避免混淆:)
(如果
CorporationBuilder
位于层次结构的底部,则不需要它本身是通用的。)但是,这变得极其复杂。您可能至少要考虑在这里避免继承。废弃泛型,并让
OrganizationBuilder
拥有一个CorporationBuilder
而不是从它派生。基本上,这种模式在一段时间后总是会变得复杂——最终需要除了叶节点之外的每个级别都是通用的,而叶节点总是需要是非通用的,以避免您已经看到的递归问题。很痛苦。
When
Name
returns a reference, it returnsthis
- so when the instance is actually an instance ofCorporationBuilder
, that reference is returned as normal. Just because the method is declared to returnOrganizationBuilder
doesn't mean it only returns anOrganizationBuilder
reference. It returns a reference to an instance ofOrganizationBuilder
instance or a derived class (ornull
, 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 stillCorporationBuilder
, 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 makeOrganizationBuilder
generic inTContact
andTBuilder
as well, and makeName
returnTBuilder
via a cast fromthis
toTBuilder
. ThenCorporationBuilder
would either be generic too, or just inherit fromOrganizationBuilder<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:You might also want to rename
OrganizationBuilder
toOrganizationBuilderBase
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 aCorporationBuilder
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.
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.