Atualmente tenho aprendido bastante sobre o conceito de programação orientada a objetos, por esse motivo decidi que seria bom compilar meus conhecimentos de forma a ajudar quem está começando a ver esses conceitos.

Introdução

Quando começamos a programar, geralmente nos deparamos com o paradigma de programação chamado procedural, isto é, orientado à procedimentos, que surgem de forma sequencial, geralmente terminados em ponto e vírgula (;), dependendo da linguagem utilizada.

Entretanto, em contraponto á este paradigma de programação, temos o paradigma de programação Orientada a Objetos. Neste paradigma, a forma como pensamos nosso código é bem diferente do paradigma procedural, sendo necessário um esforço mental que chamamos de abstração, um dos pilares da orientação a objetos.

Conceitos Iniciais

Para comprendermos os conceitos que virão pela frente, é necessário antes apresentar alguns termos iniciais da orientação a objetos.

Classe - class

Uma classe é semelhante a um molde para representar um elemento do mundo real, com seus atributos e ações. Para exemplificar estes termos, vamos adotar a partir daqui o exemplo de uma Pessoa, ou seja, uma classe para representar o elemento Pessoa do mundo real.

// Classe Pessoa
class Pessoa 
{

}

Atributos - atributes, att

Os atributos são características da nossa classe como por exemplo nome e idade de uma pessoa no mundo real. Logo, temos:

// Classe Pessoa
class Pessoa 
{
    // Atributos
    public string $nome;
    public int $idade;
}

Métodos - methods, actions, ações

Os métodos são ações que a nossa classe pode executar, e ações são exatamente o que as funções representam no paradigma procedural. Por exemplo, uma pessoa pode saudar e andar.

// Classe Pessoa
class Pessoa 
{
    // Atributos
    public string $nome;
    public int $idade;

    // Métodos
    public function saudar() : string
    {
        return "Olá! Meu nome é " . $this->nome . " e eu tenho " .$this->idade . " anos!";
    }

    public function andar() : string
    {
        return "Andando...";
    }
}

Modificadores de acesso - public, private, protected

ATENÇÃO!

Nem toda linguagem implementa todos os estes modificadores citados, ou ainda podem possuir outros além desses. Consulte a documentação oficial da linguagem com a qual você está trabalhando!

Você deve ter reparado que todo atributo ou método escrito até aqui é precedido por uma palavra reservada chamada public. Este termo é um indicativo de que o modificador de acesso do atributo ou método é público.

Segue uma breve descrição sobre cada um dos modificadores presentes no PHP até agora e o que eles fazem com os atributos e métodos:

  • public
    • podem ser acessados em qualquer escopo onde a classe foi instanciada.
  • private
    • permite o acesso apenas pela própria classe ou subclasses.
  • protected
    • permite o acesso somente no escopo da própria classe.

Métodos Setters e Getters

Estes métodos são criados para que possamos realizar o encapsulamento de nossas classes, conceito que será abordado a seguir.

Por convenção entre a comunidade de programadores é utilizado o padrão: 

setAtributo($valor) 
{
    $this->atributo = $valor;
}

getAtributo() 
{
    return $this->atributo;
}

isto é, a palavra set ou get em lowercase 
seguido do nome do atributo com a primeira letra em uppercase.

Entretanto, em PHP existem alguns "métodos mágicos" 
que são próprios da linguagem, entre eles os métodos:

__set($atributo, $valor) 
{
    $this->$atributo = $valor;
}

e 

__get($atributo)
{
    return $this->$atributo;
}

que funcionam como métodos getters setters padrões da linguagem PHP. Apesar dessa característica particular da linguagem PHP, seguirei definindo os métodos get e set da forma convenida pela comunidade. 

Pesquise na documentação oficial da linguagem de sua preferência se esta possui algum tratamento especial para getters e setters.

Setter - set

São métodos utilizados para definirem algum valor para um atributo de uma mesma classe.

Por exemplo, caso fosse necessário alterar o atributo idade da nossa classe Pessoa, então poderíamos chamar o método setIdade(valor) para realizar esta alteração.

Uma pergunta comum quando estamos iniciando é: por que precisamos dos métodos setters? Não basta apenas fazer uma atribuição simples? Para responder isso, pode-se fazer outra pergunta: e se for definido um valor negativo para o atributo idade? Ora, por este motivo é importante deixarmos nossos atributos protegidos e respaldados pelas regras de negócio da nossa aplicação. No exemplo abaixo você verá uma forma de proteger o atributo idade contra valores negativos.

Getters - get

Este método serve para obter o valor de um determinado atributo. É importante termos esse método em nossas classes por motivos de que nem sempre é interessante deixar um atributo público, visto que as vezes esse atributo em questão pode conter valores sensíveis como um documento, uma senha, um saldo bancário entre outras informações. Com este método podemos garantir a disponibilidade da informação de forma que atenda as necessidades da nossa aplicação.

A partir deste ponto, nossos exemplos vão ter o modificador de acesso private para os atributos das classes. Veja abaixo os métodos set e get implementados em nossa classe Pessoa.

class Pessoa 
{
    // Atributos
    private string $nome;
    private int $idade;

    // Métodos
    public function saudar() : string
    {
        return "Olá! Meu nome é " . $this->nome . " e eu tenho " .$this->idade . " anos!";
    }

    public function andar() : string
    {
        return "Andando...";
    }

    // setters
    public function setIdade(int $valor) : void
    {
        if ($valor > 0) {
            $this->idade = $valor;
        }
        // Repare que agora não serão aceitos valores menores do que zero para o atributo idade.
    }

    public function setNome(string $valor) : void
    {
        $this->nome = $valor;
    }

    // getters
    public function getIdade() : int
    {
        return $this->idade;
    }

    public function getNome() : string
    {
        return $this->nome;
    }
}

Método Construtor

Este método é executado sempre e apenas quando a classe é instanciada, de forma automática.

Imagine que ao instanciar a classe Pessoa, queremos definir seus atributos:

class Pessoa 
{
    // Atributos
    private string $nome;
    private int $idade;

    // Métodos

    // construtor
    function __construct(string $nome, int $idade) 
    {
        $this->setNome($nome);
        $this->setIdade($idade);
    }

    public function saudar() : string
    {
        return "Olá! Meu nome é " . $this->nome . " e eu tenho " .$this->idade . " anos!";
    }

    public function andar() : string
    {
        return "Andando...";
    }

    // setters
    public function setIdade(int $valor) : void
    {
        if ($valor > 0) {
            $this->idade = $valor;
        }
    }

    public function setNome(string $valor) : void
    {
        $this->nome = $valor;
    }

    // getters
    public function getIdade() : int
    {
        return $this->idade;
    }

    public function getNome() : string
    {
        return $this->nome;
    }
}

// Instanciando a classe Pessoa:
$p = new Pessoa("Matheus", 26);

echo $p->getIdade(); // escreve "26"
echo $p->getNome(); // escreve "Matheus"

Neste exemplo definimos que a classe deverá ser instanciada recebendo dois parâmetros, que serão valores para os atributos nome e idade.

ATENÇÃO

A sintaxe de método construtor da linguagem PHP é:

function __construct() 
{
    ... faz alguma coisa ...
}

Entretanto, outras linguagens podem implementar o seu método construtor de outra forma. 
É muito comum métodos construtores possuírem o nome da classe, 
como em Java e C#, ou seja, os métodos construtores 
da nossa classe Pessoa nessas linguagens seria:

Pessoa() 
{
    ... faz alguma coisa ...
}

Pilares da Programação Orientada a Objetos

Pilares da Orientação a Objetos

Abstração

A abstração consiste em representarmos elementos do mundo real em nossos códigos.

Um exemplo clássico de abstração é a representação de uma pessoa através de uma classe, definindo, assim, seus atributos e métodos como fizemos nos exemplos anteriores.

Herança

Herança como o nome sugere tem a ver com o conceito de herdar alguma coisa. No caso da orientação a objetos, é quando queremos que uma determinada classe receba atributos e métodos de uma outra classe herdada.

Esse pilar da POO é especialmente interessante por ser ele quem garante que não vamos repetir linhas de código desnecessariamente.

Exemplo:

Imagine que desejamos criar uma classe para representar um aluno, e todo aluno, obviamente, possui um nome e uma idade, certo? Logo não faria sentido recriar esses atributos para a classe Aluno, visto que um aluno obviamente é uma pessoa, logo nós percemos através de uma abstração que a classe Aluno é uma extensão da classe Pessoa.

class Aluno extends Pessoa
{
    // Atributos
    private string $matricula;
    private int $periodo;

    // Métodos
    public function setMatricula(string $valor) : void
    {
        $this->matricula = $valor;
    }

    public function setPeriodo(int $valor) : void
    {
        if ($valor > 0) {
            $this->periodo = $valor;
        }
    }

    public function getMatricula() : string
    {
        return $this->matricula;
    }

    public function getPeriodo() : int
    {
        return $this->periodo;
    }
}

// Instanciando a classe Aluno

$a = new Aluno("Matheus", 26);
echo $a->getNome(); // escreve "Matheus"
echo $a->getIdade(); // escreve "26"
echo $a->saudar(); // escreve "Olá! Meu nome é Matheus e eu tenho 26 anos!"

Apesar de não estar especificado na classe Aluno, ela possui todos os atributos e métodos da classe Pessoa - desde que estes não sejam do tipo protected.

Polimorfismo

A palavra polimorfismo significa muitas formas, em resumo, isso significa que um método pode possuir muitas formas de ser invocado.

Seguindo nosso exemplo anterior, na classe Aluno poderíamos reescrever o método saudar de forma a especificar comportamentos mais adequados à classe, como uma mensagem de saudação diferente, incluindo os atributos matricula e período. Para isso, vamos reescrever, também, o método construtor na classe Aluno.

class Aluno extends Pessoa
{
    // Atributos
    private string $matricula;
    private int $periodo;

    // Métodos

    // sobrescrita do método __construct
    function __construct(string $nome, int $idade, string $matricula, int $periodo)
    {
        $this->setNome($nome);
        $this->setIdade($idade);
        $this->matricula = $matricula;
        $this->periodo = $periodo;
    }

    // sobrescrita do método saudar
    public function saudar() : string
    {
        return "Olá! Meu nome é " . $this->getNome() . " e eu tenho " . $this->getIdade() . " anos!"
        . "Sou aluno do " . $this->periodo . " período e minha matrícula é " . $this->matricula;
    }

    public function setMatricula(string $valor) : void
    {
        $this->matricula = $valor;
    }

    public function setPeriodo(int $valor) : void
    {
        if ($valor > 0) {
            $this->periodo = $valor;
        }
    }

    public function getMatricula() : string
    {
        return $this->matricula;
    }

    public function getPeriodo() : int
    {
        return $this->periodo;
    }
}

// Instanciando Aluno
$a = new Aluno("Matheus", 26, "123456", 3);
echo $a->saudar(); // "Olá! Meu nome é Matheus e eu tenho 26 anos! Sou aluno do 3 período e minha matrícula é 123456"

Repare que nós temos a sobrescrita dos métodos saudar e __construct, significando dizer que, quando a classe Aluno for instanciada, os métodos reescritos dessa classe serão executados quando forem invocados, mesmo que estes possuam nomes iguais aos métodos da classe pai.

Este tipo de polimorfismo é conhecido por override, ou seja, sobrescrita e ele é apenas um dos tipos de polimorfismos existentes.

Outro tipo de polimorfismo é o chamado overload, ou seja, sobrecarga, onde uma classe ou subclasse pode ter métodos com o mesmo nome, porém com assinaturas diferentes.


class Aluno extends Pessoa
{
    // Atributos
    private string $matricula;
    private int $periodo;

    // Métodos
    function __construct(string $nome, int $idade, string $matricula, int $periodo)
    {
        $this->setNome($nome);
        $this->setIdade($idade);
        $this->matricula = $matricula;
        $this->periodo = $periodo;
    }

    public function saudar() : string
    {
        return "Olá! Meu nome é " . $this->getNome() . " e eu tenho " . $this->getIdade() . " anos!"
        . "Sou aluno do " . $this->periodo . " período e minha matrícula é " . $this->matricula;
    }

    // Overload do método saudar
    public function saudar(string $nome) : string
    {
        return "Olá, " . $nome . "! Meu nome é " . $this->getNome() . "!";
    }

    public function setMatricula(string $valor) : void
    {
        $this->matricula = $valor;
    }

    public function setPeriodo(int $valor) : void
    {
        if ($valor > 0) {
            $this->periodo = $valor;
        }
    }

    public function getMatricula() : string
    {
        return $this->matricula;
    }

    public function getPeriodo() : int
    {
        return $this->periodo;
    }
}

// Instanciando Aluno
$a = new Aluno("Matheus", 26, "123456", 3);

echo $a->saudar(); // "Olá! Meu nome é Matheus e eu tenho 26 anos! Sou aluno do 3 período e minha matrícula é 123456"

echo $a->saudar("Fulano"); // Olá, Fulano! Meu nome é Matheus!

Perceba que agora temos dois métodos saudar dentro da nossa classe Aluno. Um deles recebe um parâmetro e outro não, isto é, possuem assinaturas diferentes. Nesse caso, temos dois métodos com nomes iguais, porém com assinaturas e comportamentos diferentes devido a essa diferença nas assinaturas. Isto é polimorfismo de sobrecarga!

ATENÇÃO

Existem outros tipos de polimorfismos que você pode estudar mais a fundo no futuro, 
entretanto, os mais comuns serão esses que você viu agora.

Mais uma vez os exemplos dados são possíveis implementações de polimorfismos suportados pela linguagem PHP. 
Outras linguagens podem não implementar estes tipos de polimorfismo. Consulte sempre a documentação!

Encapsulamento

Como você viu até aqui, foram apresentados modificadores de acesso e métodos getters e setters que possibilitam o controle sobre modificações e acessos aos nossos atributos e métodos afim de garantir a integridade dos dados e da aplicação como um todo.

Apesar de em um primeiro momento parecer abstrato o porquê de ser necessário o tal encapsulamento em classes, uma dica valiosa é sempre escrever seus programas pensando em possíveis falhas de segurança que eles possam ter - como o exemplo da idade com valor negativo que vimos anteriormente - visando garantir que informações sigilosas não fiquem disponíveis para qualquer um.

O pilar do encapsulamento confere uma camada de segurança as nossas aplicações com seus modificadores e métodos próprios para este fim.

Conclusão

Se você leu até aqui, espero que este conteúdo possa ter ajudado a compreender melhor a programação orientada a objetos. Procurei ser o mais conciso e didático nos exemplos afim de que você possa começar a repensar seus códigos de forma orientada a objetos.

Nesse primeiro momento, inevitavelmente você vai acabar escrevendo códigos procedurais orientados a classes, e sobre isso o Vinícius Dias do blog dias.dev escreveu um excelente artigo que vale a pena ser lido posteriormente:

Programação procedural orientada a classes

Até a próxima!