Entendendo mais sobre classes

Vimos que uma classe é um modelo utilizado para criar objetos. Porém, uma classe é simplesmente um tipo especial, que além de armazenar dados, permite a execução de operações (funções) que chamamos de métodos. Em Java temos tipos primitivos como char, int e double. Variáveis desses tipos apenas armazenam dados. Um tipo primitivo não define internamente um conjunto de operações que podem ser feitas sobre os valores armazenados em tais variáveis. Um tipo como o int, por exemplo, poderia ter uma operação para indicar se o número armazenado na variável é par ou não.

Como nenhuma operação é definida para estes tipos, a manipulação dos dados armazenados em tais variáveis é mais complicada. Isto se deve ao fato de que, uma vez que precisarmos fazer uma operação como verificar se um número é par, devemos escrever nosso próprio código para isto ou utilizar uma função disponível em qualquer outro lugar.

Uma classe define os dados (atributos) e operações (métodos) que os objetos da classe terão, colocando tudo num só lugar. Assim, os métodos permitem que os atributos de cada objeto criado sejam manipulados. A manipulação desses dados é mais fácil, uma vez que qualquer método pode manipular qualquer um dos atributos declarados diratamente na classe.

Imagine o controle remoto de um ar condicionado: ele é um exemplo de um objeto criado a partir de uma classe que poderíamos chamar de ControleRemoto. Cada controle remoto armazena alguns dados como a temperatura configurada para o aparelho e as horas. Estes seriam atributos da classe. Os botões do controle remoto são métodos que permitem executar ações como ligar ou desligar o aparelho e alterar a temperatura. Os botões que permitem alterar a temperatura manipulam o atributo temperatura, aumentando ou reduzindo seu valor.

Se o controle remoto não fosse uma classe e sim um tipo primitivo, só poderíamos visualizar a temperatura e hora no painel do aparelho, enquanto que o controle serviria apenas para executar operações como aumentar e diminuir a temperatura. Com o controle remoto sendo uma classe, podemos tanto visualizar os atributos como executar operações para alterar seus valores. Isto torna muito mais fácil o uso do ar condicionado.

controle remoto ar.png
Figure 1. Um objeto de uma classe representando um controle remoto.

Criando novas classes

No Capítulo 4 foram sugeridas algumas classes para o sistema da loja de móveis, como Cliente, Produto, Funcionario, Venda e Empresa.

Vamos criar então a classe Empresa que permitirá representar a matriz e filiais da empresa. Para isso, crie tal classe no BlueJ adicionando os campos mostrados no código abaixo:

public class Empresa
{
    private String endereco;
    private String cidade;
    private String telefone;
    private String email;
    private boolean matriz;
}

Tal classe possui atributos para armazenar o endereço, cidade, telefone e email da empresa. Além destes, há um atributo do tipo boolean chamado matriz para indicar se a empresa é a matriz ou alguma filial. O tipo boolean é chamado de tipo lógico e permite armazenar valores true (verdadeiro) ou false (falso). Assim, se um objeto da classe Empresa tiver o valor true para tal atributo, tal instância da Empresa é a matriz, caso contrário será uma filial. Quando utilizamos o tipo boolean, queremos saber se algo é verdade ou falso. Logo, ao declarar o atributo matriz queremos saber se a empresa é uma matriz ou não.

Agora que temos o código inicial da nossa classe Empresa, devemos então criar os getter’s e setter’s que permitirão obter e alterar os valores dos atributos.

O código abaixo apresenta getter’s e setter’s para os atributos endereco e matriz.

public class Empresa
{
    private String endereco;
    private String cidade;
    private String telefone;
    private String email;

    /**
     * Situação da empresa: se ela é uma matriz ou não.
    */
    private boolean matriz;

    void setEndereco(String endereco){
        this.endereco = endereco;
    }

    String getEndereco(){
        return endereco;
    }

    void setMatriz(boolean matriz){
        this.matriz = matriz;
    }

    /**
     * Obtém a situação da empresa: se ela é uma matriz ou não.
     *
     * Getters de atributos do tipo boolean
     * devem ter o nome no formato isNomeDoAtributo.
    */
    boolean isMatriz(){
        return matriz;
    }
}

Aprendemos anteriormente que tais métodos devem ter nomes e estruturas padrões: um getter deve iniciar com a palavra get seguida do nome do atributo; um setter deve iniciar com a palavra set seguido do nome do atributo.

Como visto no código acima, para atributos do tipo boolean o getter deve ter um padrão diferente: deve iniciar com a palavra is seguida do nome do atributo. Assim, o getter para o atributo matriz é isMatriz e não getMatriz. Ao traduzir isMatriz podemos interpretar como uma pergunta: É matriz?

Como o tipo do atributo é boolean, a resposta será binária: verdade (true) ou falso (false). Desta forma, o nome do método é mais intuitivo que getMatriz.

Utilizando as classes criadas

Podemos definir que uma empresa tem um funcionário como gerente. Para isto, devemos então incluir um atributo da classe Funcionario dentro da Empresa. Abaixo é apresentado um trecho do código da classe Empresa, depois de adicionado o atributo gerente. Como podemos ver, tal atributo foi declado da mesma forma que qualquer outro, seguindo a regra tipo nomeDoAtributo;. A única diferença é que, no lugar de usarmos um tipo primitivo ou classe existente na linguagem Java, utilizamos uma classe que criamos.

public class Empresa
{
    private String endereco;
    private String cidade;
    private String telefone;
    private String email;
    private boolean matriz;

    /**
     * Indica qual funcionário é o gerente da empresa.
    */
    private Funcionario gerente;
}
Acabamos de aprender sobre associações: um tipo de relacionamento entre classes e mais um conceito fundamental da POO.

A partir do momento que incluímos o atributo Funcionario gerente dentro de Empresa, na tela inicial do BlueJ ele cria uma seta representando a associação entre a classe Empresa e Funcionario, como pode ser visto na figura abaixo. A direção da seta indica que a partir de uma empresa podemos saber qual funcionário a gerencia.

class association.png
Figure 2. Diagrama de Clases

Tal figura representa o que chamamos em POO de Diagrama de Classes. Este é um diagrama fundamental e uma forma diferente de visualizarmos nosso código. Assim como na programação estruturada podemos utilizar fluxogramas como uma alternativa para a representação textual de um algoritmo, podemos utilizar um diagrama de classes para visualizar um conjunto de classes e como elas estão relacionadas entre si.

Como adicionamos o atributo gerente, agora precisamos criar o getter e setter para ele, como mostra o código abaixo:

public class Empresa
{
    private String endereco;
    private String cidade;
    private String telefone;
    private String email;
    private boolean matriz;

    private Funcionario gerente;

    Funcionario getGerente(){
        return gerente;
    }

    void setGerente(Funcionario gerente){
        this.gerente = gerente;
    }
}

Definindo um gerente para uma empresa por meio do setter

Agora que as classes Empresa e Funcionario estão relacionadas, podemos criar uma Empresa como já fizemos antes (clicando sobre a classe na tela principal do BlueJ e escolhendo a opção new Empresa()). Após o objeto Empresa ter sido criado, podemos utilizar os setter’s para definir os valores dos atributos. Como o atributo gerente é do tipo Funcionario, precisaremos criar um funcionário antes de definir quem é o gerente da empresa. Como mostra a figura abaixo, estamos definindo um nome para o funcionário criado, antes de atribuir ele como gerente da empresa.

bluej new funcionario.gif
Figure 3. Criando um novo funcionário

Se clicarmos duas vezes na empresa que criamos anteriormente (no conto inferior esquerdo do BlueJ na imagem acima), veremos que o gerente da empresa está como null, o que indica que a empresa não possui um gerente ainda. Como a classe Empresa possui um método setGerente, podemos utilizá-lo para definir o funcionário que acabamos de criar como gerente. Tal funcionário é um objeto chamado funcionario1 na imagem acima. Ou seja, o objeto é uma variável chamada funcionario1.

Ao criar um funcionário no BlueJ, ele sugere que o nome do objeto (também chamado de instância e representado por uma variável) seja funcionaN, onde N é um número adicionado a cada objeto criado. Ou seja, ele abrevia a palavra funcionario. Assim, o nome sugerido pelo BlueJ para a variável do primeiro funcionário criado será funciona1. No entanto, observe que, na imagem acima, alteramos tal nome para funcionario1 ao criar tal Funcionario.

Agora, podemos então chamar o método setGerente na empresa criada para definir o funcionario1 como gerente, como mostrado abaixo. Quando clicarmos duas vezes novamente sobre a empresa criada, veremos que o atributo gerente não é mais null, sendo apresentada uma seta que representa a associação entre a empresa1 e o funcionario1. Se clicarmos duas vezes em tal seta, teremos acesso a tal funcionário e poderemos ver todos os seus atributos.

Não confunda o nome da variável que é utilizada para acessar um determinado funcionário com o atributo nome da classe Funcionario. O nome da variável (funcionario1 no exemplo) é utilizado para acessar o objeto Funcionario via programação. O atributo nome é apenas um dado que todo funcionário possui, assim como o cpf ou qualquer outro. Logo, ao informar qual Funcionario é o gerente de uma Empresa, devemos indicar o nome da variável (funcionario1 na Figura 4 abaixo), não o valor do atributo nome do Funcionario criado ("Manoel" na Figura 3 acima).
bluej set gerente.gif
Figure 4. Definindo o gerente de uma empresa por meio de um setter

Definindo um gerente para uma empresa por meio de um construtor

Vimos no Capítulo 5 o que são construtores e como adicioná-los a uma classe. Aprendemos que se nenhum construtor for manualmente incluído, um construtor padrão (que não recebe nenhum parâmetro) é automaticamente adicionado na classe compilada.

Podemos então criar um construtor para a classe Empresa para permitir definir, no momento que uma empresa for criada, quem é o gerente. Para isto, basta adicionar o código do construtor a seguir dentro da classe Empresa.

Construtor da classe Empresa: cria uma empresa já definindo quem é o gerente.
    /**
     * Aconselhavelmente os construtores devem ser
     * inseridos depois da lista de atributos.
     * Assim, eles ficam todos juntos.
     */
    Empresa(Funcionario gerente){
        setGerente(gerente);
    }

Lembre-se que o construtor é um método especial que cria objetos da classe. Ele deve obrigatoriamente ter o mesmo nome da classe e pode ter parâmetros (como é o caso do parâmetro gerente no código acima).

Como já fizemos antes no Capítulo 7, dentro do construtor, estamos chamando o método setGerente que já recebe um Funcionario e define ele como gerente da Empresa. No lugar de tal linha de código, poderíamos simplesmente ter feito this.gerente = gerente, mas isto duplicaria o código existente dentro de setGerente e poderia causar outros problemas, como já discutido no capítulo citado.

Como alteramos o código, precisaremos compilar a classe novamente, e recriar o funcionario1. Vamos então criar uma nova Empresa utilizando o construtor adicionado, como mostrado na figura abaixo. Observe que agora, ao criar uma Empresa, precisamos indicar quem é o gerente. Veja que pelo fato de termos adicionado um construtor com parâmetros, o construtor padrão (sem parâmetros, que cria uma Empresa sem definir inicialmente um gerente), não é mais disponibilizado.

bluej construtor empresa gerente.gif
Figure 5. Criando uma empresa e definindo o gerente por meio de um construtor

Em Java, quando um construtor com parâmetros é adicionado, se um construtor padrão (sem parâmetros) não for explicitamente definido no código, ele não é automaticamente disponibilizado mais. Isto é uma característica da linguagem. Se desejar ter o construtor padrão também, precisará manualmente incluí-lo, mesmo que ele não execute nenhum código definido por você, como mostrado abaixo.

    /**
     * Define um construtor padrão que não possui código algum.
     * Mesmo não tendo código algum, ele é capaz de criar objetos Empresa.
     * A inclusão de tal construtor vazio foi necessária pois
     * queremos ter a capacidade de criar uma empresa sem definir quem é o gerente.
     * No entanto, como um construtor que solicita quem é o gerente foi incluído
     * anteriormente, o construtor padrão deixa de existir.
     * Para termos ele disponível, precisamos neste caso incluí-lo manualmente
     * no código, como mostrado abaixo.
     */
    Empresa(){

    }

Apesar de tal construtor parecer não fazer absolutamente nada, o compilador Java inclui o código necessário para instanciar uma Empresa utilizando tal construtor.

Definindo novas associações

As classes Cliente e Empresa têm o atributo cidade em comum. Tal atributo foi declarado como String, o que, neste caso, nos traz alguns problemas.

Imagine que você cadastrou um cliente e informou sua cidade como "Paraíso do Tocantins". Outro funcionário pode ter cadastrado outro cliente e ter esquecido o acento, colocando "Paraiso do Tocantins". Outro dia você mesmo foi cadastrar mais um cliente e colocou a cidade apenas como "Paraíso". Por fim, um cliente utilizou a loja virtual para se cadastrar e informou a cidade sem acento e apenas como "Paraiso".

Assim, estamos falando da mesma cidade mas ela foi informada de 4 maneiras diferentes. Se o sistema possui um relatório que mostra o total de clientes por cidade, ele mostrará a cidade Paraíso do Tocantins como se fossem 4 cidades distintas. No lugar de mostrar que há 4 clientes de tal cidade, ele mostrará que há 4 cidades diferentes com 1 cliente cada uma.

Tendo a cidade como String, a cada cliente que for cadastrado, é preciso digitar o nome da cidade por completo, o que pode levar a erros de digitação e assim ter nomes diferentes para a mesma cidade, além de ser cansativo. Se utilizemos um campo com a lista de cidades cadastradas, isso evitará que o nome da cidade seja informado incorretamente.

Apesar da alternativa apresentada funcionar, ela traz outros problemas como o armazenamento do nome da cidade no cadastro de cada cliente. Se identificarmos que o nome de uma cidade foi incorretamente incluído na lista, teremos que corrigir tal nome em todos os clientes que foram vinculados a tal cidade. Além disso, se precisarmos incluir uma nova cidade nesta lista e ela estiver sendo exibida em várias janelas do sistema, poderemos precisar incluir esta nova cidade manualmente em cada tela.

Com esta alternativa, também não temos informações adicionais da cidade, como o estado a qual ela pertence. Mesmo que o estado fosse incluído após a cidade, como "Curitiba - PR", isto permitiria que siglas inexistentes de estados fossem informadas. Por fim, se quiséssemos saber quantos clientes há em cada estado, seria complicado obter tais informações, uma vez que a cidade e o estado não estão armazenados em atributos individuais.

Lembre-se que estamos utilizando programação orientada a objetos. Logo, você precisa pensar em termos de objetos. Como em POO um objeto pode ser qualquer coisa, uma cidade pode então ser um objeto. Assim, para representarmos cidades no nosso software, precisamos criar uma classe Cidade.

Tal classe pode ter os atributos nome e estado. Obviamente, uma cidade pode ter muito mais dados que isso. Poderíamos indicar quem é o prefeito, qual a população, a área e muitos outros dados. Mas assim como falado no Capítulo 2 quando introduzimos o conceito de classes, as características e funcionalidades de uma classe vão depender do problema em questão. Para o nosso software de loja de móveis, não nos interessa saber todos esses dados adicionais. Somente o nome e o estado são suficientes.

No entanto, assim como tratamos a cidade como um objeto, o estado também pode ser. Isto nos leva a criar uma classe Estado. Como temos que indicar a qual estado uma cidade pertence, devemos então criar primeiramente a classe Estado. Ela pode conter os atributos nome e uf, como apresentado no código abaixo.

public class Estado
{
    private String nome;
    private String uf;
}

Se continuarmos pensando no que mais um estado pode ter, rapidamente poderíamos concluir que ele percente a um determinado país. Isto nos levaria a criar uma classe para representar os países. Porém, se este dado não é importante para o nosso software, não devemos incluir.

Alguém pode questionar que a loja de móveis pode crescer e começar a vender produtos internacionalmente. Mas você não deve incluir todos os recursos no seu software por simplesmente estar pensando no longo prazo. Em engenharia de software, existe uma recomendação indicando que a inclusão de recursos para uso futuro devem ser ponderados [1] [2] [3].

Se você ainda não precisa de uma determinada característica ou funcionalidade no seu software, normalmente não deve incluí-la até elas serem realmente necessárias (a não ser que você tenha realmente um excelente motivo para isto). Incluir tais características podem só fazer você perder tempo com recursos que podem nunca ser utilizados pelos usuários do software.

Bem, agora que criamos nossa classe Estado, podemos criar a classe Cidade como mostrado abaixo:

public class Cidade
{
    private String nome;
    private Estado estado;
}

Por fim, podemos então alterar o tipo do atributo cidade, nas classes Cliente e Empresa, de String para Cidade, como demonstrado para a classe Cliente abaixo.

public class Cliente {
    private String nome;
    private String cpf;
    private String email;
    private String telefone;
    private char sexo;
    private String endereco;

    //Tipo do atributo alterado de String para Cidade
    private Cidade cidade;

    //Métodos omitidos intencionalmente.
}

Direção de uma Associação

Vamos fazer agora na classe Empresa o mesmo que fizemos na classe Cliente: alterar o tipo do atributo cidade de String para Cidade. Em seguida, vamos criar uma nova classe para representar produtos e outra para representar marcas. A classe Produto pode ter os atributos:

  • descricao como String

  • precoCompra, precoVenda e estoque como double

Poderíamos também definir um atributo marca também como String. Mas devido aos problemas discutidos para o atributo cidade na classe Cliente, vamos criar uma classe Marca e definir uma associação de Produto para Marca. Como estamos indicando a direção da associação (depara), isto significa que devemos criar um atributo da classe Marca dentro de Produto, não o contrário. Após criar tal associação, o BlueJ mostrará uma seta indicando a direção da mesma.

A direção de uma associação, representada pela direção da seta em um diagrama de classes, é chamada em POO de navegação. Ela define qual classe tem acesso à outra classe. Se a navegação da associação entre duas classes A e B for AB, um objeto da classe A tem acesso a um ou mais objetos da classe B. Isto significa que dentro de um objeto da classe A pode existir um ou mais objetos da classe B. Se a associação for BA, a classe B é que terá um ou mais objetos de A.

Em resumo, a classe de origem (de onde parte a seta) é que terá um atributo da classe de destino, indicando que a partir da origem conseguimos chegar no destino. Por exemplo, em um relacionamento EmpresaCidade, a partir da Empresa, conseguimos saber qual a Cidade onde ela está situada.

Referências

results matching ""

    No results matching ""