我有一个用于数据库无关的游标操作的抽象类。 从中衍生出来,有些类实现了处理特定于数据库的东西的抽象方法。
问题是,基类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()
在数据库中创建实际游标。这对于从班级中省略的许多方法的正确运作至关重要
必须在基本ctor中调用CreateCursor()
,以便在ctor返回时类处于一致状态。我稍微更新了上面的代码以反映这一点。
答案 0 :(得分: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;
}
}
}
答案 1 :(得分: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);
}
答案 2 :(得分: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
可以像您提到的那样从派生类中调用,但我认为从调用代码调用它会更简单。有很多类型使用这种模式,即使它需要更多代码,但这并不是一种不好的做法。
答案 3 :(得分:1)
这是我正在考虑的第二个方向:
为了解决鸡与蛋的问题,同时允许热切地创建游标,一些抽象是有序的。
在基类中,虚拟调用尝试访问派生类中尚未初始化的字段。因此,让我们提取在不同类中创建游标的功能。基类对它的创建方式不感兴趣,它只是更大算法中的一步。
对我来说,这种方法的要点与strategy pattern类似 - 主类知道算法的一般步骤,而步骤的实际实现细节在运行时插入。
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)
{ }
}
答案 4 :(得分:0)
第一次尝试是将虚拟呼叫从基本ctor中移出,但它有一些缺点:
-
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();
}
}
我特别不喜欢第二颗子弹......