C#类继承

7
我正在从事保险工作,有两种不同的保单类型 - 汽车和家庭,由两个不同的类Motor和Household表示。
这两种类型都有一些共同的数据,因此它们都将继承另一个名为Policy的类。当用户登录应用程序时,他们可能拥有汽车或家庭保单,因此应用程序需要显示通用信息以及特定于Motor或Household的信息。为了封装所有这些内容,我有一个响应对象,其中包含一个Motor成员和一个Household成员,如下所示:
public class Response
{
    ...
    private MotorPolicy                 _motorPolicy;
    private HouseholdPolicy             _householdPolicy;
    ....
}

以下代码应该演示:
if (response.PolicyType == Enumerations.PolicyType.Motor) 
{
    lblDescription.Text = response.MotorPolicy.Description;
    lblReg.Text = response.MotorPolicy.Reg;
}
else
{
    lblDescription.Text = response.HouseholdPolicy.Description;
    lblContents.Text = response.HouseholdPolicy.Contents;
}

MotorPolicy没有Contents属性,而HouseholdPolicy没有Reg属性。

但我真的很想简单地做到:

if (response.PolicyType == Enumerations.PolicyType.Motor) 
{
    lblDescription.Text = response.Policy.Description;
    ...
}

我尝试使用泛型,但是找不到正确的解决方案。

13个回答

5
每个策略后代(现在您有两个,将来可能会有更多,对吧?)应该拥有自己的UI控件,这些控件“知道”如何处理策略信息。同样的方法也可以用于其他事情,例如策略对象的“控制器”等。
然后可以将响应变得通用:
public class Response<T> where T: Policy {
    ...
    private T _policy;
    ....
}

或者,您可以采用更通用的方法,使用反射来显示信息,但这些方法在外观和可用性方面通常不太“吸引人”(请考虑Visual Studio设计器中的属性网格)。


我同意用户界面控件。 - Mike

5

您只需在响应中指定策略类型,然后可以将MotorPolicy或HouseholdPolicy类型存储到其中。

随后,您的响应只需要检查数据类型即可。

if (response.Policy is MotorPolicy) ....

或者在策略类型上有一个抽象方法或返回从子类中完全实现并返回正则数据或内容数据的抽象方法的属性。


请注意,如果您选择这条路线,可能会编写大量的“if/then”或“switch”语句来处理基于类型的处理。在这种情况下,您需要考虑一种称为“用策略替换条件语句”的模式。http://www.informit.com/articles/article.aspx?p=1398607&seqNum=2 - codekaizen
他还可以创建一个Dictionary<Type,Action<Policy>>,将每个策略类型映射到单独的处理程序方法。 - jgauffin

1
public interface IPolicy
{
    string Description { get; }
    string Reg { get; }
    string Contents { get; }
}

public class MotorPolicy : IPolicy
{
    public string Description
    {
        get { return ...; }
    }

    public string Reg
    {
        get { return ...; }
    }

    public string Contents
    {
        get { return String.Empty; }
    }
}

public class HousholdPolicy : IPolicy
{
    public string Description
    {
        get { return ...; }
    }

    public string Reg
    {
        get { return String.Empty; }
    }

    public string Contents
    {
        get { return ...; }
    }
}

public class Response
{
    ...
    private IPolicy             _policy;
    ....
}

现在您不需要枚举来显示您实现的类型,您只需说
lblDescription.Text = response.Policy.Description;
lblReg.Text = response.Policy.Reg;
lblContents.Text = response.Policy.Contents;

编辑:备选方案

public interface IPolicy
{
    string Description { get; }
}

public interface IHasReg
{
    string Reg { get; }
}

public interface IHasContents
{
    string Contents { get; }
}

public class MotorPolicy : IPolicy, IHasReg
{
    public string Description
    {
        get { return ...; }
    }

    public string Reg
    {
        get { return ...; }
    }
}

public class HouseholdPolicy : IPolicy, IHasContents
{
    public string Description
    {
        get { return ...; }
    }

    public string Contents
    {
        get { return ...; }
    }
}

public class Response
{
    ...
    private IPolicy             _policy;
    ....
}

这样做会让调用函数中的代码更多。

lblDescription.Text = response.Policy.Description;
IHasReg hasReg = response.Policy as IHasReg;
if (hasReg != null) lblReg.Text = hasReg.Reg;
IHasContents hasContents = response.Policy as IHasContents;
if (hasContents != null) lblContents.Text = hasContents.Contents;

但是相比其他选项,它更具可扩展性,并且符合您避免在实现中使用不合理功能的要求。


这是我最初的想法,但是我不喜欢我的householdPolicy包含Reg,而MotorPolicy包含Contents。我真的希望有一个根据获取的数据,只包含其中一个的policy对象。 - Neil
@Chris - 我会以另一种方式来看待这个问题。你的 Response 需要一个描述、一个 reg 和内容,因此任何策略实现都应该提供这些数据,因此需要接口。具体实现有意地在不适用于该类型策略的字段中返回 String.Empty。 - pdr

1

一种选择是向 Policy 添加一个成员,该成员综合所有派生类的相关属性以提供摘要:

 public abstract class Policy {
     public string Description { get; set; }
     public abstract string Summary { get; }
 }

 public class MotorPolicy: Policy {
     public override string Summary {
         get { return this.Description + "\r\n" + this.Reg; }
     }
 }

 public class HouseholdPolicy: Policy {
     public override string Summary {
         get { return this.Description + "\r\n" + this.Contents; }
     }
 }

这样可以将逻辑集中起来,使用户界面代码变得简单:

 label.Description.Text = response.Policy.Summary;

这种基本实现牺牲了单独格式化子部分的能力。您可以通过将摘要公开为字符串集合来克服这一点:

public abstract IEnumerable<string> SummarySections { get; }

如果您想以根本不同的方式显示派生类的详细信息,那么您必须在用户界面层中采用条件逻辑(例如,您可以在表格中列出家庭保单的内容,但对于机动车保险的注册,则显示扫描图像)。

1
使用模板模式:
创建一个名为 Policy 的基类,其中包含一个虚抽象的 get 方法来确定策略的描述。
public abstract class Policy
{ 
    protected virtual string GetDescription()
    {
         return string.Empty()    
    }

    public string Description 
    { 
        get 
        {
           return GetDescription();
        } 
    }
}

public MotorPolicy : Policy
{
    public override string GetDescription()
    {
       return ..... ////specific description implementation for MotorPolicy
    }
}

public HouseHoldPolicy : Policy
{
    public override string GetDescription()
    {
       return ..... ////specific description implementation for HouseholdPolicy
    }
}


public class Response        
{        
    ...        
    private MotorPolicy                 _motorPolicy;        
    private HouseholdPolicy             _householdPolicy; 
    private PolicyType                  _policyType;       
    ....        

    public Policy Policy
    {
        get
        {
           if (_policyType== PolicyType.Motor) 
           {
              return _motorPolicy;
           } 
           if (_policyType== PolicyType.Household) 
           {
              return _householdPolicy;
           } 

           return null;
        }
    }        
}    

客户端代码:

if (response.Policy != null)        
{       
    lblDescription.Text = response.Policy.Description;       
    ...       
}    

让MotorPolicy和HouseholdPolicy从Policy派生,并覆盖基类中的抽象get方法并创建特定的实现。

在Response类中只获取描述。


0

定义策略接口并在两个策略类中实现它

Interface IPolicy{
    int Reg {get;set;};
    string Contents {get;set;};
}

MotorPolicy : Policy,IPolicy {

 string IPolicy.Contents 
     {get;set;};


 int IPolicy.Reg 
     {get;set;};

}

HouseholdPolicy : Policy , IPolicy {
 string IPolicy.Contents 
     {get;set;};


 int IPolicy.Reg 
     {get;set;};
}

2
他有一个名为Policy的基类,使用接口也不会改变任何东西。 - jgauffin
3
我理解良好的面向对象设计不是这样的。家庭保险政策没有注册号,汽车保险政策也没有内容。因此,这种设计是误导性的。 - Dave Arkell
好的,那么他可以这样定义另外一种方式: MotorPolicy:Policy,IPolicy。 - RameshVel
Ramesh,从基类和接口继承会如何工作? - Neil

0

我的第一反应是选择:

public abstract class Response
{
  public abstract Policy Policy {get;}//can be used for stuff for dealing with all policies.
  public static Response GetResponse(Policy policy)
  {//factory method
    if(policy is MotorPolicy)
      return new MotorResponse((MotorPolicy)policy);
    if(policy is HouseholdPolicy)
      return new HouseholdResponse((HouseholdPolicy)policy);
    throw new ArgumentException("Unexpected policy type");
  }
}
public class MotorResponse : Response
{
  private readonly MotorPolicy _motorPolicy;
  public MotorResponse(MotorPolicy policy)
  {
    _motorPolicy = policy;
  }
  protected override Policy Policy
  {
    get { return _motorPolicy; }
  }
  // motor specific stuff
}
public class HouseholdResponse : Response
{
  private readonly HouseholdPolicy _householdPolicy;
  public HouseholdResponse(HouseholdPolicy policy)
  {
    _householdPolicy = policy;
  }
  protected override Policy Policy
  {
    get { return _householdPolicy; }
  }
  // household specific stuff
}

为什么您更喜欢使用显式字段等,而不是使用通用的基类? - Lucero
我不是很熟悉泛型,特别是将它们用作基类,所以我无法让那个解决方案起作用。你知道怎么做吗? - Neil
@Lucero,根据我们所知道的问题,我不会为泛型辩护,因为现在我能看到在Response中有用的共性取决于政策的共性,如果我要有Response<T> where T:Policy,那么我仍然需要从Response<HouseholdPolicy>派生以提供所需的额外内容。实际上,一个泛型基类并没有增加太多额外的东西,而且它还会阻止任何运行时多态性。 - Jon Hanna
如果我选择这个解决方案,那么我不是会有两种不同类型的响应对象吗?这样我就回到了现在的情况,只不过是用响应代替策略。 - Neil
话虽如此,您会注意到工厂方法中仍然存在一些切换和if-else。不过优点在于它将所有内容都推到一个地方,而不是散布在代码的其他部分。 - Jon Hanna
显示剩余2条评论

0
我会尝试这样做:
  public class Response
  {
     public Policy SelectedPolicy {get;set;}

     //I don't think you need these, but hard to 
     //say without seeing the rest of the code
     ...
     private MotorPolicy                 _motorPolicy;
     private HouseholdPolicy             _householdPolicy;
     ....
  }

那么

lblDescription.Text = response.SelectedPolicy.Description;

if (SelectedPolicy is MotorPolicy)
    lblReg.Text = ((MotorPolicy)response.SelectedPolicy).Reg;

else if (SelectedPolicy is HouseholdPolicy)
    lblContents.Text = ((HouseholdPolicy)response.SelectedPolicy).Contents;

我不会把Reg和Contents都放在基类或接口中。如果我这样做的话,继承的目的是什么呢?所有的类看起来都一样吗?唯一的好处就是可以得到类型信息,但在这种情况下,这并不能带给我太多好处。

0
也许我没有理解问题,但我会使用继承。
将策略定义为: public class Policy { public string Description{ get; set;} public string Details {get; set;} }
}

public class MotorPolicy:Policy 
{
    public void SetReg(string reg)
    {
        base.Details = reg;
    }
}

public class HousePolicy:Policy 
{
    public void SetContents(string contents)
    {
        base.Details = contents;
    }
}

并通过调用

    private void Form1_Load(object sender, EventArgs e)
    {
        MotorPolicy mp = new MotorPolicy();
        mp.Description = "Motor";
        SetForm(mp);       
    }

    private void SetForm(Policy p)
    {
        lblDescription.Text = p.Description;
        lblDetail.Text = p.Details;

        //then if you still need specifics 
        if (p.GetType() == typeof(MotorPolicy))
        {
            MotorPolicy mp = p as MotorPolicy;
            //continue assigning mp
        }
        else if (p.GetType() == typeof(HousePolicy))
        {
            HousePolicy hp = p as HousePolicy;
            //continue assigning Hp
        }
    }

请注意,我将reg / contents作为字段详细信息,因为它们都是字符串类型。如果其中一个是int而另一个是字符串,则必须分别完成。

0
你的代码是"重构条件为多态" [Fowler] 的一个独特示例。
然后,你的方法应该接受适当的对象,并按如下方式进行操作:
public void Update(IPolicy policy)
{
        lblDescription.Text = policy.Description;
        lblReg.Text = .Reg;

}

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