Quando aprendemos sobre Orientação a Objetos, anos atrás, ouvimos dizer que Objetos enviam mensagens uns aos outros. No entanto o que realmente vemos na maioria dos códigos de hoje não são verdadeiros Objetos, são apenas estrutura de dados com funções…
Esquecemos o que significa enviar uma mensagem a um Objeto?
Introdução
Fazendo a análise de um novo sistema, o arquiteto vê a necessidade de implementar uma Classe para representar um Cliente.
O analista de negócio diz que um Cliente precisa ter Login, Senha e seu Nome.
Você começa a implementação da Classe.
Após 2 horas o analista retorna e diz que também precisa ter mais “alguns campos”. Então ele também acrescenta Endereço de correspondência; a Data de Nascimento para dar descontos; separa o nome em 2 campos (Nome e Sobrenome); e o Cartão de Crédito que será a forma de pagamento ao comprar os produtos da empresa.
Como implementamos a nova Classe TCustomer
que irá representar
um Cliente?
Aqui está uma sugestão de implementação bastante utilizada pelos desenvolvedores:
type
TCustomer = class
private
FLogin: string;
FPassword: string;
FFirstName: string;
FLastName: string;
FAddress: string;
FBirthday: TDateTime;
FCreditCard: string;
public
constructor Create;
property Login: string read FLogin write FLogin;
property Password: string read FPassword write FPassword;
property FirstName: string read FFirstName write FFirstName;
property LastName: string read FLastName write FLastName;
property Address: string read FAddress write FAddress;
property Birthday: TDateTime read FBirthday write FBirthday;
property CreditCard: string read FCreditCard write FCreditCard;
end;
Utilizei properties. No Object Pascal, assim como no C# fica até bonito e escrevemos menos. No Java seria o dobro de linhas, pois teríamos 7 Getters e 7 Setters para cada atributo.
Bem, tudo certo nessa implementação?
Não.
Infelizmente essa Classe não tem nada a ver com o real uso da Orientação a Objetos. Essa Classe não está representando um Cliente, mas sim uma tabela do Banco de Dados.
Além disso, o uso de properties não elimina o fato que, na verdade, essa Classe só possui Getters e Setters, que é um anti-padrão.
Mensagens
Uma Classe deve representar uma Entidade, nunca um registro de tabela no Banco de Dados.
Se você tem uma Classe com Getters e Setters, seu pensamento não está na Orientação a Objetos. Você está pensando em Dados, em tabelas, em persistência.
Você não deveria pegar e muito menos alterar um dado de um Objeto diretamente!
Pense no mundo real. Quando você pergunta o nome de alguém, ele/ela pode lhe responder de diversas maneiras:
- Diz apenas o primeiro nome;
- Diz apenas o sobrenome;
- Diz o nome completo;
- Diz o apelido;
- Inventa um nome;
- Não diz nada;
Veja que uma pergunta simples pode ter inúmeras respostas. Tudo irá depender do contexto.
Todo Objeto deve ser respeitado. Então, o máximo que fazemos é enviar uma mensagem ao Objeto e aguardar uma resposta.
Vamos a um exemplo.
Compare os dois diálogos abaixo:
Diálogo 1:
Carlos quer alugar um carro. Ele entra numa loja e vai falar com o vendedor.
— Bom dia. Gostaria de alugar um carro. — disse Carlos.
— Bom dia! Qual modelo o senhor está procurando?
— Procuro um de menor custo, bem simples.
— Ok. Temos esses 3 modelos...
— Legal. Quanto custa o hatch?
— Bem, o senhor está com sorte!
— Estou? — disse Carlos
— Sim, hoje é dia de promoção para esse modelo.
O vendedor vai buscar um catálogo e diz o preço.
— E vou lhe dar 20% de desconto!
Carlos fica feliz com a escolha do modelo e
preço e resolve alugar.
— Ok! Vou levar esse!
— Certo. Preciso que o senhor preencha esse formulário.
Diálogo 2:
Um Registro quer um carro.
O Controlador pega uma lista de carros;
O Controlador ordena a lista de forma crescente;
O Controlador pega o Carro com o menor valor;
O Controlador verifica a data atual e concede um desconto;
O Controlador altera o Registro, apontando o carro;
O Controlador pega os dados do Registro e salva
em outra tabela;
No primeiro diálogo temos duas pessoas (Objetos no software) conversando.
No segundo… bem, na verdade não houve um segundo diálogo. Havia apenas um Controlador (você) dando ordens e alterando dados. Pegando o que quer, onde quer e alterando dados sempre que queria.
Para codificar o primeiro diálogo, precisaremos utilizar mensagens entre os Objetos.
Mensagens também são Métodos. Tecnicamente essa é a maneira que temos para adicionar comportamento a uma Classe.
Mas diferentemente de um Método Get
, onde o dado é tomado a força
do Objeto, uma mensagem dá o controle ao Objeto para que ele retorne
as informações que ele desejar.
Repare a diferença entre as 2 últimas linhas dos diálogos:
- “Preciso que o senhor preencha esse formulário”
- “O Controlador pega os dados do Registro e salva em outra tabela”
Na mensagem #1 o vendedor solicita o preenchimento de um formulário com os dados pessoais de Carlos.
No procedimento #2 o Controlador apenas pega os dados do Registro.
O correto é utilizarmos mensagens e dar o controle da resposta ao Objeto.
O Método About
Todos os Objetos deveriam responder a uma mensagem padrão quando outros Objetos quiserem saber sobre seus dados encapsulados.
Eu nomeei esse Método de About
, ou seja, perguntamos ao Objeto
se ele tem algo a dizer sobre si mesmo.
O Método About
é utilizado em todos os Objetos que precisam
informar ao mundo as informações que eles encapsulam.
Essa é uma alternativa viável para darmos o controle de volta ao Objeto ao invés de tomar o dado dele.
O Objeto tem o total controle sobre quais dados retornar. Dessa forma não há uma quebra de Encapsulamento
Objetos devem ser capaz de apresentar a si mesmo. De dar informações “pessoais” que só eles tem conhecimento.
Essa ideia não é nova. No livro Object Thinking o autor aborda esse assunto.
Existem outros artigos na Internet que falam sobre esse tema. Dentre esses artigos tem esse em particular que capta bem essa ideia. No entanto a abordagem do autor para implementar esse conceito é diferente da minha abordagem.
Então, o que retorna o Método About
?
Retorna um Stream (IDataStream
). E, basicamente é isso.
Para implementar o About
precisamos definir a Interface:
type
IDataStream = interface
function Save(Stream: TStream): IDataStream;
function Save(Strings: TStrings): IDataStream;
function Save(const FileName: string): IDataStream;
function AsString: string;
function Size: Int64;
end;
Veja aqui
a implementação da Classe que implementa IDataStream
.
Aqui há uma grande diferença. Antes o Objeto era passivo. Ele estava disponível para dar e receber dados através de seus Métodos, o que é extremamente errado se estivermos utilizando Orientação a Objetos.
Agora, com a implementação do Método About
, os dados do Objeto continuam encapsulados.
O Objeto não é mais passivo. Ele não é mais um “balde de dados”. Ele está vivo
e tem seu próprio comportamento.
Se Objetos externos querem obter os dados de outrem, deverão enviar uma mensagem solicitando-os. Cabe ao receptor responder com os dados que ele deseja compartilhar.
Refatorando
Nos diálogos acima, Carlos e o “Registro” obtém seus dados da mesma
tabela, chamada CUSTOMER. Seus campos são os mesmos definidos
na primeira versão da Classe TCustomer
.
O que vamos fazer é refatorar TCustomer
, implementando seu Método
About
ao invés de ter todos os seus dados expostos em Getters e Setters.
type
ICustomer = interface
function About: IDataStream;
end;
TCustomer = class(TInterfacedObject, ICustomer)
private
FId: IDataGuid;
public
constructor Create(Id: IDataGuid);
class function New(Id: IDataGuid): ICustomer;
function About: IDataStream;
end;
implementation
constructor TCustomer.Create(Id: IDataGuid);
begin
inherited Create;
FId := Id;
end;
class function TCustomer.New(Id: IDataGuid): ICustomer;
begin
Result := Create(Id);
end;
function TCustomer.About: IDataStream;
begin
// 1. get record from Database using Id
// 2. choose data
// 3. convert to XML
Result := TDataStream.New({XML});
end;
Quando o vendedor diz “Preciso que o senhor preencha esse formulário”
na verdade é o Formulário — TRentACar
— enviando uma mensagem ao Carlos:
TRentACar.New(
TCustomer.New(
TDataGuid.New('{guid}') // Carlos' guid
).About
).Rent
O Formulário necessita de alguns dados como Nome Completo,
Endereço e Cartão de Crédito. Esses dados são passados
no construtor de TRentACar
, provenientes da chamada
ao Método About
de TCustomer
.
Mas o retorno de About
será parecido com isso:
<customer>
<Id>{guid}</Id>
<FirstName>Carlos</FirstName>
<LastName>Almeida</LastName>
<Address>Rua 1...</Address>
</customer>
Veja que Login e Password não foram retornados, assim como a informação do CreditCard.
E aí temos um problema. Tudo bem que o Formulário não necessita do Login e Password — essas informações serão salvas caso o Cliente opte por fazer um registro online — mas o Cartão de Crédito é necessário para concluir a operação.
O que fazer?
Temos 2 opções:
- Retornar todos os dados no XML;
- Criar Classes especialistas de
Customer
;
Nesse exemplo é mais fácil retornar todos os dados, é claro. Tudo
está numa única tabela, num único registro. Mas imagine um sistema
mais complexo onde um Customer
poderia ter várias tabelas (outros
endereços, registros financeiros, histórico de aluguel, tickets de
reclamações…). É possível retornar tudo, num único XML, porém não
é performático. Exigiria a consulta em muitas tabelas.
Para esses casos, criamos Classes especialistas. Exemplo:
TCustomerFinances = class(TInterfacedObject, ICustomer)
Essa Classe é especializada em Finanças. Ela pode retornar o Cartão
de Crédito e também todos os registros financeiros. O mais interessante,
no entanto, é que todas as Classes especialistas deverão implementar
ICustomer
, pois todas representam a mesma Entidade, porém cada uma
na sua especialidade e em contextos diferentes.
Então, caso você opte por separar as responsabilidades, o registro do aluguel do carro poderia ser feita dessa maneira:
TRentACar.New(
TCustomerFinances.New(
TCustomer.New(
TDataGuid.New('{guid}') // Carlos' guid
)
).About
).Rent
A Classe TCustomerFinances
deverá receber uma instância de
TCustomer
no construtor.
Na chamada ao About
ela irá chamar o About
padrão, incluir os
dados financeiros e retornar a resposta.
Isso é decoração de Objetos.
Para a Classe TRentACar
é transparente. Ela está conversando
com um único Objeto que retorna um IDataStream
quando enviar uma
mensagem About
.
Conclusão
Não podemos mais confundir Dados com Objetos.
Vamos parar de criar Objetos anêmicos que mapeiam tabelas no Banco de Dados.
Devemos dar o controle aos Objetos. Eles sabem o que devem ou não responder quando estão se comunicando.
Você é apenas o diretor do teatro. Você pode até fazer o roteiro e escolher quem irá atuar em cada cena. Mas você não atua.
Quando os atores estiverem atuando, não tente controlá-los, deixe-os trabalhar.
Até logo.