Tutorial PHP: 4 Conceitos básicos da Orientação a Objetos

Blog Sobre tecnologia

Tutorial PHP: 4 Conceitos básicos da Orientação a Objetos

Tutorial - Orientação a Objetos PHP

Quando começamos a trabalhar com a orientação a objetos também começamos a trabalhar alguns conceitos que são um pouco diferentes do que costumamos ver na programação estruturada. Passamos  a desenvolver nossas aplicações de forma mais especializada e também, de certa forma, um pouco mais fragmentada (no bom sentido claro!).

Mas no fim das contas, quando falamos em orientação a objetos, falamos do que mesmo?

Vamos ver abaixo alguns conceitos, também chamados de pilares:

Conceito 1: Abstração

A principal razão de existir do paradigma da orientação a objetos é a representação de objetos reais no ambiente computacional, ou seja, iremos representar no nosso sistema um objeto que temos também na vida real, por exemplo, um carro, uma bola, uma pessoa, um animal e assim por diante. A esta representação no ambiente computacional chamamos de abstração.

Em PHP um objeto sempre será representado no ambiente computacional por uma classe.

Cada classe é única dentro de um sistema, ou seja, ela tem uma identidade, na prática, isso significa que não podem existir duas classes “Cachorro” dentro de um sistema, algumas linguagens permitem definir um escopo para estas classe, em algumas delas chamamos isso de pacotes, em outras, como é o caso do PHP, de Namespaces. Isso permite que existam duas classes com o mesmo nome, porém em namespaces diferentes.

Poderíamos ter algo semelhante a isso:

<?php 
namespace Model; 
class Cachorro 
{
     //... 
}
<?php
namespace Repository; 
class Cachorro 
{
     //... 
}

A classe do namespace Model representaria o Animal em si, enquanto que a classe dentro do namespace Repository, é a responsável por persistir os dados do Cachorro em nosso banco de dados.

Propriedades

Voltando a lógica da vida real, qualquer objeto que tenhamos em nosso mundo possui características que o representam de alguma forma, vamos tomar como exemplo uma cadeira. Quais características podemos observar geralmente em cadeiras?

  • Quantidade de pernas (1, 2, 3, 4 … ?)
  • Material que foi feito (Madeira, alumínio, ferro…)
  • É estofada?
  • Tem rodinhas?
  • É reclinável?
  • etc

Viram? Um objeto tão simples e quantas características pudemos extrair dele!

No ambiente computacional, estas características são traduzidas em propriedades de nossa classe, para o nosso exemplo acima, teríamos a seguinte classe:

<?php 
class Cadeira 
{
     $qtdPernas;
     $material;
     $ehEstofada;
     $temRodas;
     $ehReclinavel;
}

Aqui vale uma observação importante, parte dessa abstração também é identificar quais características do objeto são úteis ou não para nosso sistema. Por exemplo:

Quantas características conseguimos extrair de um objeto Roda?

  • Aro
  • Material (Ferro, liga leve etc)
  • Tala
  • Quantidade de parafusos

Todas essas características fazem sentido para uma loja que trabalha com rodas e pneus, mas fazem sentido também para uma concessionária que colocaria esta roda em um de seus carros?

Ou somente Aro e Material seriam suficientes?

Métodos

Por mais simples que um objeto possa parecer, ele sempre realiza alguma ação, direta ou indiretamente, por exemplo, a nossa roda, que abstraímos no item anterior, pode girar, nosso cachorro pode latir, pode andar, pode comer etc. Estas ações, no ambiente computacional recebem o nome de métodos, aqueles mesmos métodos que vimos no post anterior, e é uma convenção comum usarmos verbos como nome para elas, veja:

<?php 
namespace Model; 
class Cachorro 
{ 
    public function andar()
    {
        // ...
    }

    public function comer()
    {
        // ...
    }

    public function latir()
    {
        // ...
    }
}

Conceito 2: Herança

Um dos conceitos chave da orientação a objetos é a herança, que, basicamente, é o mesmo conceito de herança na vida real, ou seja, uma classe pode receber atributos e/ou métodos de outra classe, assim como um filho recebe um imóvel ou certa quantia de dinheiro quando seus pais falecem.

Vamos a um exemplo, clássico na verdade, usando nosso exemplo acima, a classe Cachorro. Se analisarmos ela com calma, podemos notar que ela possui alguns métodos que podemos utilizar para outros animais em casos onde o nosso sistema trabalhe com várias espécies (um sistema para uma clínica veterinária, talvez?), neste caso uma classe chamada Animal, poderia conter os métodos comer() e andar(), que basicamente são ações que todas as espécies executam.

Ou seja, um cachorro, ou um pato podem executar as ações acima (Tudo bem, no caso do pato talvez não tão bem). Então neste caso podemos dizer que classes como Cachorro ou Pato podem herdar os métodos andar() e comer() da classe Animal, desta forma reutilizando os métodos da mesma.

Conceito 3: Encapsulamento

Este é um conceito importante e que ajuda na segurança da aplicação.

Basicamente, podemos dizer que esta técnica permite esconder determinadas propriedades do nosso objeto que não deveriam ser visíveis para outras partes do sistema que não deveriam ter acesso a elas.

Um exemplo prático:

Tendo em mente uma conta bancária, esta conta deve ter um saldo, porém faz sentido deixarmos o saldo acessível para todas as partes do sistema?

<?php
namespace Conta;

class Conta 
{
    $saldo;
    
    /**
     * Inicializa a conta com um saldo
     */
    function __construct($valor)
    {
        $this->saldo = $valor;
    }

    /**
     * Deposita um valor na conta
     */
    function depositar($valor)
    {
        $this->saldo += $valor;
    }

    /**
     * Saca um valor da conta
     */
    function sacar($valor)
    {
        // Verifica se existe saldo para realizar o saque
        if($valor < $saldo) {
            $this->saldo -= $valor;
        }
    }
}

O código acima parece seguro, certo? Mas lembram-se de nosso último post?

mais especificamente esta parte:

Nas linhas que seguem, mudamos o estado desta classe de duas formas, a primeira chamando o método setAluno e a segunda associando as notas diretamente ao atributo (o que também não é recomendável, e falaremos mais a respeito no próximo post).

Resumo da ópera, a propriedade pode ser acessada de qualquer jeito, por qualquer pessoa que que esteja desenvolvendo o sistema junto a gente, ou até mesmo por nós mesmos “sem querer”.

O que aconteceria, por exemplo, se algum que estivesse utilizando o objeto fizesse o seguinte para realizar um saque:

<?php

$conta = new Conta(2500);

$conta->saldo -= 5000;

Note que em nosso objeto existe o método sacar, que faz uma validação para certificar que o usuário tenha saldo suficiente para realizar o saque, e neste caso, estamos ignorando o método sacar e fazendo o saque diretamente do saldo, o que gerará um problema, pois o saldo do cliente é bem menor que o valor a ser sacado.

Então, acessar a propriedade diretamente é uma péssima ideia, como encapsulamos ela?

Modificadores de acesso

Toda propriedade ou método de uma classe (objeto) pode ter um modificador de acesso, mais do que isso, é uma recomendação da PSR-2 que todos os métodos sejam precedidos dos seus modificadores de acesso.

Tanto para métodos quanto para propriedades, os modificadores de acesso que podemos utilizar são:

  • public
  • protected
  • private

O modificador public tem o mesmo efeito que se não informarmos um modificador, ou seja

public $saldo;

É o mesmo que:

$saldo;

O modificador private, limita o acesso a propriedade ou método somente ao objeto a qual pertence.

Veja o exemplo abaixo, com a classe alterada e utilizando o modificador de acesso private:

<?php
namespace Conta;

class Conta 
{
    private $saldo;
    
    /**
     * Inicializa a conta com um saldo
     */
    public function __construct($valor)
    {
        $this->saldo = $valor;
    }

    /**
     * Deposita um valor na conta
     */
    public function depositar($valor)
    {
        $this->saldo += $valor;
    }

    /**
     * Saca um valor da conta
     */
    public function sacar($valor)
    {
        // Verifica se existe saldo para realizar o saque
        if($valor < $saldo) {
            $this->saldo -= $valor;
        }
    }
}

Código que consumirá o Objeto:

<?php
require 'conta.php';

use Conta\Conta;

$conta = new Conta(2500);

$conta->saldo = 2700;

Quando executamos este código, obtemos o seguinte erro:

PHP Fatal error: Uncaught Error: Cannot access private property Conta\Conta::$saldo

Já o modificador protected funciona de forma semelhante ao modificador private, ou seja, ele restringe o acesso direto a propriedade ou método de fora da classe a qual ela pertence, porém com uma particularidade, ela permite o acesso a esta propriedade/método quando as classes herdam a classe que ela pertence.

Vejamos um exemplo da nossa classe conta um pouco mais completo:

<?php
namespace Conta;

class Conta 
{
    protected $saldo;
    
    /**
     * Inicializa a conta com um saldo
     */
    public function __construct($valor)
    {
        $this->saldo = $valor;
    }

    /**
     * Deposita um valor na conta
     */
    public function depositar($valor)
    {
        $this->saldo += $valor;
    }

    
}

class ContaCorrente extends Conta
{
    /**
     * Saca um valor da conta
     */
    public function sacar($valor)
    {
        // Verifica se existe saldo para realizar o saque
        if($valor < $saldo) {
            $this->saldo -= $valor;
        }
    }
}


class ContaPoupanca extends Conta
{
    private $limiteSaque;

    public function __construct($saldo, $limiteSaque)
    {
        parent::__construct($saldo);
        $this->limiteSaque = $limiteSaque;
    }

    /**
     * Saca um valor da conta
     */
    public function sacar($valor)
    {
        // Verifica se existe saldo para realizar o saque
        if($valor < $saldo && $valor < $limiteSaque) {
            $this->saldo -= $valor;
        }
    }
}

No exemplo acima criamos duas classes que estendem a classe Conta, ou seja, que herdam as características e métodos da classe pai, podemos observar que definimos uma método sacar com uma uma regra de negócios distinta para cada tipo de conta.

Além disso também criamos um novo atributo para a classe ContaPoupanca, e adicionamos ele a inicialização da classe (método __construct), então chamamos o __construct da classe pai e inicializamos os atributos da classe filha.

Polimorfismo

Heis mais um conceito importante, o polimorfismo.

Em orientação a objetos é muito comum o uso de classes mais abstratas e irmos estendendo seu uso para classes mais concretas, chamamos isso de polimorfismo, ou seja, uma classe mais abstrata que trata os objetos de forma mais “macro”, isto é, trata das características comuns a todos seus descendentes, e outras que derivam desta classe pai e que são mais especializadas, tratando de casos mais especializados.

Algumas vezes é preciso reescrever métodos que trabalhavam de um jeito na classe pai, mas que precisam ser mais refinados em algumas de suas classes filho, um exemplo que vimos anteriormente foi o exemplo da Conta, no caso dela tínhamos um método sacar para cada classe filha.

Basicamente falando, o polimorfismo se refere a capacidade de dito objeto ser referenciado de formas distintas, porém sem que isso seja alterado de uma hora para outra, se um animal nasce pato, ele morre pato, se outro nasce gato, morre gato.

Ou seja, se um objeto foi criado com um tipo ele será daquele tipo até deixar de existir.

Vamos utilizar o exemplo anterior e completá-lo um pouco mais:

<?php
namespace Conta;

class Conta 
{
    protected $saldo;
    
    /**
     * Inicializa a conta com um saldo
     */
    public function __construct($valor)
    {
        $this>saldo = $valor;
    }

    /**
     * Deposita um valor na conta
     */
    public function depositar($valor)
    {
        $this>saldo += $valor;
    }

    public function transferir($valor, $conta)
    {
        if($this>sacar()) {
            $conta>deposita($valor);
        }
    }
}

class ContaCorrente extends Conta
{
    /**
     * Saca um valor da conta
     */
    public function sacar($valor)
    {
        // Verifica se existe saldo para realizar o saque
        if($valor < $saldo) {
            $this>saldo -= $valor;
            return true;
        }
        return false;
    }
}


class ContaPoupanca extends Conta
{
    private $limiteSaque;
    private $limiteTransferencia;

    public function __construct($saldo, $limiteSaque, $limiteTransferencia)
    {
        parent::__construct($saldo);
        $this>limiteSaque = $limiteSaque;
        $this>limiteTransferencia = $limiteTransferencia;
    }

    /**
     * Saca um valor da conta
     */
    public function sacar($valor)
    {
        // Verifica se existe saldo para realizar o saque
        if($valor < $saldo && $valor < $limiteSaque) {
            $this>saldo -= $valor;
            return true;
        }

        return false;
    }

    public function transferir($valor, $conta) 
    { 
        if($this>sacar() && $valor < $this>limiteTransferencia) { 
            $conta>deposita($valor); 
        } 
    }
}

Agora inserimos nas nossas classe o método transferir, onde a regra de negócio é verificar se o saque da conta atual foi realizado com sucesso, se for, ele deposita o valor na conta desejada.

Inserimos também no método sacar o retorno de sucesso ou não.

Agora observemos a estrutura das três classes:

Diagrama UML das classes Conta

Observe que, nós criamos um método transferir, tanto na classe pai, quanto na classe ContaPoupanca, porém na ContaPoupanca nós aplicamos uma regra de negócios diferente, a estes casos chamamos de sobrescrita de métodos, no nosso código de exemplo temos dois casos, o método transferir e o método __construct.

A sobrescrita acontecerá sempre que tivermos um método que é compartilhado por todas as classes filhas, porém em algumas das classes filhas, ou seja, da classe concreta, a que será utilizada realmente, existe uma necessidade especial ou diferente, no nosso caso, precisamos verificar na classe ContaPoupanca se o valor solicitado é inferior ao limite diário de transferência do cliente.

Também precisamos inicializar os atributos que são específicos desta classe com relação a sua classe pai, e fazemos isso através do método construtor __construct, dentro dele inicializamos o atributo da classe pai usando parent::__construct($saldo);e os atributos específicos.

Mas afinal, por que usar a orientação a objetos

Bom, uma das principais razões para trabalharmos com orientação a objetos é a redução da complexidade de nosso sistema e o favorecimento de reuso de código, assim tanto quem desenvolve quanto quem, eventualmente, venha a fazer a manutenção do código posteriormente (mesmo que seja a mesma pessoa que desenvolveu) consegue entender de forma mais fácil o sistema como um todo, o que se traduz em um custo menor e maior agilidade na manutenção.

O desenvolvimento em si também acaba se tornando mais rápido, visto que é possível reaproveitar blocos de código e/ou objetos que já foram usados em outros sistemas mas que são comuns entre si, um bom exemplo seria um módulo de login, que se difere pouco entre um sistema e outro, porém consume um bom tempo para ser desenvolvido.

Esperamos que este texto tenha sido de alguma ajuda aos caros colegas e leitores, aqui tentamos abordar o básico da orientação a objetos, de forma a introduzir o conteúdo e nos aprofundarmos conforme formos desenvolvendo as próximas etapas do nosso tutorial.

Se gostou do conteúdo e o considera útil, ajude a divulgar, pode ser uma ajuda para outros também.

Sobre Rodrigo Teixeira Andreotti

Técnico em Informática formado pela ETE Lauro Gomes Cursando atualmente o curso de Análise e Desenvolvimento de sistemas. Atuo como programador PHP há, pelo menos, 6 anos, sendo os dois últimos em empresas e os demais como Freelancer.


Also published on Medium.