C# -  Como exibir dados em um formulário ? - II


 Qual a melhor abordagem para exibir informações de um banco de dados em um formulário Windows Forms ?

Continuando a primeira parte do artigo vamos definir qual das abordagens disponíveis para acessar os dados e exibí-los no formulário é a mais adequada.

A resposta é curta e grossa: nenhuma das abordagens apresentadas é adequada.

Por que ?

Todas as abordagens ferem o princípio da responsabilidade única e misturam a responsabilidades da camada de apresentação com responsabilidades de acesso a dados.

Vamos analisar o que temos:

  1. Temos um formulário - frmDetalhesConta  - que é responsável por exibir os dados e para isso deve conhecer apenas os TextBox onde vai exibir os dados
  2. Temos uma classe Contas que trata as informações que vamos exibir: numeroConta, nomeConta, nomeBanco e tipoConta;
  3. Estamos acessando e obtendo os dados de um banco de dados SQL Server usando a ADO .NET;

Cada um dos artefatos presentes possui uma responsabilidade e deveria realizar uma tarefa específica sem conhecer nada sobre os demais artefatos. Isso é conhecido como baixo acoplamento e alta coesão.

O que temos em todas as abordagens é que a classe Contas é a classe que coordena tudo e tem responsabilidades de acessar e obter os dados e apresentá-los no formulário.

Vamos então apresentar uma abordagem mais consistente seguindo o princípio da responsabilidade única (SRP), do baixo acoplamento e da alta coesão.

Apresentando uma abordagem mais consistente

Primeiro vamos criar uma classe ContaInfo que será responsável apenas por expor os dados que desejamos exibir.

    public class ContaInfo
    {
        public string NumeroConta { get; set; }
        public string NomeConta { get; set; }
        public string TipoConta { get; set; }
        public string NomeBanco { get; set; }
        public string NomeCliente { get; set; }
    }

Como as informações serão obtidas não é problema dessa classe, ela não tem que conhecer nada sobre formulários, nada sobre acesso a dados, SQL, etc. Sua responsabilidade é apenas exibir dados e não obter dados.

Para obter dados vamos criar uma classe de serviço chamada ContasService com um método GetContaPorNumero() que vai usar o número da conta para obter informações e vai retornar essas informações que será usada pela classe ContaInfo.

using System.Data.SqlClient;
namespace WF_ExibirDados
{
    public class ContasService
    {
        private readonly string _connectionString;
        public ContasService(string connectionString)
        {
            _connectionString = connectionString;
        }
        public ContaInfo GetContaPorNumero(string numeroConta)
        {
            using (var connection = new SqlConnection(_connectionString))
            {
                var sql = "SELECT Cliente.Nome, Banco.ContaNome, Banco.BancoNome " +
                               "Banco.ContaTipo FROM Banco INNER JOIN Cliente ON " +
                               "Banco.ClienteId = Cliente.ClienteId WHERE " +
                               "Banco.ContaNumero = @numeroConta";
                connection.Open();
                using (var command = new SqlCommand(sql, connection))
                {
                    command.Parameters.AddWithValue("@numeroConta",numeroConta);
                    using (var reader = command.ExecuteReader())
                    {
                        if (reader.Read())
                        {
                            return new ContaInfo
                            {
                                NumeroConta = numeroConta,
                                NomeCliente = reader[0].ToString(),
                                NomeConta = reader[2].ToString(),
                                NomeBanco = reader[3].ToString(),
                                TipoConta = reader[4].ToString()
                            };
                        }
                    }
                }
            }
            return null;
        }
    }
}

Agora temos uma classe cuja responsabilidade é acessar o banco de dados. Através do método GetContaPorNumero() ela vai obter e retornar as informações que desejamos exibir usando o número da conta e retornando um tipo ContaInfo. Ela não conhece nada de formulário e não se importa como o objeto retornado será usado.

Neste ponto precisamos tormar um decisão de projeto e conectar os artefatos usados, para isso vamos definir o que cada um precisa saber:

  1. O formulário frmDetalhesConta precisa conhecer ContaInfo pois precisa exibir as informações que ela possui;
  2. A classe ContaInfo não precisa conhecer a classe ContasService apenas obter o objeto retornado por esta classe;
  3. A classe ContasService precisa conhecer a classe ContaInfo para poder passar o objeto deste tipo;

Como fazer para coordenar esses artefatos de forma consistente e sem misturar responsabilidades ?

Usando o padrão MVP - Model View Presenter

O padrão Model View Presenter (MVP) é um padrão de design particularmente útil para implementar interfaces de usuário de modo realizar o desacoplamento dos artefados em responsabilidades distintas tornando assim o código mais robusto e mais fácil de passar pelos testes unitários.

Este padrão separa as seguintes responsabilidades:

  1. O Model - Armazena os dados a serem exibidos ou executados na interface do usuário (UI);
  2. A View - Representa uma interface do usuário passiva que exibe os dados do Model e roteia os eventos iniciados pelo usuário, como comandos de mouse, para o Presenter responder conforme desejado;
  3. O Presenter - Atua sobre o Model e a View recuperando dados do modelo e exibindo na View;

Abaixo temos uma representação gráfica do padrão MVP :

Analisando nosso exemplo temos a View representanda pelo formulário frmDetalhesConta e o Model representando pela classe ContasService().

Precisamos criar uma classe que vai representar o Presenter e que vai coordenar a iteração entre a View e o Model.

Vamos então criar a classe ContaPresenter com o código abaixo:

using System;
using System.Windows.Forms;
namespace WF_ExibirDados
{
    public class ContaPresenter
    {
        private ContaView _form;
        private ContasService _service;
        public ContaPresenter(ContaView form, ContasService service)
        {
            _form = form;
            _form.OnShowContaInfo += View_OnShowContaInfo;
            _service = service;
        }
        public void Show()
        {
            _form.ShowDialog();
        }
        private void View_OnShowContaInfo(object sender, EventArgs e)
        {
            var info = _service.GetContaPorNumero(_form.NumeroConta);
            if (info == null)
            {
                MessageBox.Show("Conta não localizada.");
                return;
            }
            _form.NomeConta = info.NomeConta;
            _form.TipoConta = info.TipoConta;
            _form.NomeBanco = info.NomeBanco;
            _form.NomeCliente = info.NomeCliente;
        }
    }
}

Agora nossa classe ContaPresenter atua de forma a coordenar as informações do formulário, a nossa View, e da classe de acesso a dados , o nosso Model.

Para concluir falta definir a classe que chama o método Show da classe Presenter. Vamos criar uma classe chamada ContaView:

using System;
using System.Windows.Forms;
namespace WF_ExibirDados
{
    public partial class ContaView : Form
    {
        public TextBox txtNumeroConta;
        private Label label5;
        private Button btnConsultar;
        public TextBox txtTipoConta;
        private Label label4;
        public TextBox txtNomeBanco;
        private Label label3;
        public TextBox txtNomeConta;
        private Label label2;
        public TextBox txtNomeCliente;
        private Label label1;
        public event EventHandler OnShowContaInfo;
        public string NumeroConta
        {
            get { return txtNumeroConta.Text; }
            set { txtNumeroConta.Text = value; }
        }
        public string NomeConta
        {
            get { return txtNomeConta.Text; }
            set { txtNomeConta.Text = value; }
        }
        public string TipoConta
        {
            get { return txtTipoConta.Text; }
            set { txtTipoConta.Text = value; }
        }
        public string NomeBanco
        {
            get { return txtNomeBanco.Text; }
            set { txtNomeBanco.Text = value; }
        }
        public string NomeCliente
        {
            get { return txtNomeCliente.Text; }
            set { txtNomeCliente.Text = value; }
        }
        public ContaView()
        {
            InitializeComponent();
        }
        private void GetContaButton_Click(object sender, EventArgs e)
        {
            if (OnShowContaInfo != null)
            {
                OnShowContaInfo(this, EventArgs.Empty);
            }
        }
        + private void InitializeComponent() { }
    }
}

Assim temos a classe ContaView que apenas vai exibir os dados e notificar o Presenter sempre que for necessário ou que algum evento ocorra, e assim ela possa acessar o Model usando uma instância da classe ContaService().

Temos assim que cada artefato atua apenas na sua responsabilidade sem saber nada sobre os demais e ai temos tudo encapsulado e não estamos expondo TextBox de formulários mas sim strings que representam o valor que desejamos exibir.

Essa é uma abordagem mais consistente, desacoplada e que vai facilitar a manutenção e os testes unitários.

Lembrando que usamos o código deste artigo apenas para ilustrar a criação de uma solução usando uma abordagem desacoplada com o padrão MVP em um cenário simplificado.

Pegue o projeto completo aqui :  WF_ExibirDados2.zip

"Arrependei-vos, pois, e convertei-vos, para que sejam apagados os vossos pecados, e venham assim os tempos do refrigério pela presença do Senhor,
E envie ele a Jesus Cristo, que já dantes vos foi pregado."

Atos 3:19-20

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

 

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

 

Referências:


José Carlos Macoratti