msdn11_capa.jpg

Clique aqui para ler todos os artigos desta edição

 

Programação Orientada a Objetos em .NET – Parte 2

por Claudio Lassala

 

Na parte 1 deste artigo, publicada na edição anterior, vimos os principais pilares da Programação Orientada a Objetos (POO): Abstração, Encapsulamento, Herança e Polimorfismo. Mas além disso, existem alguns outros conceitos importantes à serem vistos, como Delegação, Composição e Agregação, bem como veremos brevemente um pouco sobre UML e Design Patterns.

 

Matendo Foco no Conceito

Assim como na parte 1 deste artigo, nesta parte 2 continuarei mantendo foco em conceitos, pois acredito ser parte importante no aprendizado e utilização de POO. Vale dizer que estou deliberadamente deixando de fora alguns pontos, como métodos ou campos, por exemplo, pois suponho que todo desenvolvedor .NET já os vem utilizando de uma forma ou outra no dia-a-dia, e informações técnicas sobre os mesmos podem ser encontradas no help do VS.NET.

 

Visibilidade de Membros de Classes

A visibilidade de membros pode ser controlada utilizando modificadores como Public ou Protected. Isto é importante para reforçar o encapsulamento de dados e funcionalidade dentro de objetos. Membros públicos  (public) são acessíveis por qualquer objeto, enquanto que membros protegidos (protected) são acessíveis apenas pela própria classe e suas subclasses. Membros privados (private) são acessíveis apenas pela própria classe, mas não para suas subclasses. Existem ainda os membros internos ou amigáveis (internal em C#, friend em VB.NET), que são visíveis apenas dentro do assembly em que foram criados.

Por exemplo, o código na Listagem 1 define a classe ServicoDeDados. Esta classe é utilizada para executar comandos SQL em um banco de dados. O código é bem simples pois a intenção é mostrar a proteção de membros controlando sua visibilidade; em um cenário real, a classe seria muito mais complexa.

A class ServicoDeDados deve ser capaz de executar comandos SQL em um banco de dados, mas o usuário da classe não deve ser capaz de ter acesso direto à conexão (implementada por um objeto SqlConnection). Tal funcionalidade é implementada criando na classe um campo chamado connection, e este campo é marcado como protected.

 

Listagem 1. Mantendo encapsulamento com membros protegidos.

// Classe ServicoDeDados.

public class ServicoDeDados

{

   // Campo protegido "connection".

   // Mantém um objeto de conexão.

   protected SqlConnection connection = new SqlConnection();

 

   // "Construtor" da classe.

   public ServicoDeDados()

   {

      // Configura string de conexão.

      this.connection.ConnectionString =

         "server=(local);database=northwind;Trusted_Connection=yes";

 

      // Abre conexão.

      this.connection.Open();

   }

 

   // Método Executar.

   public DataSet Executar(string comando)

   {

      // Criar DataSet que será retornado.

      DataSet ds = new DataSet();

     

      // Criada objeto Command e DataAdapter.

      SqlCommand com = new SqlCommand(comando, this.connection);

      SqlDataAdapter da = new SqlDataAdapter(com);

 

      // Preenche DataSet com resultado de Query.

      da.Fill(ds);

 

      // Retorna DataSet.

      return ds;

   }

}

 

Note que o objeto de conexão é acessado pela classe internamente, utilizando this.connection. A Listagem 2 mostra uma possível utilização da classe ServicoDeDados. O código reside em um método qualquer dentro de um form, o qual possui um DataGrid chamado dgResultado.

 

Listagem 2. Utilizando a classe ServicoDeDados.

ServicoDeDados sd = new ServicoDeDados();

DataSet dsProdutos = sd.Executar("Select * from Products");

this.dgResultado.DataSource = dsProdutos.Tables[0];

 

O usuário da classe pode apenas chamar o método Executar, passando o comando SQL, e nada mais. Qualquer tentativa de acesso ao objeto de conexão (como em sd.connection) causará um erro de compilação pelo fato do objeto de conexão estar protegido dentro da classe ServicoDeDados.

 

Delegação

Quando uma classe não possui uma determinada funcionalidade por não ter herdado a mesma de uma superclasse, a solução é delegar a responsabilidade para outra classe. Esta delegação pode acontecer através de Composição ou Agregação.

Tome por exemplo a classe Negocios, definida na Listagem 3. Esta classe deve servir como classe-base para objetos de negócio (objetos que modelam entidades do mundo real, como produtos, clientes, pedidos, etc.). Note que a classe Negocios possue campos protegidos (como tabelaPrincipal e chavePrimaria), os quais não devem ser acessados fora da classe.

 

Listagem 3. Definição da classe Negocios.

// Classe base para objetos de negócios

public abstract class Negocios

{

   // Armazena nome de tabela principal do objeto

   protected string tabelaPrincipal = "";

 

   // Armazena lista de campos a serem retornados em consulta

   protected string campos = "*";

 

   // Armazena nome do campo que é chave primária

   protected string chavePrimaria = "";

 

   // Método TrazerLista: retorna lista de registros da

   // tabela principal

   public DataSet TrazerLista()

   {

      // Criar conexão com banco de dados

      SqlConnection conn = new SqlConnection();

      conn.ConnectionString = 

            "server=(local);database=northwind;Trusted_Connection=yes";

      conn.Open();

 

      // Criar DataSet

      DataSet ds = new DataSet();

           

      // Monta comando SQL

      string comando = "select " + this.campos + " from " + this.tabelaPrincipal;

     

      // Criada objeto Command e DataAdapter

      SqlCommand com = new SqlCommand(comando, conn);

      SqlDataAdapter da = new SqlDataAdapter(com);

 

      // Preenche DataSet com resultado de Query

      da.Fill(ds);

 

      // Retorna DataSet

      return ds;

   }

}

 

A classe Negocios possue um método chamado TrazerLista. Este é um método de uso genérico que retorna uma lista de registros da tabela principal controlada pelo objeto. O ponto importante a se enfatizar sobre o método TrazerLista é que o mesmo possui código específico sobre como se conectar a um banco de dados e executar uma consulta. Isso é algo que está fora das responsabilidades de um objeto de negócios (o qual deve ser responsável apenas por executar código referente a regras de negócios, e não conexão à bancos de dados). Além disso, se a classe tiver outros métodos que necessitem acessar ao banco de dados, o volume de código deste tipo torna-se ainda maior, escondendo ainda mais o código relacionado à negócios.

Uma solução muito melhor é delegar a responsabilidade que se refere à acesso a dados para outro objeto, cujo responsabilidade limita-se a somente isto. E tal objeto foi criado anteriormente neste artigo: ServicoDeDados. A Listagem 4 mostra a classe Negocios que faz uso de delegação.

 

Listagem 4. Classe Negocios usando Delegação.

// Classe base para objetos de negócios.

public abstract class Negocios

{

   // Armazena referência para Serviço de Dados

   protected ServicoDeDados sd = new ServicoDeDados();

 

   // Armazena nome de tabela principal do objeto

   protected string tabelaPrincipal = "";

 

   // Armazena lista de campos a serem retornados em consulta

   protected string campos = "*";

 

   // Armazena nome do campo que é chave primária

   protected string chavePrimaria = "";

 

   // Método TrazerLista: retorna lista de registros da

   // tabela principal

   public DataSet TrazerLista()

   {

      // Monta comando SQL e o delega para Serviço de Dados

      DataSet ds = this.sd.Executar("select " + this.campos +

                            " from " + this.tabelaPrincipal);

 

      // Retorna DataSet

      return ds;

   }

}

 

Note que a classe Negocios além de utilizar agora menos código, também não possui código lidando diretamente com criação de objectos SqlConnection, DataAdapter, Command, ou qualquer coisa neste sentido. Ao invés disso, a classe agora possui um campo chamado sd, do tipo ServicoDeDados. Este campo está marcado como protegido, e portanto, não pode ser acesso de fora da classe (não é aconselhável permitir que usuários da classe Negocios acessem diretamente ao objeto que lida com acesso aos dados). O método TrazerLista foi alterado para apenas delegar o comando SQL para o objeto ServicoDeDados, o qual é responsável por criar a conexão com o banco de dados e executar a consulta.

 

Neste ponto, o objeto Negocios tem controle sobre a criação do objeto ServicoDeDados. Por causa disto, diz-se que a delegação neste caso ocorre através de Composição, visto que o objeto ServicoDeDados é parte do objeto Negocios. Em outras palavras, o objeto Negocios tem um objeto ServicoDeDados como parte de sua composição.

A Listagem 5 mostra a classe Produto, que é uma subclasse da classe Negocios. A classe Produtos é utilizada para lidar com dados de produtos. O construtor desta classe apenas alimenta os campos da classe que determinam detalhes importantes, como qual é a principal tabela que contém os dados sobre produtos, qual é o campo que atua como chave-primária na tabela, e qual é a lista de campos a serem retornados em pesquisas genéricas.

 

Listagem 5. Classe Produto.

// Classe Produto (objeto de negócios).

public class Produto : Negocios

{

   // Construtor da classe.

   public Produto()

   {

      // Inicializa principais campos do objeto

      this.tabelaPrincipal = "Products";

      this.chavePrimaria = "ProductID";

      this.campos = "ProductName, UnitPrice";

   }

}

 

A Listagem 6 mostra a classe ProdutosForm, que é um form utilizado para visualização da lista de produtos retornada pelo objeto de negócios Produto. Este form contém uma referência a um objeto do tipo Produto, mas a técnica de composição não será utilizada, pois em situações onde o form é instanciado diversas vezes, uma cópia do objeto Produto é criada para cada instância do form. Ao invés disso, utilizamos a técnica de Agregação¸ e apenas uma referência a um objeto Produto existente é passado para o form, e deste modo apenas um objeto Produto é instanciado, não importando quantos forms foram criados. Na agregação, o objeto que delega responsabilidades a outro não tem controle sobre a criação (e eventual destruição) do objeto que recebe a responsabilidade.

 

Listagem 6. Classe ProdutosForm.

public class ProdutosForm : System.Windows.Forms.Form

{

   Código gerado pelo VS.NET foi omitido aqui para clareza do exemplo

 

   // Referência à objeto Produto

   protected Produto prod = null;

 

   // Método usado para agregar objeto Produto a este form

   public void AgregaObjetoDeNegocios(Produto produto)

   {

      // Agrega referência à objeto Produto

      this.prod = produto;

 

      // Invoca método para atualizar grade

      this.AtualizarGrade();

   }

 

   // Método para atualizar grade

   public void AtualizarGrade()

   {

      // Verifica se temos referência para objeto Produto

      if (this.prod != null)

      {

         // Se referência existe, trazer lista de produtos

         DataSet ds = this.prod.TrazerLista();

 

         // e vincular à DataGrid

         this.dgListaDeProdutos.DataSource = ds.Tables[0];

      }

      else

      {

         // Se não existir referência, mostrar mensagem

         MessageBox.Show("Form não possui objeto de negócios.");

      }

   }

}

 

A razão pela qual o campo prod está marcado como protegido dá-se pelo fato de que não é aconselhável permitir a outros objetos acessarem objetos de negócio sendo utilizado por uma classe de interface com o usuário (neste exemplo, o form ProdutosForm). Por este motivo, encapsulamos o objeto de negócios dentro da classe de form. A Listagem 7 mostra um trecho de código que coloca em prática a agregação.

 

Listagem 7.  Delegação por Agregação.

Produto prod = new Produto();

 

ProdutosForm form1 = new ProdutosForm();

form1.AgregaObjetoDeNegocios(prod);

form1.Show();

 

ProdutosForm form2 = new ProdutosForm();

form2.AgregaObjetoDeNegocios(prod);

form2.Show();

 

O exemplo instancia a classe Produto, cria duas instâncias da classe ProdutosForm, e agrega o objeto Produto aos dois forms através do método AgregaObjetoDeNegocios. A Figura 1 mostra o resultado.

 

image002.jpg 

Figura 1. Forms utilizando objeto de negócio por agregação.

UML - Unified Modeling Language

Toda linguagem é uma forma de comunicação. Por exemplo, duas pessoas podem se comunicar através de linguagem escrita, oral, corporal, etc. A comunicação sem alguma forma padrão de comunicação torna-se realmente difícil. Para facilitar a comunicação, símbolos são criados e agrupados em linguagens. Isto não é diferente no mundo do desenvolvimento de software.

Aplicações escritas usando POO normalmente tornam-se bastante complexas, e o uso de algum tipo de idioma padrão pode ajudar em áreas como projeto, documentação e comunicação entre desenvolvedores e pessoas envolvidas com o projeto. No mundo da POO, esta linguagem é a Unified Modeling Language (Linguagem Unificada de Modelagem), ou UML.

UML não é uma linguagem de programação; ao invés disto, UML define um conjunto de simbolos e diagramas que facilitam o projeto de classes (delineando definições das classes e interação entre diferentes classes, por exemplo), criam uma compreensiva documentação da aplicação, e facilitam a comunicação entre membros da equipe de desenvolvimento.

Esta seção do artigo oferece uma introdução à UML, pois isto é algo muito importante para qualquer desenvolvedor criando aplicações orientas a objeto. Para uma introdução mais detalhada, recomendo a leitura do livro UML Distilled, Second Edition, escrito por Martin Fowler. Este é um livro de fácil leituras, poucas páginas, e provê um ótimo ponto de partida no aprendizado e utilização de UML.

 

Diagrama de Classes

O diagrama de classes é um dos diagramas mais utilizados em UML. Um diagrama de classes define um modelo para classes, mostrando detalhes como quais são os membros disponíveis em classes, e qual é o relacionamento entre as classes.

Algumas classes possuim vários membros, um modelo de classe não precisa necessariamente mostrar todos os seus membros. Apenas os membros relevantes para algo que precisa ser entendido precisam ser mostrados. Por exemplo, é possível que o diagrama foi criado apenas para documentar as parte mais importantes de algumas classes, omitindo detalhes sobre membros irrelevantes para aquilo que se deseja representar pelo diagrama. Por outro lado, o desenvolvedor responsável por codificar a classe definitivamente precisa visualizar todos os detalhes.

Além dos membros de classes, o diagrama de classes também apresenta o relacionamento entre classes, como qual é a relação de herança entre duas ou mais classes, ou qual é a relação de agregação ou composição entre duas ou mais classes. A Figura 2 mostra um diagrama de classes que modela classes criadas neste artigo.

 

image004.jpg

Figura 2: Diagrama de Classes em UML.

 

Como diz o ditado, “uma imagem vale mais do que mil palavras”. Um desenvolvedor acostumado com UML pode facilmente interpretar este diagrama apresentado. Segue uma interpretação do diagrama:

 

·         A classe ServicoDeDados possui uma propriedade protegida do tipo SQLConnection, chamada connection. Um objeto do tipo ServicoDeDados delega responsabilidades para um objeto do tipo SqlConnection através de composição, e este objeto é armazenado na propriedade connection. Esta relação de agregação é indicada pelo “diamante preenchido” na linha que liga a classe SqlConnection à classe ServicoDeDados. Os números junto à esta linha indicam a cardinalidade (ou multiplicidade) nesta relação: um objeto do tipo ServicoDeDados sempre terá uma única instância da classe SqlConnection, ao passo que um objeto do tipo SqlConnection somente fará parte de um objeto do tipo ServicoDeDados, e não mais que isso.

·         A classe ServicoDeDados possui um método chamado Executar, que recebe um parâmetro do tipo string, e retorna um DataSet.

·         A classe SqlConnection possui uma propriedade chamada ConnectionString e um método chamado Open, sendo ambos membros públicos. Note que esta classe na verdade possui muitos outros membros, mas para este cenário, somente estes dois membros são relevantes (pois são os únicos usados no código de exemplo neste artigo).

·         Ambas as classes ServicoDeDados e SqlConnection são classes concretas, como indicado pelo nome das classes sendo formatadas em fonte normal (não-itálico).

·         A classe Negocios é uma classe abstrata, como indicado pelo nome da classe sendo formatado em itálico.

·         A classe Negocios tem três propriedades protegidas do tipo string: tabelaPrincipal, campos e chavePrimaria. A classe tem uma propriedade protegida chamada sd, do tipo ServicoDeDados. A propriedade sd armazena referência para um objeto do tipo ServicoDeDados, indicada pela relação de composição entre as classes Negocios e ServicoDeDados. A cardinalidade indica que um objeto do tipo Negocios terá somente uma instância de um objeto do tipo ServicoDeDados, e um objeto do tipo ServicoDeDados servirá apenas a um objeto do tipo Negocios.

·         A classe Negocios possui um método público chamado TrazerLista, o qual retorna um DataSet..

·         A classe Produto (a qual é uma classe concreta) é uma sub-classe da classe Negocios, relação esta indicada pelo tipo de seta que liga a classe Produto à classe Negocios.

·         A classe produto sobrescreve as propriedades tabelaPrincipal, chavePrimaria e campos, as quais foram herdadas da class Negocios, e pode-se ver quais são os valores iniciais atribuídos àquelas propriedades.

·         A classe Produto sobrescreve seu construtor (o método New, que na verdade em C# é um método com o mesmo nome da classe, mas na minha opinião a palavra New, usada em VB.NET, faz mas sentido para indicar construtor).

·         A classe ProdutosForm possui uma propriedade protegida do tipo Produto, chamada prod. Esta propriedade armazena uma referência para um objeto do tipo Produto através de agregação, como indicado pelo “diamante vazio” presente na linha que liga a class Produto à classe ProdutosForm. A cardinalidade desta relação indica que um objeto do tipo produto pode ser agregado em zero ou n (0..*) objetos do tipo ProdutosForm, enquanto que um objeto do tipo ProdutosForm pode agregar zero ou um (0..1) objeto do tipo Produto.

·         A classe ProdutosForm possui três métodos públicos: o método AgregaObjetoDeNegocios, o qual recebe um parâmetro do tipo Produto e retorna void, o método AtualizarGrade, o qual não recebe parâmetros e retorna void, e o construtor da classe. Indicar o construtor da classe no modelo é importante para enfatizar que o mesmo está sobrescrito e possui algum código relevante.

 

Tudo bem, neste caso, uma imagem não valeu por mais de mil palavras (foram apenas umas 551 palavras), mas provavelmente foram suficientes para enfatizar como diagramas de classes podem facilitar na compreensão de sistemas complexos, onde muitas classes e objetos são utilizados.

 

Diagrama de Sequência

O diagrama de sequência é outro diagrama bastante útil e dos mais usados em UML. Trata-se de um diagrama do tipo “diagrama de interação”. Diagramas de interação apresentam a integração entre diferente objetos. O diagrama de sequência mostra a sequência de ações na interação entre objetos, onde normalmente esta interação se inicia em resposta a alguma ação do usuário. Este tipo de diagrama é extremamente útil na compreensão da sequência e order de mensagens enviadas entre objetos.

A Figura 3 mostra um diagrama de sequência que representa a principal interação entre os objetos criados no exemplo apresentado neste artigo.

Novamente, um desenvolvedor familiarizado com UML não enfrenta problemas tentando compreender o diagrama apresentado. Diagramas de sequência são analisados da esquerda para a direita, de cima para baixo. Segue uma interpretação do diagrama:

 

·         O usuário manda uma mensagem Click para o objeto Botão (em outras palavras, clica em um botão que está em algum form. O form está omitido neste diagrama por não ser realmente relevante neste caso).

·         Durante a execução do método Click, várias ações ocorrem, e a extensão da execução do método Click é indicado pelo retângulo vertical que vai do início da mensagem Click até quase o rodapé do diagrama.

·         O método Click cria o objeto prod, usando o comando new.

·         O método Click manda uma mensagem AgregaObjetoDeNegocios para o objeto form1. Ao mandar esta mensagem (que ocorre invocando o método AgregaObjetoDeNegocios no objeto form1), passa como parâmetro uma referência para o objeto prod, que foi criado no passo anterior.

·         Dentro do método AgregaObjetoDeNegocios, o objeto form1 chamada uma mensagem para si mesmo, invocando o método AtualizarGrade.

·         Ainda dentro do método AgregaObjetoDeNegocios, o objeto form1 manda uma mensagem para o objeto prod, invocando o método TrazerLista naquela objeto. Este segundo objeto prod é na verdade acessado através da referência interna que o objeto form1 tem para o objeto prod instanciado anteriormente.

·         O método TrazerLista invoca o método Executar do objeto sd, passando como parâmetro o comando necessário. Para auxiliar na compreensão do que é o objeto sd, foi colocado uma nota próxima ao mesmo, indicando que aquele é um objeto do tipo ServicoDeDados.

·         O método Executar retorna um DataSet para o método que o chamou (neste caso, TrazerLista).

·         O método TrazerLista retorna um DataSet com uma lista de produtos.

·         Finalmente, a execução de código retorna para o método Click do objeto Botão.

 

image006.jpg

 

Figura 3: Diagrama de Sequência em UML.

 

A princípio tudo isto possivelmente parecerá bastante confuso, mas a confusão é dissipada conforme o desenvolvedor passa a analizar e criar mais e mais diagramas.

 

Design Patterns

Design Patterns (ou Padrões de Planejamento, em uma tradução livre) são modelos que podem ser utilizados para resolver cenários recorrentes no desenvolvimento de software. Um catálogo de padrões possui a descrição de cada um dos principais padrões encontrados na criação de software. Um design pattern descreve um cenário comum recorrente, cita as dificuldades em tal cenário, e detalha como uma certa disposição e colaboração entre determinadas classes e objetos podem resolver o cenário.

Uma explicação detalhada sobre design patterns não faz parte do escopo deste artigo, mas este é um tópico bastante relevante no que se refere à POO e UML, onde um complementa o outro, e por isso a menção sobre o mesmo neste artigo, de modo a incentivar o leitor a procurar mais informações sobre o assunto.

 

Conclusão

Neste artigo cobrimos a visibilidade de membros em uma classe, delegação através de agregação e composição, além de uma breve introdução à UML e uma rápida menção aos Design Patterns.

Programação Orientada a Objetos está longe de ser um tópico de fácil compreensão. Basta ver que o paradigma foi introduzido ao mundo há mais de 30 anos, e ainda hoje existem desenvolvedores com muita dificuldade em entender aos conceitos e aplicá-los em seu dia-a-dia.

Diversos livros e artigos foram escritos sobre o tópico e em momento algum tive a intenção de substitui-los com este meu artigo, pois isto seria uma tarefa impossível de ser concretizada. Minha intenção foi realmente apresentar os conceitos utilizando exemplos simples para que o leitor pudesse familiarizar-se (e não assustar-se) com este assunto tão complexo de forma menos sofrível do que àquela apresentada por muitos autores.

Praticamente toda linguagem de programação moderna é toda baseada em objetos, e isto inclui linguagens .NET. Todo desenvolvedor de software deve entender muito bem o que são objetos, e como utilizá-los, para só assim levarem ao extremo sua capacidade de arquitetar e programar aplicações orientas a objetos.

Se existirem partes deste artigo que gostaria de rever em artigos mais específicos, mande um email para a revista ou para mim, e definitivamente estudaremos a viabilidade de publicar tais artigos.

Até a próxima!