Definir classes não é programar com orientação a objetos: Ciclo de vida.

Há pouco mais de 2 anos, falei sobre esse tema na PHP Conference Brasil e acho válido revisitá-lo uma vez que muitos desenvolvedores sentem orgulho em dizer que programam orientado a objetos, mas ao fazer uma simples pergunta acabam não sabendo respondê-la:

O que é Orientação a Objetos ?

Alan Kay, criador da linguagem Smalltalk e um dos primeiros defensores da orientação a objetos, a explicou da seguinte forma:

"The big idea is "messaging" (...)

Pronto, fim do post. Obrigado por ler.

Messaging ?

De nada adianta falar sobre Test-First, Princípios de Orientação a Objetos, Mocking, AntiPatterns uma vez que não se conhece o núcleo da Orientação a Objetos.

O grande cerne da questão é justamente a troca de mensagem entre objetos através de seus métodos. É assim que Alan Kay defendeu e é assim que o Smalltalk funciona. Recentemente, alguns desenvolvedores, especialmente em Ruby, têm voltado a reforçar o propósito em OO.

A melhor forma de manter seu código desacoplado e "falante" é forçar os objetos a trocarem mensagens. Lembra do Tell Don't Ask? Pois é, aqui isso é parte chave para atingir-se tal objetivo.

Vou repetir:

Pronto, acho que deu para entender o quão importante é manter no objeto a responsabilidade de manipular os próprios dados (armazenados em vossos atributos).

O Ciclo de Vida de um Objeto

Já parou para pensar em qual seria o ciclo de vida de um objeto?

Bom, inicialmente ele Nasce, depois Cresce, Continua disponível e finalmente Morre.

O nascimento de um objeto

O nascimento do objeto é através de seu construtor. Simples, porém muitas vezes ele acaba ignorado, ainda mais quando vicia-se em utilizar frameworks arquiteturais sem entender o motivos das coisas.

Correto:

Employee.new(name: "Durval", lastname: "da Silva", cpf: "123.123.123-X")

new Employee("Durval", "da Silva", "123.123.123-X")

Incorreto:

durval = Employee.new
durval.name = "durval"
durval.lastname = "da Silva"
durval.cpf = "123.123.123-X"

durval = new Employee();
durval.setName("durval");
durval.setLastname("da Silva");
durval.setCpf("123.123.123-X");

O motivo é simples: o construtor é o contrato que nos diz: "para você obter um novo objeto tipo Employee, você precisa obrigatoriamente me informar: Nome, Sobrenome e CPF, pois caso contrário, você criará um Employee inválido."

E é justamente o que o exemplo incorreto faz: cria um Employee sem dado algum. Quem garante que eu irei chamar setName, setLastname e setCpf depois? Eu posso esquecer. Pode nem ser eu o responsável por utilizá-lo depois. (APIs-like).

Lembre-se o construtor é a certidão de nascimento do objeto. Não tire dele esse direito. Brasil, país rico é país...

Cresce

Aqui temos outros métodos que mudarão o status de nosso objeto ao longo de sua vida. Nem sempre é com setter - outro erro comum daqueles que programam por coincidência. Veja exemplos:

Correto:

employee.exame_admissional = arquivo_pdf_do_exame  # snake case r0cks

employee.setExameAdmissional(arquivoPdfDoExame)

employee.definir_credencial_de_rede(username: "new_username", password: "983hJfh78")

Incorreto:

objeto_credencial = Credencial.new(username: "new_username", password: "9789237498")
employee.credencial = objeto_credencial

objetoCredencial = new Credencial("new_username", "3798472398478234")
employee.setCredencial(objetoCredencial)

Neste caso, o incorreto viola o encapsulamento da Credencial, deixando a cargo do cliente conhecer como a Credencial é criada. Para entender melhor, desenho:

Quebra de Encapsulamento

Cliente é toda classe que utiliza outra. Só para deixarmos as coisas claras por aqui.

O problema aí é que no caso o Controller precisa saber como construir Employee e Credencial, além de precisar conhecer como relacioná-las. Um exemplo mais OOP seria:

Aggregate Root

Agora, o Controller precisa apenas lidar com Employee e este lidará com a Credencial. Em pseudocódigo, ficaria:

# Ruby
class Employee
  # construtor e outros métodos ocultados
    
  def define_credencial(username: username, password: password)
    @credencial = Credencial.new(username: username, password: password, employee: self)
  end
end

# Java, et al.
class Employee {
    private Credencial credencial;
    
    public void defineCredencial(String username, String password) {
        credencial = new Credencial(username, password, this);
    }
}

Neste caso, Credencial é um Value Object pertencente ao Employee. Esse tipo de relacionamento é parte do Domain-Driven Design e é chamado de Aggregate Root.

Aggregate root são úteis quando você tem um relacionamento aonde a Parte (Credencial) é totalmente dependente do Todo (Employee). Em outras palavras: a Parte só tem vida quando o Todo tem vida. Não é legal que uma Parte tenha vários Todo(s).

Continuando disponível

Em orientação a objetos, nossa linha de pensamento deveria ser um pouco mais ampla. Ou seja, devemos pensar que uma vez que o objeto é criado (instanciado) ele ficará disponível até que alguém o mate (remova da memória). Enquanto ninguém o remover da memória, ele continuará vivo.

É importante esquecer o meio/tipo de armazenamento aqui. Robert Martin (@unclebob) falou sobre isso na sua must watch palestra na Ruby Midwest 2011 - sério: assista até o fim. Você será outro depois disso ;)

Morre

O objeto é removido através do Garbage Collector, Removido com ORM, etc. Aqui não há novidade mesmo.

1 imagem, 1000 palavras

Object Life Cycle

O meio de vida do objeto (aquele dentro do retângulo) é aonde a maior parte dos objetos estarão. Ao buscar um objeto com um Repository por exemplo, devemos pensar que o objeto estava em memória, já construído e do jeito que o deixamos da última vez que trabalhamos com ele. Ou seja: o Repository não dará new novamente, pois o objeto já existe. Pelo que me lembro, o Doctrine 2 seguia isto: ao recuperar um objeto do Repository o construtor não era chamado novamente - o que é excelente, pois o Ciclo de Vida do Objeto não é quebrado!

Concluindo?

O tema é extenso assim como Test-First. Conhecer bem o que a Orientação a Objetos reserva e espera de você é pré-requisito para iniciar com Teste de Software em aplicação Orientada a Objeto. Na sequência, continuarei a falar sobre Object Oriented Design para permitir que se veja além ao testar software.

Hélio Costa

Desenvolvedor de software há um tempinho. Interessou-se por Object-Oriented Design e Test-first antes do Eddy Merckx ser removido na organização da UCI Road World Championships.

Sao Paulo, Brazil