none
Como Eliminar os Ifs RRS feed

  • Pergunta

  • Tenho um metodo para testar conexao com o banco de dados que recebe uma entidade burra com  strings de nome, usuario senha e tipo de banco

    public static bool TestarConexao(Banco banco){
    
    IProvedor provedor;
    
    if banco.tipo == "mysql")
      provedor = new ProvedorMysql();
    if banco.tipo == "SqlServer2000")
      provedor = new ProvedorSqlServer2000();
    if banco.tipo == "SqlServer2008")
      provedor = new ProvedorSqlServer2008();
    
    return provedor.testar(banco);
    
    }
    


    o problema é que tenho estes ifs (são mais ou menos uns 10) e a tendencia é aumentar.

    Eu sei que com algum padrão de projeto (talvez strategy) eu posso resolver isso mas me falta conhecimento.

    Se alguem puder me esclarecer.

     

    Fernando Mondo

    terça-feira, 9 de agosto de 2011 13:59

Respostas

  • Olá Fernando,

    Excelente dúvida.

    Nestes casos existem diversas formas de associar o banco aí, este tipo de arquitetura esta diretamente relacionada a Injeção de Dependência, no caso, o objetivo é injetar um objeto que implemente IProvedor a sua factory, o problema é que aí no TestarConexao você esta passando uma classe com uma string e fazendo um monte de IF, em questão de arquitetura esta errado, você precisa injetar a responsabilidade em 3 opções:

    1. passando o objeto que implemente IProvider (este seria o tipo Interface Injection), 
    2. utilizando REFLECTION na raça
    3. utilizando algum container de inversão de controle como o SPRING que através de um arquivo de configuração XML voce diz qual a classe a ser instanciada e ele ja tem o reflection implementado).

    Em primeiro momento, seria possível já passar o provedor instanciado para o TestaConexão ao invés do banco?

    Caso contrário segue um exemplo de reflection na mão (opção 2):

     

    using System.Reflection;
    public class LateBinder 
    {
     /// <exception cref="System.TypeLoadException">The type couldn't be 
     /// loaded.</exception>
     /// <exception cref="System.Exception">The assembly couldn't be loaded 
     /// or other errors.</exception>
     /// <exception cref="System.ArgumentNullException">fullyQualifiedTypeName 
     /// is a null reference.</exception>
     public static object CreateInstance(string assemblyName,string fullyQualifiedTypeName)
     {
     Type type = null;
     if (assemblyName != null) 
     {
     // this method will also search the GAC and will use the latest
     // version if multiple versions are found.
     Assembly assembly = Assembly.LoadWithPartialName(assemblyName);
     if (assembly != null) 
     {
     type = assembly.GetType(fullyQualifiedTypeName,true,true);
     } 
     else 
     {
     throw new Exception("Failed to load assembly \"" + assemblyName + "\".");
     }
     } 
     else 
     {
     type = Type.GetType(fullyQualifiedTypeName,true,true);
     }
     // invoke the type's constructor to create an instance
     object instance = type.InvokeMember("",
     BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance,
     null,
     null,
     new object[] { });
     return instance;
     }
     public static object CreateInstance(string fullyQualifiedTypeName) 
     {
     return CreateInstance(null,fullyQualifiedTypeName);
     }
    }
    
    
    
    private static IProvider GetConnection(string assemblyName, string providerConnectionTypeName)
    {
    object o = LateBinder.CreateInstance(assemblyName,providerConnectionTypeName);
    if (!(o is IProvider)){
    throw new Exception("\"" + providerConnectionTypeName  + "\" doesn't implement IProvider.");
    }
    return (IProvider) o;
    }

    Com isso basta fazer o load desta forma:

     

    public static bool TestarConexao(Banco banco){
    
    IProvedor provedor = GetConnection(banco.AssemblyName,banco.TypeName)
    
    return provedor.testar(banco);
    }
    
    

    Nesse caso você deverá não passar mais um nome qualquer e sim ter 2 propriedades a mais nessa classe banco, uma com o assembly do provider e outra com o nome completo da classe.

    Peguei esse exemplo reflection em http://authors.aspalliance.com/thycotic/articles/view.aspx?id=5 fiz algumas modificações para o seu caso específico!

    Qualquer dúvida avise!


    Tornar o simples complicado é facil, tornar o complicado simples é criatividade, vontade e conhecimento






    quarta-feira, 10 de agosto de 2011 15:51

Todas as Respostas

  • Olá Fernando,

    Se eu entendi bem, você podeia implementa o "Factory Pattern", acho isso resolveria seu problema.

    da uma olhada nesse link.

    http://www.c-sharpcorner.com/UploadFile/mosessaur/simplefactorypattern03012006124722PM/simplefactorypattern.aspx


    Artur Araújo
    quarta-feira, 10 de agosto de 2011 14:16
  • na verdade eu já uso uma Factory, só não coloquei aqui pra não ficar poluido mas no fim a minha Factory se parece com isso:

     

    public IProvedor CreateProvedor(string tipo) 
    {
    if (tipo == "mysql")
     return new ProvedorMysql();
    if (tipo == "SqlServer2000")
     return new ProvedorSqlServer2000();
    if (tipo == "SqlServer2008")
     return new ProvedorSqlServer2008();
    }
    

    ou seja, continuo com os Ifs

    quarta-feira, 10 de agosto de 2011 14:57
  • Olá Fernando,

    Excelente dúvida.

    Nestes casos existem diversas formas de associar o banco aí, este tipo de arquitetura esta diretamente relacionada a Injeção de Dependência, no caso, o objetivo é injetar um objeto que implemente IProvedor a sua factory, o problema é que aí no TestarConexao você esta passando uma classe com uma string e fazendo um monte de IF, em questão de arquitetura esta errado, você precisa injetar a responsabilidade em 3 opções:

    1. passando o objeto que implemente IProvider (este seria o tipo Interface Injection), 
    2. utilizando REFLECTION na raça
    3. utilizando algum container de inversão de controle como o SPRING que através de um arquivo de configuração XML voce diz qual a classe a ser instanciada e ele ja tem o reflection implementado).

    Em primeiro momento, seria possível já passar o provedor instanciado para o TestaConexão ao invés do banco?

    Caso contrário segue um exemplo de reflection na mão (opção 2):

     

    using System.Reflection;
    public class LateBinder 
    {
     /// <exception cref="System.TypeLoadException">The type couldn't be 
     /// loaded.</exception>
     /// <exception cref="System.Exception">The assembly couldn't be loaded 
     /// or other errors.</exception>
     /// <exception cref="System.ArgumentNullException">fullyQualifiedTypeName 
     /// is a null reference.</exception>
     public static object CreateInstance(string assemblyName,string fullyQualifiedTypeName)
     {
     Type type = null;
     if (assemblyName != null) 
     {
     // this method will also search the GAC and will use the latest
     // version if multiple versions are found.
     Assembly assembly = Assembly.LoadWithPartialName(assemblyName);
     if (assembly != null) 
     {
     type = assembly.GetType(fullyQualifiedTypeName,true,true);
     } 
     else 
     {
     throw new Exception("Failed to load assembly \"" + assemblyName + "\".");
     }
     } 
     else 
     {
     type = Type.GetType(fullyQualifiedTypeName,true,true);
     }
     // invoke the type's constructor to create an instance
     object instance = type.InvokeMember("",
     BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance,
     null,
     null,
     new object[] { });
     return instance;
     }
     public static object CreateInstance(string fullyQualifiedTypeName) 
     {
     return CreateInstance(null,fullyQualifiedTypeName);
     }
    }
    
    
    
    private static IProvider GetConnection(string assemblyName, string providerConnectionTypeName)
    {
    object o = LateBinder.CreateInstance(assemblyName,providerConnectionTypeName);
    if (!(o is IProvider)){
    throw new Exception("\"" + providerConnectionTypeName  + "\" doesn't implement IProvider.");
    }
    return (IProvider) o;
    }

    Com isso basta fazer o load desta forma:

     

    public static bool TestarConexao(Banco banco){
    
    IProvedor provedor = GetConnection(banco.AssemblyName,banco.TypeName)
    
    return provedor.testar(banco);
    }
    
    

    Nesse caso você deverá não passar mais um nome qualquer e sim ter 2 propriedades a mais nessa classe banco, uma com o assembly do provider e outra com o nome completo da classe.

    Peguei esse exemplo reflection em http://authors.aspalliance.com/thycotic/articles/view.aspx?id=5 fiz algumas modificações para o seu caso específico!

    Qualquer dúvida avise!


    Tornar o simples complicado é facil, tornar o complicado simples é criatividade, vontade e conhecimento






    quarta-feira, 10 de agosto de 2011 15:51
  • Amigo, obrigado por responder.

    Reflection foi o que o pessoal aqui da empresa me aconselhou, e eu acabei usando o  Activator.CreateInstance() do proprio framework.

    A arquitetura do projeto possue classes burras que recebem os dados do database. portatnto a principio nao posso mudar a classe Banco.

    Acabei por mudar os nomes dos provedores para "Provedor" + Banco.Tipo.

    thanks!!

    quinta-feira, 11 de agosto de 2011 15:28