Atenção: esse artigo tem uma palestra complementar. Clique e assista!
O artigo aborda alguns dos tipos de encapsulamento e apresenta o Padrão Command como uma solução para criar um sistema robusto e flexível. Para exemplificar o uso deste padrão é criado um exemplo de como implementar o recurso Desfazer em um programa.
Para que serve
Técnicas de encapsulamento ajudam você a criar um sistema fechado contra erros e ao mesmo tempo aberto para expansão. Haverá uma integração maior entre as partes do programa, pois o encapsulamento juntamente com o Padrão Command permite que estas partes se comuniquem sem saber o que realmente cada uma faz.
Em que situação o tema é útil
Usando esta técnica em conjunto com o Padrão Command você ganha um “cinto de utilidades” cheio de novos recursos interessantes para implantar no sistema. Um deles é o recurso de desfazer operação apresentado neste artigo.
Resumo do DevMan
Saber como aproveitar ao máximo o que o encapsulamento tem para oferecer possibilita a você criar grandes sistemas garantindo a facilidade de manutenção durante a vida do mesmo. Com um pequeno, exemplo disponível para download, você vai aprimorar um pouco mais seus conhecimentos sobre esta técnica e ainda vai aprender como implementar o Padrão Command. Este padrão traz consigo um conjunto de recursos muito interessantes, como desfazer e refazer operações apresentado neste artigo, além de permitir que você faça um controle detalhado de tudo que seu programa executar.
Neste artigo você vai aprender na prática como funciona o Padrão Command. Este padrão possibilita que partes do programa sejam encapsuladas em pequenas classes de comandos que serão executadas através de um invocador de comandos. Durante o artigo será criado um projeto de exemplo, para simular um sistema onde serão criadas algumas classes de comandos para fazer um cadastro qualquer. Após mostrar como são as classes de comando, é explicado como usar o Padrão Command para adicionar o recurso de desfazer operações, que você poderá facilmente modificar para que também seja possível refazer algo que havia sido desfeito. No projeto de exemplo trabalharei apenas com o código da aplicação para tornar o artigo menos extenso, ou seja, tudo será feito sem usar um único formulário sequer. Mas no arquivo disponível para download você vai encontrar o mesmo exemplo, porém com formulários como qualquer aplicação.
Encapsulamento
Encapsulamento é um dos fundamentos básicos da programação orientada a objetos. É o ato de proteger algo, normalmente atributos de uma classe, para que outras classes não acessem diretamente este atributo. Encapsular, segundo dicionário Michaelis significa: Incluir, proteger em uma cápsula ou como em uma cápsula. Na Listagem 1 existe um exemplo de uma classe cujo os atributos NÃO ESTÃO encapsulados.
Listagem 1. Classe TCliente sem encapsulamento
1 unit Cliente;
2 Interface
3 Type
4 TCliente = class
5 Private
6 FCpf: string;
7 FNome: string;
8 Published
9 property Nome: string read FNome write FNome;
10 property Cpf: string read FCpf write FCpf;
11 end;
12 Implementation
13 end.
Imagine uma classe que represente a entidade cliente (Listagem 1): ela poderia receber o nome TCliente e conforme o exemplo possui dois atributos (propriedades): Nome e CPF. Olhando com mais atenção nas linhas 9 e 10 você verá que quando os valores destas propriedades são lidos (read) e escritos (write), é feito um acesso direto às variáveis onde os valores serão armazenados. Isto significa que estas propriedades não estão encapsuladas, pois, apesar das variáveis (linhas 6 e 7) estarem na sessão private, o usuário pode alterar estes valores através das propriedades declaradas nas linhas 9 e 10. Do jeito como está, a classe TCliente poderá aceitar números de CPF inválido, pois nenhuma validação será feita.
Para garantir que um usuário nunca irá conseguir salvar um número inválido de CPF é preciso encapsular (empacotar, proteger), o acesso à variável declarada na linha 6. Para conseguir isto vou criar dois novos métodos: SetCpf e GetCpf. Veja Listagem 2.
Listagem 2. Encapsulando a variável FCpf
1 unit Cliente;
2 Interface
3 Type
4 TCliente = class
5 Private
6 FCpf: string;
7 FNome: string;
8 function GetCpf: string;
9 procedure SetCpf(const Value: string);
10 Published
11 property Nome: string read FNome write FNome;
12 property Cpf: string read GetCpf write SetCpf;
13 end;
14 Implementation
15 uses SysUtils;
16 { TCliente }
17 function TCliente.GetCpf: string;
18 Begin
19 Result := FCpf;
20 end;
21 procedure TCliente.SetCpf(const Value: string);
22 Begin
23 if Length(Value) <> 11 then
24 raise Exception.Create('O CPF precisar ter 11 caracteres');
25 FCpf := Value;
26 end;
27 end.
Nada de surpreendente. Apenas criei dois novos métodos: GetCpf e SetCpf que podem ser vistos nas linhas 8 e 9 da Listagem 2. Observe na linha 12 desta mesma listagem que os valores de acesso (write) e de leitura (read) da propriedade CPF foram alterados. Desta maneira, sempre que o CPF do cliente for alterado ele vai passar obrigatoriamente pelo método SetCpf. O mesmo acontece para pegar o CPF atual, pois para isso será obrigatório passar pelo método GetCpf. O que nos interessa neste caso é o método SetCpf, pois nele estão definidas as condições para que um CPF seja aceito. Veja entre as linhas 21 e 25 que a única regra é o CPF ter exatamente 11 caracteres.
Quando criamos estes dois métodos estamos reforçando o encapsulamento. Um dos objetivos do encapsulamento é proteger algo. Na classe TCliente, quando criei o método de acesso SetCpf para a variável FCpf, também criei uma oportunidade de controlar o que pode ou não ser colocado na variável, já que para qualquer outra classe acessar esta variável será obrigado executar o método SetCpf. Entendido este raciocínio, que tal deixar o código um pouco mais simples de ler. Veja o que pode ser feito (Listagem 3).
Listagem 3. Adotando um padrão de codificação
1 unit Cliente;
2 Interface
3 Type
4 TCliente = class
5 Private
6 FCpf: string;
7 FNome: string;
8 Public
9 function GetNome: string;
10 procedure SetNome(const Value: string);
11 function GetCpf: string;
12 procedure SetCpf(const Value: string);
13 end;
14 Implementation
15 uses SysUtils;
16 { TCliente }
17 function TCliente.GetCpf: string;
18 Begin
19 Result := FCpf;
20 end;
21 function TCliente.GetNome: string;
22 Begin
23 Result := FNome;
24 end;
25 procedure TCliente.SetCpf(const Value: string);
26 Begin
27 if Length(Value) <> 11 then
28 raise Exception.Create('O CPF precisar ter 11 caracteres');
29 FCpf := Value;
30 end;
31 procedure TCliente.SetNome(const Value: string);
32 Begin
33 FNome := Value;
34 end;
35 end.
O funcionamento da classe TCliente não mudou em nada entre as Listagens 2 e 3. Porém a Listagem 3 está seguindo um padrão de codificação mais simples: as propriedades declaradas na sessão published foram removidas, pois esta sessão não será útil para nós. O que restou foram os métodos de “empacotamento” get e set. Veja entre as linhas 9 e 12 que estes métodos foram movidos para a sessão public da classe. As variáveis FCpf e FNome continuam escondidas na sessão private e não podem ser tiradas de lá para não ferir o encapsulamento. Colocar estas variáveis em outra sessão, como por exemplo a sessão public, faria com que qualquer outra classe pudesse acessar estas variáveis diretamente e assim não faria sentido a existência dos métodos de encapsulamento get e set.
Membros da sessão published têm a mesma visibilidade que os membros da sessão public com a diferença de que, até a versão 2009 do Delphi, as informações de tempo de execução (RTTI) são geradas apenas para membros da sessão published. Além disto, published possui alguma restrição sobre os tipos dos membros declarados. Alguns tipos simplesmente não são permitidos.