Pensando em Objetos


Como Pensar em Objetos?

Aqui vai uma resposta simples e direta:

Pensar em Objetos significa não implementar uma tarefa na forma de instruções passo-a-passo para o computador.

Pensando

Pensar em Objetos é uma mudança de paradigma enorme para quem começou a programar em linguagens procedurais como C, ASM ou Pascal e não conseguiu — ou nunca quis — mudar o mindset para entender como programar Orientado a Objetos.

A tempos aprendemos sobre Herança, Encapsulamento e Polimorfismo, mas eu acho que todos nós — inclusive eu, no passado — não entendemos a transição do Procedural para o paradigma Orientado a Objetos.

Programando Orientado a Objetos

Nada melhor do que um exemplo prático para começarmos a entender as diferenças entre programação Procedural e Orientada a Objetos.

Um cliente nos contratou. Um WebService está disponível e precisamos construir um Client para consumi-lo.

Eu não quero utilizar um gerador automatizado de código para ler o WSDL e gerar uma classe — existe essa opção no Delphi e no Lazarus instalando um package específico.

Então, hipoteticamente falando, temos um WebService simples com 5 métodos. Cada método tem os mesmos argumentos: usuário e senha.

As configurações do WebService como Usuário, Senha, URL, métodos do WebService que deverão ser executados, horário de execução, etc. estão num arquivo de configurações. Por que isso? Bem, esse WebService será executado em batch automaticamente em horários pré-programados. E em cada horário alguns métodos serão executados, em outros não.

Como é automatizado, precisamos de um Log. Em texto mesmo. Um Log que grave a execução, método, horário, usuário/senha, exeções, etc.

O cliente também quer testar o WebService utilizando um Form (GUI) para ver se está tudo funcionando, mostrando na tela o XML recebido de acordo com o método a ser executado, escolhido numa ComboBox. Quando o Form é utilizado o sistema não precisa criar Log, mas precisa mostrar exceções na tela. Mas quando em execução em batch, ele precisa gravar o Log de execução e de exceções.

Vejam que esse Client é para o pessoal de Infra então é sem frescura mesmo: Log em texto, roda em batch e não tem tela bonita.

Para cada requisição, o Client precisa:

  1. Trazer o XML;
  2. Fazer um parser nos dados;
  3. Fazer uma persistência com os dados obtidos;

Exemplo 1:

Aqui está o código Procedural para consumir o WebService:

Atenção: Eu digitei a procedure diretamente no post, sem teste de compilação, então podem haver erros de sintaxe.

procedure TDpvAction.Act(const WebMethodName: string);
var
  Client: TDpvService;
  Mapping: TDpvXMLMappingInTable;
  XML: string;
  XMLIterable: TXMLIterable; 
begin
  // FFileConfig é um atributo da classe
  
  Client := TDpvService.Create;
  Mapping := TDpvXMLMappingInTable.Create;
  XMLIterable := TXMLIterable.Create; 
  try
    // Configura a URL do WebService
    Client.SetURL(FFileConfig.ReadString('WebService', 'URL'));
   
    // Configura com verificação de exceção
    Client.WithCheck(True);

    // Configura para gerar Log
    Client.WithLog(True);

    // Client executa o método
    XML := Client.ExecuteMethod(
      FFileConfig.ReadString('WebService', 'User'),
      FFileConfig.ReadString('WebService', 'Password'),
      WebMethodName
    );

    // Obtém as configurações necessárias
    XMLIterable.SetXMLNodes(FFileConfig.ReadString('XMLNodes', WebMethodName));

    // Configura o XML gerado
    XMLIterable.SetXML(XML);

    // Executa e gera outro XML (sim, reutilizei a mesma variável)  :)
    XML := XMLIterable.Execute;

    // Executa a persistência
    Mapping.SetXML(XML);
    Mapping.Execute;
  finally
    // Não pode esquecer de desalocar tudo
    Client.Free;
    Mapping.Free;
    XMLIterable.Free;
  end;
end;

É um código Procedural e funciona (teoricamente).

Parece fácil de ler, mas o código tem complexidade onde não deveria. As classes tem funções demais — mesmo separando em classes, continua procedural.

Reparou nas linhas em branco para “facilitar a leitura”?

Todo mundo faz isso, não é? Mesmo assim, tente eliminar esse hábito. Irei escrever sobre isso no futuro.


Dica: Pular linhas dentro de um método é um “mal cheiro” no código. Pode significar que você está fazendo coisas demais num único método.


Nesse tipo de código, a cada manutenção, você se preocupa em quebrar alguma coisa.

Para não quebrar nada você precisa:

  • Chamar os métodos na sequência correta
  • Chamar os SetXXX na sequência correta
  • Ter cuidado com as variáveis, suas inicializações e reutilização
  • Instanciar os objetos na sequência correta
  • Etc.

Não tome cuidado e… BUMM! Quebrou o código. Debugg. Checagem das atribuições na sequência exata. Perda de tempo.

Não? Você sabe que é verdade :)

O código apresentado é 100% Procedural, mesmo utilizando classes! Você poderia usar mais classes e até mesmo Herança entre elas, mas o código continuaria Procedural. Você não tem objetos, você tem funções agrupadas em blocos (classes) para fazer ações para o orquestrador, você!

E como estes mesmos requisitos poderiam ser codificados utilizando Orientação a Objetos, sem um orquestrador, utilizando “objetos vivos” com encapsulamento perfeito, minimizando a quebra de código em manutenções futuras e com um código muito mais elegante?

Exemplo 2:

Aqui está o código Orientado a Objetos para consumir o mesmo WebService:

function TDpvAction.Act(const WebMethodName: string): IActionResult;
begin
  Result := TDpvXMLMappingInTable.New(
    FFileConfig.ReadString('MethodsVsTables', WebMethodName),
    TDpvXMLIterable.New(
      FFileConfig.ReadString('XMLNodes', WebMethodName),
      TDpvServiceWithLogging.New(
        TDpvServiceWithChecking.New(
          TDpvService.New(
            FFileConfig.ReadString('WebService', 'URL')
          )
        )
      )
      .Call(
        TDpvMethod.New(
          WebMethodName,
          TWebAuthorization.New(
            FFileConfig.ReadString('WebService', 'User'),
            FFileConfig.ReadString('WebService', 'Password')
          )
        )
      ).XML
    )
  ).Act;
end;

Esse é um código verdadeiramente Orientado a Objetos. Não dizemos ao computador linha-a-linha sobre o que fazer. Os Objetos sabem o que fazer! Eles não precisam de um orquestrador lhes dizendo o que fazer a cada momento. Crie-os já configurados e deixe-os conversarem entre si. Eles sabem os métodos a chamar e quando chamá-los. Eles podem criar objetos internamente ou não e isso é irrelevante para o código chamador. Eles são como uma caixa preta, ninguém sabe o que há dentro deles. Você, o programador, só precisa saber o contrato que o objeto deverá cumprir. Esse contrato chamamos de interface.

Não dá pra ver no código por motivos de simplificação, mas cada instância é do tipo de uma interface, por isso temos os métodos New — um padrão que eu defini para meus projetos para contornar uma feature do compilador mas só irei falar a respeito em outros posts.

Ao invés de uma procedure agora temos uma function que retorna um IActionResult e o fato de ter apenas um Result é um ótimo sinal de que a função não faz coisas demais. Essa função é como um grande objeto ou uma composição deles — vou falar mais sobre isso em futuros posts.

Se todas as instâncias são do tipo interface, então não preciso desalocar nada manualmente deixando o código mais clean sem try-finally e sem memleaks (caso esqueça algum .Free).

Todos os objetos são imutáveis, ou seja, após criados não há nenhum SetXXX que altere seu estado interno. Então não configuro meu Client, e nenhum outro objeto, utilizando métodos SetXXX para ele trabalhar com Log ou com checagem de exceções.


Dica: Toda classe deveria ser imutável, a não ser que tenha uma ótima razão para não ser.


Eu uso Decorators ao invés de properties ou SetXXX para ligar/desligar flags (atributos). Assim não preciso completar a(s) classe(s) numa sequência correta de chamadas dos métodos.

Se eu não preciso do Log, não utilizo o decorator do Service de Log que chamei de TDpvServiceWithLogging; o mesmo para a checagem de Exceções, que chamei de TDpvServiceWithChecking.

O método de chamada no WebService é uma classe separada, que chamei de TDpvMethod. Cada autorização (usuário/senha) também é uma classe, que chamei de TWebAuthorization… enfim.

Viu as diferenças?

Espero que você tenha visto as vantagens de programar Orientado a Objetos, mesmo com esse exemplo simples.

Tá gostando? Alguma dúvida? Não concorda? Posta aí nos comentários.

Vejo você no próximo post.


Dica: Sugiro a leitura do livro Object Thinking de David West. Leia-o com a mente aberta e começará a entender mais sobre o Pensamento Objeto. Object Thinking

Posts Relacionados

  • Memória Segura Utilizando Instâncias de Interfaces

  • Classes Mutáveis vs Objetos Imutáveis

  • Implementando Interfaces Utilizando Diferente Assinaturas de Métodos

  • Usando Paths ao invés de Diretivas de Compilação

  • Trabalhando com Exceções em Requisições HTTP

  • Tipo object Continua Vivo

  • Array de Objetos

  • Variáveis Locais Deveriam ter Nomes Curtos

  • Como Dividir e Organizar o Código em Formulários com Muitos Widgets

  • Pascal Deveria ser Modernizado?