Pergunta

Estou trabalhando duro para padronizar uma maneira única de design em camadas/n-camadas de todos os meus aplicativos.

Estou tentando fazer de todos os meus aplicativos 5 camadas.

Código:


| Ui |

|

| Objeto de negócios |

|

| OR-MAPPER |

|

| Acesso de dados |

|

| Rdbms |

Suponha que estou desenvolvendo um aplicativo com um recurso de login/log-out para os usuários. Estou criando 4 projetos sob uma solução VS2005. Cada projeto é para uma das 4 camadas superiores. Estou projetando minha aula de objeto de negócios da seguinte maneira:-

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);
    }
}

É assim que estou dando aos meus objetos alguma funcionalidade que corresponde ao cenário do mundo real. Aqui getByuserName () e getByUserTyPecode () são funções getter. Essas funções não correspondem a uma lógica do mundo real. Porque, no mundo real, um usuário nunca "consegue o nome de usuário" ou "recebe pelo userTyPecode". Portanto, essas funções são mantidas estáticas.

Minha aula para ou camada de mapeador é a seguinte:-

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;
    }
}

E finalmente, a classe DA é a seguinte:-

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;
    }
}

Se alguém examinar de perto essas classes, ele poderá entender isso, ou a camada de mapeador precisará da referência da camada de BusinessObject. BusinessObject- Layer também precisa de uma referência ou da camada de mapeador.

Isso deve criar uma dependência circular.

Como posso evitar esse problema?

Alguém sugeriu o uso de objetos de transferência de dados simples (DTO). Mas, até onde eu sei, de acordo com o OOP, atributos e funcionalidade de um objeto do mundo real devem ser agrupados como uma classe. Se eu usar o DTO, como posso encapsular a funcionalidade em uma classe? Além disso, estou criando outra classe sem nenhum atributo (BO). Para mim, isso é violação de OOP nos dois lados. Se eu fizer isso, então para que é o OOP neste mundo? A mesma resposta pode ser aplicada para classes "UserManager".

Achei um blog.

Ele discute sobre a implementação de interfaces. Defina uma interface separada, implemente-a na sua classe de dados no BusinessObject e programe contra sua interface no BusinessObject e na camada OR-MAPPER.

Mas eu não poderia fazer isso.

Alguém pode me mostrar isso com um exemplo prático?

Foi útil?

Solução

Eu acho que há algumas coisas que você pode fazer isso juntas pode ajudar com seu design. Eu também acho que você pode querer ler em Injeção de dependência como talvez proporcione um melhor padrão de design para o que você deseja fazer.

Supondo que você apenas queira fazer o que tem funcionar:

  • Primeiro, remova o static Métodos do seu User classe, já que eles 'criam' usuários e, portanto, são os melhores acabados de UserMapper.

  • Depois disso, ainda haverá vários métodos potencialmente que usam UserMapper funcionalidade do User classe. Crie uma interface IUserLookup (ou algo assim) que suporta o UserNameExists e UserNamePasswordExists métodos; Coloque esta interface no mesmo projeto que o User classe.

  • Implementar o IUserLookup no UserMapper classe, e então 'injete' no User instâncias de classe que cria com os métodos estáticos através de um construtor, então basicamente, como o UserMapper cria User objetos, isso lhes dá uma referência ao IUserLookup interface que ele se implementa.

Nesse caminho, User usa apenas métodos em IUserLookup, que está na mesma solução, portanto, nenhuma referência necessária. E UserMapper Referências esta solução, para que possa criar User objetos e implementar o IUserLookup interface.

Outras dicas

Se o mapeador está realmente fazendo ou provavelmente não Precisa de uma referência ao BL - só precisa saber o Type(S) isto é (estão envolvidos. Mas isso é um problema lateral ...

A principal resposta para esse tipo de questão é "inversão de controle" / "injeção de dependência", presumivelmente cortando tudo sob o BL - então o BL depende apenas de uma interface (definida em um conjunto base), mas não sabe sobre o concreto ou/da/rdbms (eles são fornecidos pelo IOC/DI).

Este é um grande tópico, por isso estou sendo intencionalmente vago. Pessoalmente eu gosto de structureMap, mas há grande quantidade das ferramentas do IOC/DI disponíveis.

Observe que tecnicamente isto é possível criar referências de montagem circular; é um verdade Porém, a má idéia - e as ferramentas (intencionalmente) lutarão com você a cada passo.

No código que você enviou acima, não há evidências de dependência circular.

Quando sua chamada viaja de camadas superiores para camadas inferiores ... seu objeto transformado em especialização, que é apropriado para cada camada (no entanto, no seu caso, você está lidando com primitivas em cada camada ... pelo menos no código enviado) ... e Quando sua chamada retornar, deve ser da especialização para a generalização ....

Isso pode ser vice -versa e não há problemas de dependência circular se um único caminho for observado dessa maneira. No entanto, se em qualquer camada, você tenta implementar o cenário de especilização para ambos os caminhos de viagem de informações laterais, temos um problema, pois cada camada dependerá e exigiu a referência de sua camada de anexo.

Mas em seu código não há evidências de dependência circular. No entanto, se houver tal caso, isso pode ser evitado pela implementação da camada de interface ou do padrão adaptador (a camada de interface é um padrão adaptador).

Por exemplo, temos uma camada InformationTravel (it) ... (ok, eu entendo que isso não parece muito bom)

| Ui | | | InformationTravel | ** | | Objeto de negócios | | | OR-MAPPER | | | Acesso de dados | | | Rdbms |

O que você faz é para o seu objeto de negócios do usuário, declarar um interface iuser e implementá -lo no objeto de negócios do usuário ....

Então Bo tem uma referência disso. A criação de objetos deve estar apenas na camada BO que implementa uma interface a partir dela. Isso é perfeitamente bom. Quando você precisa passar esse objeto para o seu ORM, você o passa cortando -o a uma interface implementada nele e quando você o receber de volta, retorna novamente o mesmo objeto depois de fazer a modificação necessária.

Mas isso novamente impõe uma implicação de que você não pode criar seu BO em várias camadas, pois apenas o BO tem a implementação da interface. No entanto, se você realmente não pode evitar isso, precisará fornecer a implementação em vários locais ou usar o padrão adaptador (encapsule seu objeto em outro objeto que é esperado pelo cliente).

Para evitar referência circular entre as montagens, você deve usar interfaces. Por exemplo, se o seu ou-mapper precisar invocar alguns membros do BL, você deve reconhecer esses membros e colocá-los em uma ou várias interfaces e colocá-los em uma montagem (interfaces, por exemplo) Ou na sua montagem OR-MAPPER e deixe que seus objetos BL os implementem, não haverá necessidade de fazer referência ao seu BL em sua montagem OR-MAPPER.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top