继承并不一定是最好的方式来建模那些类型实例会随时间改变的问题。
您可能需要考虑使用组合代替。例如:
class Task
{
private TaskDetail m_Detail;
public TaskDetail Detail { get { return m_Detail; } }
}
abstract class TaskDetail { ... }
class PhoneCallDetail : TaskDetail { ... }
class FaxDetail : TaskDetail { ... }
class EmailDetail : TaskDetail { ... }
当任务的详细信息从一种类型转换为另一种类型时,任务不会发生变化。您还需要实现一些实用程序代码,以根据需要在不同的任务类型之间进行转换。
因此,使用示例可能如下所示:
Task theTask = new Task( ... );
theTask.ConvertToEmail(); // internally establishes this as an email task
EmailDetail detail = (EmailDetail)theTask.Detail;
detail.EmailAddress = "wiley.coyote@acme.com";
theTask.ConvertToFax(); // may transfer or lose some detail...
FaxDetail faxDetail = (FaxDetail)theTask.Detail;
faxDetail.FaxDate = DateTime.Now;
// and so on.
上述方法的主要缺点是,
Task
类的使用者必须使用运行时检查来确定与任务相关联的详细信息的类型,然后需要在各处进行详细属性的强制转换。
Task someTask = ...;
if( someTask.Detail is EmailDetail )
{
EmailDetail detail = (EmailDetail)someTask.Detail;
/* operate on email detail ... */
}
else if( someTask.Detail is FaxDetail )
{
FaxDetail detail = (FaxDetail)someTask.Detail;
/* operate on fax detail ... */
}
随着不同子类型数量的增加,这种方法变得更难以维护和演进。如果子类型数量很少,并且随时间稳定,那么这可能是一个合理的选择。
一般来说,像这样的情况很难建模 - 你经常不得不根据你使用的持久化提供者、有多少不同的细节类型以及你打算支持从一个细节类型到另一个细节类型的转换的使用案例来做出妥协。
在这种情况下通常采用的另一种设计方法是键值编码。这种方法使用键/值字典来建模不同种类细节的各种数据元素。这使得细节非常灵活,但代价是较少的编译时安全性。我尽可能避免使用这种方法,但有时它确实能更好地建模某些问题域。
实际上可以将键值编码与更强类型的方法结合起来。这允许细节公开其属性(通常仅限于只读)而不需要调用方执行运行时检查或强制转换:
abstract class TaskDetail
{
public abstract object this[string key] { get; }
}
public class FaxDetail : TaskDetail
{
public string FaxNumber { get; set; }
public DateTime DateSent { get; set; }
public override object this[string key]
{
get
{
switch( key )
{
case "FaxNumber": return FaxNumber;
case "DateSent": return DateSent;
default: return null;
}
}
}
}
public class EmailDetail : TaskDetail
{
public string EmailAddress { get; set; }
public DateTime DateSent { get; set; }
public override object this[string key]
{
get
{
switch( key )
{
case "EmailAddress": return EmailAddress;
case "DateSent": return DateSent;
default: return null;
}
}
}
}
Task someTask;
object dateSent = someTask.Detail["DateSent"];
if( dateSent != null )