求めているのは、オブジェクトのファクトリ メソッド パターンと、データ アクセス コードのリポジトリ パターンです。記事ほどうまく説明できないので、代わりに基本的な考え方を説明し、いくつかの例を示します。
目標は、ユーザーとの通信 (UI)、アプリケーション内でのデータの保持と検証 (ビジネス クラス/モデル)、またはデータ永続性の管理 (データアクセス)。これらの領域をきちんと分割しておくと、コードの保守とデバッグ、または並行開発が容易になります。複数の物理マシンにまたがるアーキテクチャを容易にするなど、他の利点もありますが、それは問題の範囲外です。
基本構造:
概念的な進行状況の取得:
UI -> Person Factory -> Person class -> Repository -> Database
コンセプト進行の保存:
UI -> Person class -> Repository -> Database
内部に説明コメントを含む Person クラス構造:
public class Person
{
// various properties & methods
// Constructor access is restricted to control how the class gets consumed.
// All instance management must go through the factories.
protected Person() { /* stuff */ }
// Person factory implementation. It's done inside the Person class so that
// tight control can be kept over constructor access.
// The factory is what gives you your instances of Person.
// It has defined inputs and outputs, as well as more descriptive
// names than constructor overloads, so consumers know what to expect.
// It's also a place to put scaffolding code, so you can avoid doing
// things like setting properties every time you fetch an instance.
// The factory takes care of all the object initialization and returns
// an instance that's ready for use.
public static Person GetPerson(int id)
{
Person p = new Person();
// here you call the repository. It should return either a native
// data structure like DataReader or DataTable, or a simple DTO class
// which is then used to populate the properties of Person.
// the reason for this is to avoid a circular dependency between
// the repository and Person classes, which will be a compile time error
// if they're defined in separate libraries
using(PersonRepository repo = new PersonRepository())
{
DataReader dr = repo.GetPerson(id);
p.FillFromDataReader(dr);
}
return p;
}
protected void FillFromDataReader(DataReader dr)
{ /* populate properties in here */ }
// Save should be an instance method, because you need an instance of person
// in order to save. You don't call the dealership to drive your car,
// only when you're getting a new one, so the factory doesn't do the saving.
public void Save()
{
// Again, we call the repository here. You can pass a DTO class, or
// simply pass the necessary properties as parameters
using(PersonRepository repo = new PersonRepository())
{
this.Id = repo.SavePerson(name, address);
}
}
}
さて、リポジトリコード:
// This class implements IDisposable for easy control over DB connection resources.
// You could also design and implement an IRepository interface depending on your needs.
public class PersonRepository : IDisposable
{
private SqlConnection conn;
public PersonRepository()
{
// in here you initialize connection resources
conn = new SqlConnection("someConnectionString");
}
public void IDisposable.Dispose()
{
// clean up the connection
conn.Dispose();
}
// The instance methods talk to the database
public int SavePerson(string name, string address)
{
// call your stored procedure (or whatever) and return the new ID
using(SqlCommand cmd = conn.CreateCommand())
{
// stuff
return (int)cmd.Parameters["myOutputIDParameter"].Value;
}
}
public DataReader GetPerson(int id)
{
// call your stored procedure (or whatever) and return the fetched data
using(SqlCommand cmd = conn.CreateCommand())
{
// stuff
return cmd.ExecuteReader();
}
}
}
最後に、UI レベルで行うことは次のとおりです。
Person joe = Person.GetPerson(joeId);
// stuff
joe.Save();