多态性,使用子类的方法

时间:2017-05-04 13:21:13

标签: c# interface polymorphism abstract

我有一个界面IRecordBuilder和一个abstract class Query,其中包含字段protected IRecordBuilder recordBuilder和方法public abstract IList<IRecords> GetRecordsFromResults();

Query子类构造函数中,我根据我所在的子类指定了一个recordBuilder具体类型,例如:

recordBuilder = new RecordsPerMonthBuilder(); //RecordsPerMonthBuilder implements IRecordBuilder

我想在上面的抽象方法的实现中使用我的recordBuilder字段,但是在编译时IRecordBuilder的实现中的属性仍然未知,我无法使用它们。

除了将recordBuilder从母类转移到每个子类并使用正确的类型在那里实例化之外,有没有办法让多态在这里工作?

以下是代码格式的解释:

public interface IRecordBuilder
{
  IRecords BuildRecord();
}

public class RecordsPerMonthBuilder : IRecordBuilder
{
   public IRecords BuildRecord()
   {
      if(Foo != null) return new FooRecord(Foo); // class FooRecord : IRecord
      return null;
   }
  public string Foo {get; set;} 
}


public abstract class Query
{
  protected IRecordBuilder recordBuilder;
  public abstract IList<IRecords> GetRecordsFromResults();
}


public sealed class ConcreteQuery: Query
{
  public ConcreteQuery()
  {
    RecordBuilder = new RecordsPerMonthBuilder();
  }

  public override IList<IRecords> GetRecordsFromResults()
  {  
     var recordsList = new List<IRecords>();
     recordBuilder.foo = "foo"; // IRecordBuilder does not contain a definition for foo
     recordsList.Add(RecordBuilder.BuildRecord());
     return recordsList;
  }
}

2 个答案:

答案 0 :(得分:0)

我看到三种可能的解决方案:

选项1 :在您的子类中,将构建器强制转换为具体类型(因为子类创建它,它知道具体类型)。如果这样做,您可能希望将recordBuilder字段只读,并将其传递给基础构造函数,以确保在编译时无法更改其类型。

选项2 :在您的子课程中,请保留一个额外的&#34;强类型&#34;对记录构建器的引用。 (事实上​​,为什么你甚至需要&#34;界面输入&#34;参考?)

public sealed class ConcreteQuery: Query
{
  private RecordsPerMonthBuilder myBuilder; 

  public ConcreteQuery()
  {
    myBuilder = new RecordsPerMonthBuilder();
    RecordBuilder = myBuilder;
  }

  public override IList<IRecords> GetRecordsFromResults()
  {  
     var recordsList = new List<IRecords>();
     myBuilder.foo = "foo";
     recordsList.Add(myBuilder.BuildRecord());
     return recordsList;
  }
}

选项3 :使您的基类具有通用性:

public abstract class Query<TBuilder> where TBuilder : IRecordBuilder
{
  protected TBuilder RecordBuilder;
  public abstract IList<IRecords> GetRecordsFromResults();
}

public sealed class ConcreteQuery : Query<RecordsPerMonthBuilder>
{
    ...
}

答案 1 :(得分:0)

一个令人困惑的地方是,您的Query课程明确依赖于IRecordBuilderRecordsPerMonthBuilder的一个实施。接口IRecordBuilder没有Foo属性,但Query取决于Foo属性。 Query被硬编码为仅使用RecordsPerMonthBuilder

很难看到意图。清除它的一种方法是确保在Query中定义IRecordBuilderIRecordBuilder的实现之间的任何互动。 Query应该依赖于接口,不应该调用不在该接口中的任何属性或方法。

如果只有IRecordBuilder的一个实现需要Foo,则该值不应来自您的Query类,因为Query不知道IRecordBuilder 1}}需要Foo。它不应该知道IRecordBuilder 的实现需要,只知道做什么

这是一种移动它的方法。你会看到很多这种模式。

public abstract class Query
{
    protected IRecordBuilder RecordBuilder { get; private set; }

    protected Query(IRecordBuilder recordBuilder)
    {
        RecordBuilder = recordBuilder;
    }

    public abstract IList<IRecords> GetRecordsFromResults();
}

现在它永远不会知道IRecordBuilder的实现是什么。非常好。它现在不可能依赖于IRecordBuilder界面中没有的任何东西。现在Query取决于抽象,应用Dependency Inversion原则。

RecordsPerMonthBuilder怎么样?它取决于值FooIRecordBuilder的每个实现都需要吗?如果是这样,您可以将其添加到界面:

IRecords BuildRecord(string foo);

但如果只有一个实现需要该值,则它不应来自Query,因为Query不应该知道一个IRecordBuilder与另一个Foo之间的差异。我不能更具体地回答这个问题,因为我不知道Query是什么。

另一个建议:如果ConcreteQueryPLANTUML之间的继承给你带来任何悲伤,那就不要使用继承。有时尝试使用继承会产生并发症,并且不会给我们带来任何好处。