3.6. Orientação à Objetos em Dart: Classes Abstratas


Usamos a palavra chave abstract para impedir que uma classe possa ser instanciada.

.

Interfaces e Classes Abstratas

Introdução

O problema da Herança Multipla

Em Dart não se pode fazer uma herança multipla, ou seja, uma classe não pode herdar de duas classes. Mas você sabe o por quê? A herança múltipla possui o problema de conflito de nomes. Imagine se você criar uma classe chamada Humano que tenha um atributo nome, e uma Classe Robô que também tenha o atributo nome. Ao criar uma classe Android que herde de Humano e de Robô você vai ter problemas sérios, geralmente complicados de resolver. Herança Múltipla é uma das facilidades que geralmente não facilitam nada que criamos.

Então para resolver este problema nós utilizamos a interface ou abstract class, no caso exposto acima, Android será uma classe abstrata.

Objetivo

O objetivo aqui é pegar classes não relacionadas, isto é, pegar funcionalidades comuns para classes não relacionadas.

Assim, por exemplo, um gato, cachorro, leão podem ser agressivos, mas esta relação é diferente de uma relação de herança, é uma relação de implementação (implementam aquilo que existe na classe Agressivos).

Portanto não estamos limitados somente a heranças, mas também podemos usar interfaces ou classes abstratas.

Isto tudo se resume a seguinte pergunta: como podemos utilizar funções comuns para classes não relacionadas? que é para quebrar esse limite que temos de que uma classe não pode jamais herdade de duas classes.

O gato, cachorro e leão continuam sendo Animais, herdam esta característica, porém a agressão é uma implementação.

Classes Abstratas

Não é concreta, é só uma idéia, um conceito abstrato, uma classe genérica só tem um conteúdo padrão que depois poderá ser implementado de uma forma concreta. Ex. se eu disser que gosto de formas (é muito genérico), a pergunta que você faria seria qual forma? aí vem a classe concreta que explica que tipo de forma.

Por exemplo, dentro de Animal temos os felinos e pessoas, dentro de felinos gato e tigre que são concretos.

Assim, vemos que as classes abstratas são genéricas.

Usamos a palavra chave abstract para impedir que uma classe possa ser instanciada. Esse é o efeito direto de se usar o modificador abstract na declaração de uma classe:

abstract class Animal {
  String nome;
  double peso; 

  Animal(this.nome, this.peso); 

  void comer(){
    print("$nome comeu");
  } 

  void fazerSom(){
    print("$nome fez som!");
  }
}

Para usá-la, basta colocar abstract antes da classe que quer tornar abstrata, assim, não poderá instanciá-la, mas pode-se usar ela como super classe para herdar algo.

Pode-se também declarar métodos nas classes abstratas sem declarar nada, sem nenhum corpo, ou seja, na parte void fazerSom() pode-se retirar o seu corpo, desta forma:

abstract class Animal {
  String nome;
  double peso; 

  Animal(this.nome, this.peso); 

  void comer(){
    print("$nome comeu");
  } 

  void fazerSom();
}

Porém, será obrigado a implementar (com implements ou extends) ela nas classes que herdam a classe abstrata. E sempre que implementá-las temos que implementar o método:

abstract class Animal {
  String nome;
  double peso; 

  Animal(this.nome, this.peso); 

  void comer(){
    print("$nome comeu");
  } 

  void fazerSom();
}
 
class Gato extends Animal {
  Gato(String nome, double peso): super(nome, peso); 

  bool estaAmigavel(){
    return true;
  } 

  // Obrigado a implementá-lo
  @override
  void fazerSom(){
    print("$nome fez miau!");
  }
}

Agora vamos em declarar em main para ver o que ocorre:

main() {
var gato = Gato('Garfield', 0.30);
gato.comer();
}

Irá mostrar 'Garfield comeu' no console, veja que implementamos o método comer da classe abstrata no Gato. 

Qual o potencial desssa classe?

Vamos criar tudo novamente, primeiro uma classe Animal:

abstract class Animal {
void respirar();

void fazerSom(){
print('fazendo som');
}
}

Criando a classe Pessoa com alguns atributos:

class Pessoa implements Animal {
String nome, nacionalidade;

Pessoa(this.nome, this.nacionalidade);

@override
void respirar() {
print('respirando igual um humano');
}

@override
void fazerSom() {
print('fazendo som como Humano');
}
}

Agora teremos que utilizar o construtor em main:

main() {
var pessoa = Pessoa('Tiago', 'Brasileiro');
pessoa.respirar();
pessoa.fazerSom();
}

Criando a classe abstrata Engracado:

abstract class Engracado {

}

Criaremos uma classe Humorista que extenderá Pessoa implementando a classe Engracada:

class Humorista extends Pessoa implements Engracado {
// como criamos construtores na class Pessoa, temos que passá-lo para o super
Humorista(String nome, String nacionalidade) : super(nome, nacionalidade);
}

Na classe Engracado criaremos uma função e já que a classe Humorista implementa esta classe, teremos também que fazer o @override nele também:

abstract class Engracado {
void fazerRir();
}

class Humorista extends Pessoa implements Engracado {
// como criamos construtores na class Pessoa, temos que passá-lo para o super
Humorista(String nome, String nacionalidade) : super(nome, nacionalidade);

void fazerRir() {
print("humorista engracado");
}
}

Vamos testar:

main() {
var pessoa = Pessoa('Tiago', 'Brasileiro');

var grandeHumorista = Humorista("Pedro", "Americano");
grandeHumorista.fazerSom();
grandeHumorista.respirar();
grandeHumorista.fazerRir();
}

Assim, podemos fazer aqui também outra classe chamada Palestra que não tem relação com Animal, nem é uma pessoa, mas que pode implementar a classe Engracado.

Mas aí você me pergunta: para que server esse trem?

Bom, no código escrito acima, não faz sentido criar o objeto "Animal", pois eu só quero saber que tipo de animal é, entendeu?

O que, exatamente, vem a ser a nossa classe Animal? Eu só tenho dois animais gato e cachorro. Ela é uma classe que apenas idealiza um tipo, define apenas um rascunho.

Para o nosso sistema, é inadmissível que um objeto seja apenas do tipo Animal (pode existir um sistema em que faça sentido ter objetos do tipo Animal, mas no nosso caso não).

Este é o último arigo dessa série, próximo artigo iremos aprender a criar listas em Dart.


Todo os artigos postados destas séries (Dart Iniciante e Intermediário) foram baseados no https://www.udemy.com/curso-completo-flutter-app-android-ios/ e com alguns comentários retirados do livro "Dart: Up and Running by Kathy Walrath and Seth Ladd". Recomendo a todos a fazer o curso, pois estou aprendendo muito com este curso.
Atualizado em 23/11/2019.
3125 Visualizações