4

私は、すべてのアプリケーションの階層化/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 レイヤーのインターフェースに対してプログラムします。

しかし、私はこれを行うことができませんでした。

誰かが実際の例でそれを示すことができますか?

4

4 に答える 4

4

一緒にデザインに役立つことがいくつかあると思います。また、依存性注入について、自分がやりたいことに対してより良いデザインパターンを提供するものとして読んでおくとよいと思います。

ただし、自分が持っているものを機能させたいだけだと仮定します。

  • まず、ユーザーを「作成」するため、クラスstaticからメソッドを削除します。したがって、メソッドはに残しておくのが最適です。UserUserMapper

  • その後も、クラスUserMapperの機能を使用する可能性のあるメソッドがいくつかあります。およびメソッドをサポートUserするインターフェースIUserLookup(または何か)を作成します。このインターフェースをクラスと同じプロジェクトに配置します。UserNameExistsUserNamePasswordExistsUser

  • クラスにを実装しIUserLookup、コンストラクタを介して静的メソッドで作成UserMapperするクラスインスタンスに「注入」しUserます。したがって、基本的に、オブジェクトをUserMapper作成するときに、それ自体を実装するインターフェイスへの参照を提供します。UserIUserLookup

このように、同じソリューションにあるのUserメソッドのみを使用するIUserLookupため、参照は必要ありません。そしてUserMapper、このソリューションを参照して、Userオブジェクトを作成し、IUserLookupインターフェイスを実装できるようにします。

于 2009-05-29T04:58:56.513 に答える
3

ORマッパーが実際にORを実行している場合は、おそらくBLへの参照は必要ありませんType。関係している(s)を知る必要があるだけです。しかし、それは副次的な問題です...

このタイプの問題に対する主な答えは、「制御の反転」/「依存性注入」であり、おそらくBLの下にあるすべてのものを切り取っています。したがって、BLは(ベースアセンブリで定義された)インターフェイスのみに依存しますが、具体的なOR/DA / RDBMS(IoC / DIによって提供されます)。

これは大きな話題なので、わざと漠然としています。個人的にはStructureMapが好きですが、利用できるIoC/DIツールはたくさんあります。

技術的、円形のアセンブリ参照を作成できることに注意してください。しかし、それは本当に悪い考えです-そしてツールは(意図的に)すべてのステップであなたと戦うでしょう。

于 2009-05-29T04:58:37.090 に答える
0

上記で送信したコードには、循環依存の証拠はありません。

呼び出しが上位レイヤーから最下位レイヤーに移動すると...オブジェクトは各レイヤーに適した特殊化に変換されます(ただし、あなたの場合、各レイヤーのプリミティブを扱っています...少なくとも送信されたコードでは)...そしてあなたの呼び出しが戻ってきたとき、それは特殊化から一般化に向かっているはずです....

これはその逆である可能性があり、そのような方法で単一のパスが観察された場合、循環依存の問題はありません。ただし、いずれかのレイヤーで両側の情報移動パスの仕様化シナリオを実装しようとすると、各レイヤーが依存し、それを囲むレイヤーの参照が必要になるため、問題が発生します。

しかし、あなたのコードには循環依存の証拠はありません。ただし、そのような場合は、インターフェイス レイヤーまたはアダプター パターンを実装することで回避できます (インターフェイス レイヤーはアダプター パターンです)。

たとえば、Layer InformationTravel (IT) があります。

| | UI | | | | | Information旅行 | ** | | | ビジネス オブジェクト | | | | | OR-マッパー | | | | | データ アクセス | | | | | RDBMS |

あなたがすることは、ユーザービジネスオブジェクトに対して、インターフェイス IUser を宣言し、それをユーザービジネスオブジェクトに実装することです....

それからBOはITの参照を持っています。オブジェクトの作成は、IT からのインターフェイスを実装する BO レイヤーでのみ行う必要があります。これはまったく問題ありません。このオブジェクトを ORM に渡す必要がある場合は、スライスして IT に実装されたインターフェイスに渡し、それを受け取ったときに、必要な変更を行った後に同じオブジェクトを再度返します。

ただし、これは、BO だけがインターフェイスの実装を持っているため、複数のレイヤーで BO を作成できないことを意味します。ただし、これを本当に回避できない場合は、複数の場所で実装を提供するか、アダプターパターンを使用する必要があります (クライアントが期待する別のオブジェクトにオブジェクトをカプセル化します)。

于 2009-05-29T05:19:34.880 に答える
0

アセンブリ間の循環参照を防ぐには、インターフェイスを使用する必要があります。たとえば、OR-Mapper が BL の一部のメンバーを呼び出す必要がある場合は、これらのメンバーを認識し、それらを 1 つまたは複数のインターフェイスに配置して、アセンブリ (インターフェイスなど) に配置する必要があります。または OR-Mapper アセンブリで BL オブジェクトに実装させると、OR-Mapper アセンブリで BL を参照する必要がなくなります。

于 2009-05-29T06:54:17.987 に答える