VB .NET - O padrão Factory revisitado


Este artigo vai tratar do conceito que envolve padrão de projetos, especificamente o padrão de projeto Factory. (eu já tratei desse assunto no artigo: Design Patterns - o padrão Factory )

Se você é novato na programação orientada a objetos pode ficar muito confuso ao tentar entender os conceitos sobre padrão de projetos e a descrição de um padrão como o padrão Factory.

Pensando nisso, resolvi dar uma pequena introdução aos conceitos da programação orientada a objetos (daqui pra frente chamarei de POO) para que o entendimento seja facilitado.

Um introdução bem básica ao conceito da POO para os inicantes.

A base do conceito da POO são classes e objetos.

Nota: A literatura cita que os pilares da POO são: Abstração, Encapsulamento, Herança e Polimorfismo.

E este conceito base é muito importante e pode confundir um iniciante. Afinal qual a diferença entre classes e objetos ?

Pense em uma classe como um modelo que é usado para criar algo.

Como exemplo pense em um bolo de banana. Para fazer um bolo de banana precisamos de uma receita de bolo de banana.

A receita é a classe e o bolo o objeto.

  ==>
Classe   Objeto

A classe é apenas usada para criar os objetos baseados nela.

São os objetos que realmente são usados na POO. Afinal ninguém vai comer uma receita de bolo de banana mas o bolo de banana que a receita criou.

Na POO é comum dizer que um objeto é a instância de uma classe. (guarde bem este conceito pense no bolo criado pela receita.)

Na linguagem Visual Basic .NET e C# (e qualquer outra linguagem orientada a objetos) tudo são objetos.(não vou polemizar...)

Para criar uma nova classe na linguagem VB .NET usamos as palavra chave Class/End Class. Abaixo temos a criação da classe Pessoa no VB .NET:

Obs: por convenção o nome de uma classe começa com caixa alta.

Public Class Pessoa

---- Variáveis
-----Metodos
-----Propriedades
-----Eventos

End Class

Obs: Uma classe pode conter variáveis, métodos, propriedades e eventos.

Para criar um objeto da classe devemos criar uma instância da classe e isso é feito na linguagem VB .NET usando a palavra-chave new.

Assim para criar o objeto macoratti como instância da classe Pessoa fazemos assim:

Dim macoratti as Pessoa = new Pessoa()    ou assim    Dim macoratti as Pessoa

macoratti = new Pessoa()

A classe Pessoa vai ser usada através do objeto macoratti instanciado a partir da classe.

Resumindo:

1- Objeto

- Um objeto é a representação de um modelo abstrato definido em uma classe;
- Um objeto é uma instância de uma classe;
- O estado de um objeto é o conjunto de suas propriedades;
- Cada objeto tem o seu próprio estado;
- O comportamento de um objeto são as funções (métodos) que afetam suas propriedades e/ou as propriedades de outros objetos;

2- Classe

- Uma classe é o modelo a partir do qual os objetos serão criados;
- Um objeto é uma instância de uma classe;
- Uma classe define as propriedades e comportamento que cada uma de suas instâncias(objetos) possuirá;
- Classes podem se comunicar em entre si trocando informações(dados, parâmetros, controles,etc.);

O padrão Factory

Feita a introdução voltemos ao assunto principal do artigo : o padrão Factory.

Para não ficar me repetindo vou citar o conceito adaptado da wikipédia.

Um padrão de projeto simplesmente descreve uma solução para um problema recorrente no desenvolvimento de sistemas de software orientados a objetos. Um padrão de projeto estabelece um nome, define o problema, a solução, quando aplicar esta solução e suas conseqüências.(adpatado da wikipédia).

O objetivo em usar um padrão de projeto é : Não reinventar a roda

Para definir o padrão Factory vou me repetir:

O padrão Factory fornece uma interface para a criação de famílias de objetos correlatos ou dependentes sem a necessidade de especificar a classe concreta destes objetos.

Por exemplo , quando estamos trabalhando com uma interface e temos mais de uma implementação para esta interface, podemos utilizar uma fábrica para criar um objeto que implementa a interface; a fábrica pode selecionar a implementação que ela retorna.

A utilização do padrão Factory é útil quando você precisa criar objetos dinamicamente sem conhecer a classe de implementação, somente sua interface: o padrão factory estabelece uma forma de desenvolver objetos que são responsáveis pela criação de outros objetos.

Para melhor entender a utilização do padrão e sua atuação vou usar um exemplo a criação de uma fábrica de empregados usando a linguagem VB .NET.

Vamos imaginar uma aplicação que possui uma interface para aceitar a seleção por parte do usuário do tipo de funcionário e que partir da seleção crie novos objetos funcionario. A aplicação lida com 3 tipos de funcionários representados pelas seguintes classes:

Cada uma das classes acima herda da classe Funcionario que possui propriedades e métodos comuns a todos os funcionários, como nome, matricula, setor, etc.

A seguir temos o código de cada uma das classes usadas pela aplicação na linguagem VB .NET:

Funcionario FunciAssalariado FunciHorista FunciEstagiario
Public Class Funcionario
    Private _nome As String
    Private _matricula As String
    Private _setor As String
    Private _tipo As String

    Property Nome As String
        Get
            Return _nome
        End Get
        Set(ByVal value As String)
            _nome = value
        End Set
    End Property
    Property Matricula As String
        Get
            Return _matricula
        End Get
        Set(ByVal value As String)
            _matricula = value
        End Set
    End Property
    Property Setor As String
        Get
            Return _setor
        End Get
        Set(ByVal value As String)
            _setor = value
        End Set
    End Property
    Property Tipo As String
        Get
            Return _tipo
        End Get
        Set(ByVal value As String)
            _tipo = value
        End Set
    End Property
End Class
Public Class FunciAssalariado
    Inherits Funcionario

    Private _valorSalario As Double
    Property Salario As Double
        Get
            Return _valorSalario
        End Get
        Set(ByVal value As Double)
            _valorSalario = value
        End Set
    End Property

End Class
Public Class FunciHorista
    Inherits Funcionario

    Private _valorHora As Double

    Property ValorHora As Double
        Get
            Return _valorHora
        End Get
        Set(ByVal value As Double)
            _valorHora = value
        End Set
    End Property
End Class
Public Class FunciEstagiario
    Inherits Funcionario

    Private _valorEstagio

    Property ValorEstagio As Double
        Get
            Return _valorEstagio
        End Get
        Set(ByVal value As Double)
            _valorEstagio = value
        End Set
    End Property

End Class

Observe que todas as classes herdam da classe Funcionario. A herança no VB .NET é implementada usando a palavra-chave inherits.

O formulário da aplicação é bem simples e aceita a informação do nome, matrícula, setor e do tipo de funcionário.

Ao clicar no botão Criar a aplicação executa o seguinte código:

 Private Sub btnCriarFuncionario_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCriarFuncionario.Click

        Dim novoFuncionario As Funcionario = Nothing

        If cboTipoFuncionario.SelectedItem.ToString = "Assalariado" Then
            novoFuncionario = New FunciAssalariado
        ElseIf cboTipoFuncionario.SelectedItem.ToString = "Horista" Then
            novoFuncionario = New FunciHorista
        ElseIf cboTipoFuncionario.SelectedItem.ToString = "Estagiário" Then
            novoFuncionario = New FunciEstagiario
        End If

        novoFuncionario.Nome = txtMatricula.Text
        novoFuncionario.Matricula = txtMatricula.Text
        novoFuncionario.Setor = txtSetor.Text
        novoFuncionario.Tipo = cboTipoFuncionario.SelectedItem.ToString

    End Sub

O código acima é muito simples mas esconde alguns problemas. Vejamos:

O código esta fortemente acoplado com a classe Funcionario.

Mas o que vem a ser esse negócio de fortemente acoplado ? Isso é bom ou ruim ?

Para esclarecer vejamos rapidamente o que significa isso:

Acoplamento

- Acoplamento é o nível de dependência/conhecimento que pode existir entre as classes;
- Uma classe com acoplamento fraco não é dependente de muitas classes para fazer o que ele tem que fazer;
- Uma classe com acoplamento forte depende de muitas outras classes para fazer o seu serviço;
- Uma classe com
acoplamento forte é mais difícil de manter, de entender e de ser reusada;

Para ilustrar imagine que um novo tipo de funcionário deverá ser tratado pela aplicação : o funcionário Contratado (classe FunciContratado) que tem a propriedade valor do contrato: ValorContrato.

Quais as implicações dessa alteração no código:

  1. Deverá ser criada a nova classe FunciContratado que herda(inherits) de Funcionario;
  2. O novo tipo de funcionário deverá ser cadastrado;
  3. O código do formulário deverá ser modificado para criar a instância do objeto do tipo funcionário Contratado;

As duas primeira alterações são inevitáveis e são uma consequência da mudança na regra de negócio da aplicação mas a última alteração pode trazer implicações que poderão afetar toda a minha aplicação pois existe um forte acoplamento do código com a classe Funcionario.

Não seria maravilhoso se fazer essa alteração não precisássemos realizar nenhuma alteração no código do formulário a não ser a recompilação das classes ?

Mas como seria isso possível ?

Que tal se movermos o código que cria as instâncias das classes do formulário para a classe Funcionario ?

Com isso estaríamos minimizando o impacto causando pela alteração e tornando o código menos acoplado a classe Funcionario.

Aqui entra o padrão Factory.

Vamos incluir o seguinte código na classe Funcionario:

  Public Shared Function CriarFuncionario(ByVal funciTipo As String) As Funcionario

        Dim novoFuncionario As Funcionario = Nothing

        If funciTipo = "Assalariado" Then
            novoFuncionario = New FunciAssalariado
        ElseIf funciTipo = "Horista" Then
            novoFuncionario = New FunciHorista
        ElseIf funciTipo = "Estagiário" Then
            novoFuncionario = New FunciEstagiario
        ElseIf funciTipo = "Contratado" Then
            novoFuncionario = New FunciContratado
        End If

        Return novoFuncionario

    End Function

Este método estático chamado CriarFuncionario() recebe o tipo de funcionário e cria uma instância da respectiva classe do funcionário.

As propriedades e métodos de uma classe podem ser membros de instância da classe ou membros compartilhados(shared) ou estáticos.

Membros de instância são associados com instâncias de um tipo , enquanto que membros estáticos (shared) são associados com a classe e não com uma instância em particular.

Métodos são métodos de instância a menos que você explicitamente os defina como métodos estáticos (shared) usando a palavra-chave Shared.

A grande maioria dos métodos são métodos de instância , isto significa que você deve acessá-los a partir de um objeto especifico. Quando você precisar de um método de classe , ou seja , um método que seja acessado diretamente da classe você deve definí-lo como estático ou Shared.

O código do formulário ficaria da seguinte forma:

 Private Sub btnCriarFuncionario_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCriarFuncionario.Click

        Dim novoFuncionario As Funcionario
        novoFuncionario = Funcionario.CriarFuncionario(cboTipoFuncionario.SelectedItem.ToString)

        novoFuncionario.Nome = txtMatricula.Text
        novoFuncionario.Matricula = txtMatricula.Text
        novoFuncionario.Setor = txtSetor.Text
        novoFuncionario.Tipo = cboTipoFuncionario.SelectedItem.ToString

    End Sub

Observe que não temos mais a palavra-chave new sendo usada para criar instâncias das classes de funcionários, todo o código responsável pela criação das instâncias foi movido para a classe Funcionario que é quem deve saber como fazer isso. (Veja o princípio da responsabilidade única - SRP)

O que fizemos foi implementar o padrão Factory para resolver o nosso problema. A nossa implementação retorna uma instância de uma das diversas classes dependendo do parâmetro passado para o método estático (Shared) CriarFuncionario() que é o único responsável por retornar a instância da classe apropriada.

Obs: Poderíamos criar outra classe chamada FuncionarioFactory() para realizar o mesmo trabalho.

Se você desejar proteger a classe Funcionario de forma que a mesma não seja instanciada diretamente pela aplicação basta definir um construtor protegido da seguinte forma:

Protected Sub New()

End Class

Fazendo isso você não permite que ninguém que esteja fora da sua classe crie uma instância da mesma. Se alguém tentar criar uma instância de Funcionario no formulário obterá uma mensagem de erro conforme a figura abaixo:

Dessa forma você 'força' a utilização da sua classe para criar instâncias das classes de funcionários.

Com isso mostrei como usar o padrão Factory e qual o conceito básico associado a ele.

Pegue o código completo usado no artigo aqui: padraoFactory.zip

Eu sei é apenas VB .NET, mas eu gosto...

Referências:

José Carlos Macoratti