Aulas de Resumo, Parte 1 – Comportamento de Abstração
Cerca de um mês atrás, escrevi sobre um dos pilares da programação orientada a objetos (especificamente a abstração). No post, defini abstração como o seguinte:
Em vez disso, vamos abstrair ideias em suas classes. E há uma ideia chave aqui: uma classe deve representar um substantivo.
E embora isso ainda seja verdade, a ideia de classes abstratas é algo diferente na programação orientada a objetos.
Parece confuso, certo? Aquilo é:
- em um nível, temos abstração sendo definida como a ideia de que pegamos uma ideia e a representamos em uma classe,
- em outro nível, temos classes abstratas que são usadas para ajudar a definir funções que as subclasses devem implementar.
E se isso não for confuso o suficiente, misturamos isso com interfaces que fornecem um contrato de implementação de classes que deve seguir, e então misturamos com classes abstratas que definem métodos que também devem ser implementados, mas também podem implementar métodos próprios.
Confuso ainda? Sem problemas. O objetivo dos próximos três posts é fazer o seguinte:
- Defina o que são classes abstratas,
- Descrever o diferente em classes abstratas e interfaces,
- Ajude a decidir quando você quer usar um sobre o outro.
Com isso dito, aqui está toda a ideia por trás das classes abstratas.
Comportamento de abstração
Em primeiro lugar, há uma diferença na abstração e nas classes abstratas. A primeira refere-se à ideia de representar algo na programação; o último refere-se a uma maneira real de escrever código.
E uma das melhores maneiras que encontrei para pensar sobre classes abstratas na programação orientada a objetos é pensar nelas assim:
As classes abstratas são um substituto para a implementação.
Talvez outra maneira de pensar neles seja como espaços reservados. Em última análise, eles fornecem o comportamento que as subclasses devem implementar.
Como isso é diferente de uma interface? Lembre-se de que uma interface define uma assinatura para uma função (o nome da função, seus argumentos e seus modificadores de visibilidade) que uma classe deve implementar.
A abstração, por outro lado, fornece um substituto para a implementação que uma subclasse deve implementar. Mas talvez isso seja melhor demonstrado através do uso de código.
Abstração na prática
Digamos que você esteja trabalhando em um projeto e descubra que tem uma funcionalidade que existe em mais de um lugar. Além de violar toda a ideia de DRY, ele também tem potencial para ser um lugar onde você pode abstrair a funcionalidade em uma classe base e usá-la novamente.
Vamos considerar isso no contexto de um sistema de publicação. Não é necessariamente assim que o WordPress o implementa, mas usa uma ideia com a qual estamos familiarizados: Taxonomias.
No WordPress, lembre-se que temos Tags e Categorias. Existem diferenças sutis entre os dois (como se um é hierárquico ou não), mas eles também compartilham atributos semelhantes, como ter um nome e um slug.
Uma Abstração de Taxonomia
Assim, podemos começar escrevendo uma classe abstrata de Taxonomia que abstrai a funcionalidade comum em sua própria classe.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
No código acima, você verá que fiz o seguinte:
- declarou o resumo da classe
- definiu vários atributos que serão definidos no construtor
- desde várias funções públicas com implementação,
- adicionou vários métodos protegidos.
A principal vantagem de olhar para essa classe é que qualquer classe que implemente essa classe abstrata terá automaticamente a funcionalidade definida no construtor, na função getName e na função getSlug.
Eles não terão, no entanto, a implementação das funções abstratas. Isso é o que resta para ser implementado pelas subclasses (que compartilharei momentaneamente).
Uma taxonomia concreta: uma etiqueta
Agora que temos uma classe abstrata definida, é possível realmente implementar a abstração. Por exemplo :
<?php
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
No código acima, observe que tudo que a classe faz é fornecer a implementação para as funções abstratas definidas na classe abstrata (que é especificada pela função extends na definição da classe).
Mais adiante neste artigo, compartilharei como testar esse código, mas observe que o acima não apenas oferece a funcionalidade que você vê, mas também a funcionalidade da classe Taxonomy.
Uma taxonomia concreta: uma categoria
Antes de dar uma olhada nisso em ação, quero definir uma categoria também. Isso incluirá código que implementa funções da classe abstrata, mas também funções próprias.
<?php
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
Aqui, temos tudo que vem com a classe Taxonomy, mas também definimos nossa propriedade para seu ID pai e métodos getter e setter. Embora trivial neste caso, mostra como as categorias, que são hierárquicas, podem funcionar.
Além disso, se a categoria não tiver pai, o ID será definido como -1, o que facilita a gravação para testes automatizados ou até mesmo verificar se tem um pai.
Vendo isso em ação
Para demonstrar todo esse código, tenho uma essência que inclui todo o código em um único arquivo. Como prática recomendada, não recomendo isso. Em vez disso, cada classe deve ser mantida em seu próprio arquivo e cada classe deve pertencer a um namespace.
Mas como isso é puramente para fins de demonstração, é suficiente.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
/*--*/
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
/*--*/
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
/*- Tag Demo ----------------------------*/
$tag = new Tag('Acme Tag');
echo $tag->getName();
echo $tag->getSlug();
/*- Category Demo -----------------------*/
$category = new Category('Acme Category');
echo $category->getName();
echo $category->getSlug();
echo $category->getParentId();
$category->setParentId(100);
echo $category->getparentId();
Ao executar isso no console, você deverá ver algo como a seguinte saída:
Você pode precisar adicionar algumas declarações de eco para ter certeza de que está criando novas linhas, mas isso é com você.
E as interfaces?
Então, neste momento:
- temos uma definição funcional do que são classes abstratas,
- temos um exemplo de como são as classes abstratas,
- e temos uma demonstração funcional de como eles podem funcionar.
Em seguida, vou me aprofundar na discussão das diferenças entre classes abstratas e interfaces, quando você pode querer usar uma sobre a outra, ou quando você pode querer usá-las em conjunto.