3

我有一个与数据库无关的游标操作的抽象类。从中派生出一些类,它们实现了用于处理特定于数据库的东西的抽象方法。

问题是,基类ctor需要调用一个抽象方法——当调用ctor时,它需要初始化数据库特定的游标。

我知道为什么不应该这样做,我不需要那个解释!

这是我的第一个实现,显然不起作用 - 这是教科书的“错误方式”。重写的方法访问派生类中的一个字段,该字段尚未实例化:

public abstract class CursorReader
{
    private readonly int m_rowCount;
    protected CursorReader(string sqlCmd)
    {
         m_rowCount = CreateCursor(sqlCmd); //virtual call !
    }
    protected abstract int CreateCursor(string sqlCmd);

    //...other (non-abstract) methods that assume a cursor exists
}

public class SqlCursorReader : CursorReader
{
    private SqlConnection m_sqlConnection;

    public SqlCursorReader(string sqlCmd, SqlConnection sqlConnection)
    {
        m_sqlConnection = sqlConnection;     //field initialized here
    }
    protected override int CreateCursor(string sqlCmd)
    {
        //uses not-yet-initialized member *m_sqlConnection*
        //so this throws a NullReferenceException
        var cursor = new SqlCursor(sqlCmd, m_sqlConnection); 
        cursor.Create();
        return cursor.Count();
    }
}

我将跟进我尝试解决此问题的答案...

更新

重写的方法在数据库中CreateCursor()创建一个实际的游标。这对于类中省略的许多方法的正确运行至关重要。
CreateCursor()必须在基 ctor 中调用,以便类在 ctor 返回时处于一致状态。我稍微更新了上面的代码以反映这一点。

4

5 回答 5

4

你总是可以有一个惰性属性来获取计数。

public abstract class CursorReader
{
    private int? m_rowCount;
    protected CursorReader()
    {

    }
    protected abstract int CreateCursor(string sqlCmd);
    protected int RowCount {
      get {
          if (m_RowCount == null)
          {
             m_RowCount = CreateCursor(sql);
          }
          return m_RowCount.Value;
      }

    }
}
于 2012-10-03T15:29:50.963 回答
1

怎么样:

public abstract class CursorReader
{
    private int? m_rowCount = null;
    private int rowCount { get { return m_rowCount = m_rowCount ?? CreateCursor(sqlCmd); } }
    protected CursorReader() { }
    protected abstract int CreateCursor(string sqlCmd);
}
于 2012-10-03T15:31:27.950 回答
1

您可能需要将逻辑分成构造函数和初始化。

public abstract class CursorReader
{
    private readonly int m_rowCount;
    protected CursorReader()
    {

    }

    protected void Init()
    {
         m_rowCount = CreateCursor(sqlCmd); //virtual call !
    }

    protected abstract int CreateCursor(string sqlCmd);
}

这将要求您调用Init()每个新实例,但这是我能想到的最佳解决方案。

请注意,Init可以像您提到的那样从派生类中调用它,但我认为从调用代码中调用它会更简单。有很多类型使用这种模式,尽管它需要更多代码,但这并不是一个坏习惯。

于 2012-10-03T15:34:53.797 回答
1

这是我正在考虑的第二个方向:

为了回避先有鸡还是先有蛋的问题,同时允许急切地创建光标,一些抽象是有序的。

在基类中,虚拟调用尝试访问派生类中尚未初始化的字段。因此,让我们提取在不同类中创建游标的功能。基类对它的创建方式不感兴趣,它只是更大算法中的一个步骤。

在我看来,这种方法的要点类似于策略模式——主类知道算法的一般步骤,而这些步骤的实际实现细节是在运行时插入的。

public interface ICursorCreator {
    int CreateCursor(string sqlCmd);
}
public abstract class CursorReader
{
    private readonly int m_rowCount;
    protected CursorReader(string sqlCmd, ICursorCreator creator)
    {
         m_rowCount = creator.CreateCursor(sqlCmd); //no longer a virtual call 
    }
    //protected abstract int CreateCursor(string sqlCmd);//no longer needed

    //...other (non-abstract) methods that assume a cursor exists
}

//move the logic of creating a cursor in a separate class, and pass an instance of that to the base class. 
public SqlCursorCreator: ICursorCreator {
    private SqlConnection m_sqlConnection;
    public SqlCursorCreator(SqConnection conn){
        m_sqlConnection = conn;
    }
    public int CreateCursor(string sqlCmd)
    {
        var cursor = new SqlCursor(sqlCmd, m_sqlConnection); 
        cursor.Create();
        return cursor.Count();
    }
}

public class SqlCursorReader : CursorReader
{
    //private SqlConnection m_sqlConnection;//no longer needed

    //by saving the connection in the factory, it will be available when needed later
    public SqlCursorReader(string sqlCmd, SqlConnection sqlConnection)
        :this(sqlCmd, new SqlCursorCreator(sqlConnection))
    { }
    protected SqlCursorReader(string sqlCmd, SqlCursorCreator creator)
        : base(sqlCmd, creator)
    { }
}
于 2012-10-03T21:10:38.867 回答
0

第一次尝试是将虚拟调用移出基本 ctor,但它有一些缺点:

  • m_rowCount 不再是只读的
  • 派生类和所有未来派生类都需要调用基类 Initialize() 方法。

-

public abstract class CursorReader
{
    private int m_rowCount;//no longer read-only
    protected CursorReader()
    {
        //no virtual call here
    }
    protected abstract int CreateCursor(string sqlCmd);
    protected void Initialize()
    {
         //virtual call moved here
         m_rowCount = CreateCursor(sqlCmd); //virtual call !
    }
}
public class SqlCursorReader : CursorReader
{
    private SqlConnection m_sqlConnection;

    public SqlCursorReader(string sqlCmd, SqlConnection sqlConnection)
    {
        m_sqlConnection = sqlConnection;

        //the derived classes NEED to call the base class' Initialize() 
        Initialize();
    }
    protected override int CreateCursor(string sqlCmd)
    {
        //uses not-yet-initialized member m_sqlConnection
        var cursor = new CustomCursor(sqlCmd, m_sqlConnection); 
        return cursor.Count();
    }
}

我特别不喜欢第二个子弹...

于 2012-10-03T15:35:47.397 回答