将基本类型转换为派生类型。

3

基类是Task。还有一些派生类,例如PhoneCall、Fax、Email等……。框架是.NET 3.5,语言是C#。

在我们的应用程序中,我们希望根据某些规则为客户创建一些自动任务。例如,如果客户已经注册了30天,规则引擎将创建一个任务。

任务的所有者应该能够根据情况将此任务转换为PhoneCall、Fax等等。另外,另一个要求是将PhoneCall转换为Fax或Email或反之亦然。

1)应该有一个转换类来方便进行转换,还是每个业务对象都应该允许方法执行转换?

2)如果有任何设计模式或指导,将非常好。

Pratik

3个回答

4

继承并不一定是最好的方式来建模那些类型实例会随时间改变的问题。

您可能需要考虑使用组合代替。例如:

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

// now we can operate against TaskDetails using a KVC approach:
Task someTask;
object dateSent = someTask.Detail["DateSent"]; // both fax/email have a DateSent
if( dateSent != null )
   // ...

0

1) 你可以使用工厂方法来创建这样的任务。

2) 我认为让所有者将任务转换为子类并不是一个好主意。也许策略模式或状态模式会有所帮助。


0
在你的特定情况下,类型并不是正确的选择,因为你还没有定义 PhoneCall、Fax 或 Email 的行为,除了 Task 之外。
如果你唯一要做的就是在 PhoneCall 对象中存储电话号码,那么你已经破坏了封装性。PhoneNumber 属性应该在 Customer 对象上,邮件地址、邮寄地址、传真号码等也应该如此。
你可以愉快地将 TaskType 枚举作为 Task 的属性使用,直到 Task 根据当前任务类型改变其行为为止。这是你需要担心使用类型转换的时候,就像 LBushkin 描述的那样。
我可以看到你在使用每个任务的不同呈现逻辑的情况下。在这种情况下,你会使用装饰器模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接