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()作成します。これは、クラスから省略された多くのメソッドが正しく機能するために最も重要です。ctor が戻ったときにクラスが一貫した状態になるようにするには、ベース ctor で呼び出す必要があります。これを反映するために、上記のコードを少し更新しました。
CreateCursor()

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

私が考えている2番目の方向は次のとおりです。

ニワトリが先か卵が先かという問題を回避するために、カーソルを熱心に作成できるようにするために、何らかの抽象化が必要です。

基本クラスでは、仮想呼び出しは派生クラスのまだ初期化されていないフィールドにアクセスしようとします。それでは、別のクラスでカーソルを作成する機能を抽出しましょう。基本クラスは、それがどのように作成されたかには関心がありません。これは、より大きなアルゴリズムの 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 に答える
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 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 に答える
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();
    }
}

特に第2弾が苦手…

于 2012-10-03T15:35:47.397 に答える