Циркулярная ссылка между сборками в C # и Visual Studio 2005 - PullRequest
4 голосов
/ 29 мая 2009

Я прилагаю все усилия, чтобы стандартизировать единый подход ко всем приложениям Layered / n-Tiered.

Я пытаюсь сделать все свои приложения 5-уровневыми.

Код:


| UI |

|

| Бизнес-объект |

|

| OR-Mapper |

|

| Доступ к данным |

|

| СУРБД |

Предположим, я разрабатываю приложение с возможностью входа / выхода для пользователей. Я создаю 4 проекта в рамках решения VS2005. Каждый проект для одного из верхних 4 слоев. Я проектирую свой класс Business Object следующим образом: -

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 () являются функциями получения. Эти функции не соответствуют реальной логике. Ведь в реальности пользователь никогда не «получает по имени пользователя» или «получает по типу пользователя». Таким образом, эти функции остаются неизменными.

Мой класс для слоя O-R 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;
    }
}

Если кто-нибудь внимательно изучит эти классы, он поймет, что для слоя O-R Mapper необходима ссылка на слой BusinessObject. BusinessObject-layer также требуется ссылка на O-R Mapper-layer.

Это должно создать круговую зависимость.

Как мне избежать этой проблемы?

Кто-то предложил использовать простые объекты передачи данных (DTO). Но, насколько я знаю, согласно ООП атрибуты и функциональные возможности объекта реального мира должны быть сгруппированы как класс. Если я использую DTO, то как я могу инкапсулировать функциональность в класс? Более того, я создаю другой класс без каких-либо атрибутов (BO). Для меня это нарушение ООП в обоих направлениях. Если я это сделаю, то для чего ООП в этом мире? Тот же ответ может быть применен к классам "UserManager".

Я нашел блог .

В нем обсуждается реализация интерфейсов. Определите отдельный интерфейс, внедрите его в свой класс данных в BusinessObject и запрограммируйте на свой интерфейс в BusinessObject и на уровне OR-Mapper.

Но я не смог этого сделать.

Может кто-нибудь показать мне это на практическом примере?

Ответы [ 4 ]

4 голосов
/ 29 мая 2009

Я думаю, что есть несколько вещей, которые вы можете сделать вместе, что может помочь с вашим дизайном. Я также думаю, что вы, возможно, захотите прочитать о Dependency Injection , возможно, предоставляя лучший шаблон проектирования для того, что вы хотите сделать.

Предполагая, что вы просто хотите сделать то, что у вас есть, хотя:

  • Сначала удалите методы static из вашего класса User, так как они «создают» пользователей, и поэтому лучше их просто оставить на UserMapper.

  • После этого все еще будет ряд методов, потенциально использующих функциональность UserMapper из класса User. Создайте интерфейс IUserLookup (или что-то еще), который поддерживает методы UserNameExists и UserNamePasswordExists; поместите этот интерфейс в тот же проект, что и класс User.

  • Реализуйте IUserLookup в классе UserMapper, а затем «внедрите» его в экземпляры класса User, которые он создает, с помощью статических методов через конструктор, так что в основном, как создает UserMapper User объектов, он дает им ссылку на интерфейс IUserLookup, который он реализует сам.

Таким образом, User использует только методы для IUserLookup, который находится в том же решении, поэтому ссылка не требуется. И UserMapper ссылается на это решение, поэтому оно может создавать User объекты и реализовывать интерфейс IUserLookup.

3 голосов
/ 29 мая 2009

Если OR Mapper фактически выполняет OR, то, вероятно, не не нужна ссылка на BL - ему просто нужно знать Type (s), которые (ы) участвуют. Но это побочный вопрос ...

Основной ответ на этот тип проблемы - «Инверсия управления» / «Инъекция зависимости», предположительно перехватывая все под BL - так что BL зависит только от интерфейса (определенного в базовой сборке), но не знать о конкретных OR / DA / RDBMS (они поставляются IoC / DI).

Это большая тема, поэтому я намеренно размыта. Лично мне нравится StructureMap, но есть лотов инструментов IoC / DI.

Обратите внимание, что технически это возможно для создания ссылок на круглые сборки; хотя это действительно плохая идея - и инструменты будут (намеренно) сражаться с вами на каждом шагу.

0 голосов
/ 29 мая 2009

Чтобы избежать циклической ссылки между сборками, вы должны использовать интерфейсы. Например, если вашему OR-Mapper необходимо вызвать некоторые элементы BL, вы должны распознать эти элементы и поместить их в один или несколько интерфейсов и поместить их либо в сборку (интерфейсы например) или в вашей сборке OR-Mapper, и пусть ваши объекты BL реализуют их, тогда не будет необходимости ссылаться на ваш BL в вашей сборке OR-Mapper.

0 голосов
/ 29 мая 2009

В приведенном выше коде нет признаков циклической зависимости.

Когда ваш вызов перемещается из верхних уровней в нижние слои ... ваш объект трансформируется в специализацию, соответствующую каждому уровню (однако в вашем случае вы имеете дело с примитивами на каждом уровне ... по крайней мере в представленном коде). .. И когда ваш звонок возвращается, он должен быть от специализации к обобщению ....

Это может быть наоборот, и нет проблем с циклической зависимостью, если таким образом наблюдается единственный путь. Однако, если в каком-либо из уровней вы попытаетесь реализовать сценарий Specilization для пути прохождения обеих сторон информации, у нас возникнет проблема, так как каждый уровень будет зависеть, и для него потребуется ссылка его вмещающего слоя.

Но в вашем коде нет таких доказательств круговой зависимости. Однако, если есть такой случай, этого можно избежать путем реализации уровня интерфейса или шаблона адаптера (интерфейсный уровень является шаблоном адаптера).

Например, у нас есть Layer InformationTravel (IT) ... (ОК, я понимаю, это звучит не слишком хорошо)

| UI | | | InformationTravel | ** | | Бизнес Объект | | | OR-Mapper | | | Доступ к данным | | | СУРБД |

Что вы делаете для своего бизнес-объекта пользователя, объявите интерфейс IUser и внедрите его в бизнес-объект пользователя ....

Тогда у БО есть ссылка на ЭТО. Создание объекта должно быть только на уровне BO, который реализует интерфейс из IT. Это совершенно нормально. Когда вам нужно передать этот объект в ORM, вы передаете его, разрезая его на интерфейс, реализованный в IT, и когда вы получаете его обратно, вы снова возвращаете тот же объект после выполнения необходимой модификации.

Но это опять-таки подразумевает, что вы не можете создать свой BO на нескольких уровнях, поскольку только BO имеет реализацию интерфейса. Однако, если вы действительно не можете избежать этого, вам нужно предоставить реализацию в нескольких местах или использовать шаблон адаптера (инкапсулируйте ваш объект в другой объект, который ожидается клиентом).

...