私は、すべてのアプリケーションの階層化/n 層化設計の 1 つの方法を標準化するために懸命に取り組んでいます。
すべてのアプリケーションを 5 層にしようとしています。
コード:
| | UI |
| |
| | ビジネス オブジェクト |
| |
| | OR-マッパー |
| |
| | データ アクセス |
| |
| | RDBMS |
ユーザーのログイン/ログアウト機能を備えたアプリケーションを開発しているとします。VS2005 ソリューションで 4 つのプロジェクトを作成しています。各プロジェクトは、上位 4 つのレイヤーのいずれかです。次のようにビジネスオブジェクトクラスを設計しています:-
public class User
{
private string _username;
public string Username
{
get { return _username; }
set { _username = value; }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; }
}
public User()
{
}
public bool LogIn(String username, String password)
{
bool success = false;
if (UserMapper.UsernameExists(username))
{
success = UserMapper.UsernamePasswordExists(username, password);
}
else
{
//do nothing
}
return success;
}
public bool LogOut()
{
bool success;
//----some logic
return success;
}
public static User GetUserByUsername(string username)
{
return UserMapper.GetUserByUsername(username);
}
public static UserCollection GetByUserTypeCode(string code)
{
return UserMapper.GetByUserTypeCode(code);
}
}
これが、実際のシナリオに一致する機能をオブジェクトに与える方法です。ここで、GetByUsername() と GetByUserTypeCode() は getter 関数です。これらの関数は、実際のロジックと一致しません。Coz、現実の世界では、ユーザーは「ユーザー名で取得」または「UserTypeCode で取得」することはありません。したがって、これらの関数は静的に保持されます。
OR Mapperレイヤーの私のクラスは次のとおりです:-
public static class UserMapper
{
public static bool UsernameExists(String username)
{
bool exists = false;
if (UserDA.CountUsername(username) == 1)
{
exists = true;
}
return exists;
}
public static bool UsernamePasswordExists(String username, String password)
{
bool exists = false;
if (UserDA.CountUsernameAndPassword(username, password) == 1)
{
exists = true;
}
return exists;
}
}
最後に、DA クラスは次のとおりです。
public static class UserDA
{
public static int CountUsername(string username)
{
int count = -1;
SqlConnection conn = DBConn.Connection;
if (conn != null)
{
try
{
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandText = @"SELECT COUNT(*)
FROM User
WHERE User_name = @User_name";
command.Parameters.AddWithValue("@User_name", username);
command.Connection.Open();
object idRaw = command.ExecuteScalar();
command.Connection.Close();
if (idRaw == DBNull.Value)
{
count = 0;
}
else
{
count = (int)idRaw;
}
}
catch (Exception ex)
{
count = -1;
}
}
return count;
}
public static int CountUsernameAndPassword(string username, string password)
{
int count = 0;
SqlConnection conn = DBConn.Connection;
if (conn != null)
{
try
{
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandText = @"SELECT COUNT(*)
FROM User
WHERE User_name = @User_name AND Pass_word = @Pass_word";
command.Parameters.AddWithValue("@User_name", username);
command.Parameters.AddWithValue("@Pass_word", password);
command.Connection.Open();
object idRaw = command.ExecuteScalar();
command.Connection.Close();
if (idRaw == DBNull.Value)
{
count = 0;
}
else
{
count = (int)idRaw;
}
}
catch (Exception ex)
{
count = 0;
}
}
return count;
}
public static int InsertUser(params object[] objects)
{
int count = -1;
SqlConnection conn = DBConn.Connection;
if (conn != null)
{
try
{
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandText = @"INSERT INTO User(ID, User_name, Pass_word, RegDate, UserTypeCode, ActualCodeOrRoll)
VALUES(@ID, @User_name, @Pass_word, @RegDate, @UserTypeCode, @ActualCodeOrRoll)";
command.Parameters.AddWithValue("@ID", objects[0]);
command.Parameters.AddWithValue("@User_name", objects[1]);
command.Parameters.AddWithValue("@Pass_word", objects[2]);
command.Parameters.AddWithValue("@RegDate", objects[3]);
command.Parameters.AddWithValue("@UserTypeCode", objects[4]);
command.Parameters.AddWithValue("@ActualCodeOrRoll", objects[5]);
command.Connection.Open();
count = command.ExecuteNonQuery();
command.Connection.Close();
}
catch (Exception ex)
{
count = -1;
}
}
return count;
}
public static SqlDataReader GetUserByUsername(string username)
{
SqlDataReader dataReader = null;
SqlConnection conn = DBConn.Connection;
if (conn != null)
{
try
{
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandText = @"SELECT * FROM User WHERE User_name = @User_name";
command.Parameters.AddWithValue("@User_name", username);
command.Connection.Open();
dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (Exception ex)
{
dataReader.Close();
dataReader.Dispose();
}
}
return dataReader;
}
public static SqlDataReader GetUserByUserTypeCode(string userTypeCode)
{
SqlDataReader dataReader = null;
SqlConnection conn = DBConn.Connection;
if (conn != null)
{
try
{
SqlCommand command = new SqlCommand();
command.Connection = conn;
command.CommandText = @"SELECT * FROM User WHERE UserTypeCode = @UserTypeCode";
command.Parameters.AddWithValue("@UserTypeCode", userTypeCode);
command.Connection.Open();
dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (Exception ex)
{
dataReader.Close();
dataReader.Dispose();
}
}
return dataReader;
}
}
誰かがこれらのクラスを詳しく調べると、OR Mapper レイヤーには BusinessObject-layer の参照が必要であることを理解できます。BusinessObject-layer には、OR Mapper-layer の参照も必要です。
これにより、循環依存が作成されます。
この問題を回避するにはどうすればよいですか?
プレーンなデータ転送オブジェクト (DTO) の使用を提案した人がいます。しかし、私の知る限り、OOP によれば、実世界のオブジェクトの属性と機能は、クラスとしてグループ化する必要があります。DTO を使用する場合、どうすれば機能をクラスにカプセル化できますか? さらに、属性 (BO) のない別のクラスを作成しています。私にとって、それは両方の点で OOP の違反です。もしそうなら、OOP はこの世界で何のためにあるのでしょうか。「UserManager」クラスにも同じ答えを適用できます。
ブログを見つけました。
インターフェイスの実装について説明します。別のインターフェースを定義し、それを BusinessObject のデータ クラスに実装し、BusinessObject および OR-Mapper レイヤーのインターフェースに対してプログラムします。
しかし、私はこれを行うことができませんでした。
誰かが実際の例でそれを示すことができますか?