Modificadores de Classe em Dart

15 jun. 2025

Modificadores de Classe em Dart: O Guia Completo com Exemplos Práticos

Os modificadores de classe em Dart são como superpoderes que definem como suas classes podem ser utilizadas, tanto dentro da própria biblioteca quanto externamente. Eles oferecem controle granular sobre herança, implementação e instanciação de classes.

O que são Modificadores de Classe?

Modificadores de classe são palavras-chave que precedem a declaração de uma classe ou mixin, moldando seu comportamento e uso. Eles determinam:

  • Como a classe pode ser herdada
  • Se pode ser instanciada diretamente
  • Quais bibliotecas podem acessá-la
  • Como ela interage com outras classes

Lista Completa de Modificadores

  • abstract
  • base
  • final
  • interface
  • sealed
  • mixin

1. ABSTRACT - O Mentor Misterioso 🧙‍♂️

Classes abstratas são como mentores sábios que ensinam, mas nunca entram em ação diretamente. Elas definem um contrato que suas subclasses devem seguir.

Características:

  • Não podem ser instanciadas diretamente
  • Podem ter métodos abstratos (sem implementação)
  • Podem ter métodos concretos (com implementação)
  • Devem ser herdadas para serem úteis
// Definindo uma classe abstrata
abstract class Animal {
  String nome;
  
  Animal(this.nome);
  
  // Método abstrato - deve ser implementado pelas subclasses
  void emitirSom();
  
  // Método concreto - pode ser usado diretamente
  void dormir() {
    print('$nome está dormindo...');
  }
}

// Implementando a classe abstrata
class Cachorro extends Animal {
  Cachorro(String nome) : super(nome);
  
  @override
  void emitirSom() {
    print('$nome faz: Au au!');
  }
}

class Gato extends Animal {
  Gato(String nome) : super(nome);
  
  @override
  void emitirSom() {
    print('$nome faz: Miau!');
  }
}

void main() {
  // Animal animal = Animal('Rex'); // ❌ ERRO! Não pode instanciar classe abstrata
  
  Cachorro rex = Cachorro('Rex');
  Gato mimi = Gato('Mimi');
  
  rex.emitirSom(); // Rex faz: Au au!
  rex.dormir();    // Rex está dormindo...
  
  mimi.emitirSom(); // Mimi faz: Miau!
  mimi.dormir();    // Mimi está dormindo...
}

2. INTERFACE - O Chefe que Só Dá Ordens 👔

Interfaces definem apenas contratos - elas dizem o que deve ser feito, mas não como fazer.

Características:

  • Todos os membros são públicos
  • Não podem ser estendidas, apenas implementadas
  • Não podem ter implementação de métodos
  • Forçam a implementação de todos os métodos
// Definindo uma interface
interface class Veiculo {
  double velocidade = 0;
  
  void acelerar();
  void frear();
  void ligar();
}

// Implementando a interface
class Carro implements Veiculo {
  @override
  double velocidade = 0;
  
  @override
  void acelerar() {
    velocidade += 10;
    print('Carro acelerando: ${velocidade}km/h');
  }
  
  @override
  void frear() {
    velocidade = velocidade > 0 ? velocidade - 10 : 0;
    print('Carro freando: ${velocidade}km/h');
  }
  
  @override
  void ligar() {
    print('Carro ligado!');
  }
}

class Moto implements Veiculo {
  @override
  double velocidade = 0;
  
  @override
  void acelerar() {
    velocidade += 15;
    print('Moto acelerando: ${velocidade}km/h');
  }
  
  @override
  void frear() {
    velocidade = velocidade > 0 ? velocidade - 15 : 0;
    print('Moto freando: ${velocidade}km/h');
  }
  
  @override
  void ligar() {
    print('Moto ligada!');
  }
}

void main() {
  Carro civic = Carro();
  Moto ninja = Moto();
  
  civic.ligar();     // Carro ligado!
  civic.acelerar();  // Carro acelerando: 10km/h
  
  ninja.ligar();     // Moto ligada!
  ninja.acelerar();  // Moto acelerando: 15km/h
}

3. ABSTRACT INTERFACE - O Chefe Supremo do Nada 👑

Combina o poder do abstract com interface - define regras sem fazer nada.

// Combinando abstract e interface
abstract interface class Forma {
  double calcularArea();
  double calcularPerimetro();
  void desenhar();
}

class Retangulo implements Forma {
  double largura;
  double altura;
  
  Retangulo(this.largura, this.altura);
  
  @override
  double calcularArea() => largura * altura;
  
  @override
  double calcularPerimetro() => 2 * (largura + altura);
  
  @override
  void desenhar() {
    print('Desenhando retângulo ${largura}x${altura}');
  }
}

class Circulo implements Forma {
  double raio;
  
  Circulo(this.raio);
  
  @override
  double calcularArea() => 3.14159 * raio * raio;
  
  @override
  double calcularPerimetro() => 2 * 3.14159 * raio;
  
  @override
  void desenhar() {
    print('Desenhando círculo com raio $raio');
  }
}

void main() {
  // Forma forma = Forma(); // ❌ ERRO! Não pode instanciar
  
  Retangulo ret = Retangulo(5, 3);
  Circulo circ = Circulo(4);
  
  print('Área do retângulo: ${ret.calcularArea()}'); // 15.0
  print('Área do círculo: ${circ.calcularArea()}');  // ~50.27
  
  ret.desenhar();  // Desenhando retângulo 5x3
  circ.desenhar(); // Desenhando círculo com raio 4
}

4. BASE - O Pai Superprotetor 🛡️

Classes base controlam rigorosamente como podem ser herdadas, mantendo o controle familiar.

Características:

  • Subclasses devem chamar o construtor da classe base
  • Só pode ser estendida dentro da mesma biblioteca
  • Membros privados ficam protegidos
  • Subclasses devem ser base, final, ou sealed
// arquivo: veiculo_base.dart
base class VeiculoBase {
  String _marca;
  String _modelo;
  double _velocidade = 0;
  
  VeiculoBase(this._marca, this._modelo);
  
  // Método protegido que subclasses podem usar
  void _acelerar(double incremento) {
    _velocidade += incremento;
    print('$_marca $_modelo acelerando: ${_velocidade}km/h');
  }
  
  void acelerar() => _acelerar(10);
  
  String get info => '$_marca $_modelo';
  double get velocidade => _velocidade;
}

// Deve ser base, final ou sealed
final class CarroEsportivo extends VeiculoBase {
  CarroEsportivo(String marca, String modelo) : super(marca, modelo);
  
  @override
  void acelerar() => _acelerar(25); // Acelera mais rápido!
  
  void turbo() {
    _acelerar(50);
    print('TURBO ATIVADO! 🚀');
  }
}

base class Caminhao extends VeiculoBase {
  double capacidadeCarga;
  
  Caminhao(String marca, String modelo, this.capacidadeCarga) 
      : super(marca, modelo);
  
  @override
  void acelerar() => _acelerar(5); // Acelera mais devagar
  
  void carregarCarga(double peso) {
    print('Carregando ${peso}kg no ${info}');
  }
}

void main() {
  CarroEsportivo ferrari = CarroEsportivo('Ferrari', 'F430');
  Caminhao volvo = Caminhao('Volvo', 'FH16', 25000);
  
  ferrari.acelerar(); // Ferrari F430 acelerando: 25km/h
  ferrari.turbo();    // Ferrari F430 acelerando: 75km/h, TURBO ATIVADO! 🚀
  
  volvo.acelerar();   // Volvo FH16 acelerando: 5km/h
  volvo.carregarCarga(20000); // Carregando 20000kg no Volvo FH16
}

5. FINAL - Eu Sou o Chefe 🦸‍♂️

Classes final são os “lobos solitários” - funcionam perfeitamente, mas não permitem extensão externa.

Características:

  • Não podem ser estendidas fora da mesma biblioteca
  • Métodos não podem ser sobrescritos
  • Podem ser instanciadas normalmente
  • Combinam segurança de base com isolamento de sealed
// Uma classe final bem definida
final class Configuracao {
  static final Configuracao _instancia = Configuracao._interno();
  
  String _ambiente = 'desenvolvimento';
  Map<String, dynamic> _configuracoes = {};
  
  // Construtor privado para Singleton
  Configuracao._interno();
  
  // Factory constructor retorna sempre a mesma instância
  factory Configuracao() => _instancia;
  
  void definirAmbiente(String ambiente) {
    _ambiente = ambiente;
    print('Ambiente definido como: $_ambiente');
  }
  
  void adicionarConfiguracao(String chave, dynamic valor) {
    _configuracoes[chave] = valor;
    print('Configuração adicionada: $chave = $valor');
  }
  
  dynamic obterConfiguracao(String chave) {
    return _configuracoes[chave];
  }
  
  String get ambiente => _ambiente;
  Map<String, dynamic> get todasConfiguracoes => Map.from(_configuracoes);
}

// Classe final para representar um usuário
final class Usuario {
  final String nome;
  final String email;
  final DateTime dataCriacao;
  
  Usuario(this.nome, this.email) : dataCriacao = DateTime.now();
  
  void exibirInfo() {
    print('Usuário: $nome');
    print('Email: $email');
    print('Criado em: ${dataCriacao.day}/${dataCriacao.month}/${dataCriacao.year}');
  }
  
  // Não pode ser sobrescrito
  bool validarEmail() {
    return email.contains('@') && email.contains('.');
  }
}

void main() {
  // Testando Singleton
  Configuracao config1 = Configuracao();
  Configuracao config2 = Configuracao();
  
  print(identical(config1, config2)); // true - mesma instância
  
  config1.definirAmbiente('produção');
  config1.adicionarConfiguracao('debug', false);
  config1.adicionarConfiguracao('porta', 8080);
  
  print(config2.ambiente); // produção (mesma instância!)
  print(config2.obterConfiguracao('porta')); // 8080
  
  // Testando Usuario
  Usuario joao = Usuario('João Silva', 'joao@email.com');
  joao.exibirInfo();
  print('Email válido: ${joao.validarEmail()}'); // true
}

// ❌ ERRO! Não é possível estender uma classe final
// class UsuarioAdmin extends Usuario { ... }

6. SEALED - O Clube VIP 🕴️

Classes sealed criam um clube exclusivo onde apenas membros selecionados (da mesma biblioteca) podem participar.

Características:

  • Não podem ser instanciadas diretamente
  • Só podem ser estendidas na mesma biblioteca
  • Permitem switches exaustivos
  • Ideais para representar estados ou tipos limitados
// Definindo estados de uma requisição HTTP
sealed class EstadoRequisicao {}

final class Carregando extends EstadoRequisicao {
  final String mensagem;
  Carregando([this.mensagem = 'Carregando...']);
}

final class Sucesso extends EstadoRequisicao {
  final dynamic dados;
  Sucesso(this.dados);
}

final class Erro extends EstadoRequisicao {
  final String mensagem;
  final int? codigo;
  Erro(this.mensagem, [this.codigo]);
}

final class Vazio extends EstadoRequisicao {}

// Classe para gerenciar requisições
class GerenciadorRequisicao {
  EstadoRequisicao _estado = Vazio();
  
  EstadoRequisicao get estado => _estado;
  
  Future<void> buscarDados() async {
    _estado = Carregando('Buscando dados...');
    print('Estado: ${_obterDescricaoEstado()}');
    
    // Simula uma requisição
    await Future.delayed(Duration(seconds: 2));
    
    // Simula sucesso ou erro aleatoriamente
    if (DateTime.now().millisecond % 2 == 0) {
      _estado = Sucesso({'usuarios': ['João', 'Maria', 'Pedro']});
    } else {
      _estado = Erro('Falha na conexão com o servidor', 500);
    }
    
    print('Estado: ${_obterDescricaoEstado()}');
  }
  
  // Switch exaustivo - o compilador garante que todos os casos são cobertos
  String _obterDescricaoEstado() {
    return switch (_estado) {
      Carregando(mensagem: var msg) => 'Carregando: $msg',
      Sucesso(dados: var dados) => 'Sucesso: $dados',
      Erro(mensagem: var msg, codigo: var cod) => 'Erro $cod: $msg',
      Vazio() => 'Nenhuma requisição feita',
    };
  }
  
  void processarEstado() {
    switch (_estado) {
      case Carregando():
        print('🔄 Aguarde, processando...');
      case Sucesso(dados: var dados):
        print('✅ Dados recebidos: $dados');
      case Erro(mensagem: var msg):
        print('❌ Erro: $msg');
      case Vazio():
        print('⚪ Estado inicial');
    }
  }
}

void main() async {
  GerenciadorRequisicao gerenciador = GerenciadorRequisicao();
  
  gerenciador.processarEstado(); // ⚪ Estado inicial
  
  await gerenciador.buscarDados();
  gerenciador.processarEstado(); // ✅ ou ❌ dependendo do resultado
  
  // ❌ ERRO! Não é possível instanciar uma classe sealed
  // EstadoRequisicao estado = EstadoRequisicao();
}

7. MIXIN - O Superpoder Anexável 🦸‍♀️

Mixins são como habilidades especiais que podem ser “anexadas” a diferentes classes.

Características:

  • Não podem ser instanciados diretamente
  • Podem ser aplicados a múltiplas classes
  • Não podem ter construtores
  • Ideal para funcionalidades reutilizáveis
// Definindo mixins para habilidades
mixin Voador {
  double altitude = 0;
  
  void voar(double novaAltitude) {
    altitude = novaAltitude;
    print('Voando a ${altitude}m de altitude! ✈️');
  }
  
  void pousar() {
    altitude = 0;
    print('Pousando... aterrizou! 🛬');
  }
}

mixin Nadador {
  double profundidade = 0;
  
  void mergulhar(double novaProfundidade) {
    profundidade = novaProfundidade;
    print('Mergulhando a ${profundidade}m de profundidade! 🏊‍♂️');
  }
  
  void emergir() {
    profundidade = 0;
    print('Emergindo... na superfície! 🌊');
  }
}

mixin Corredor {
  double velocidadeCorrida = 0;
  
  void correr(double velocidade) {
    velocidadeCorrida = velocidade;
    print('Correndo a ${velocidade}km/h! 🏃‍♂️');
  }
  
  void parar() {
    velocidadeCorrida = 0;
    print('Parando de correr... 🛑');
  }
}

// Classes base
class Animal {
  String nome;
  Animal(this.nome);
}

// Aplicando mixins a diferentes animais
class Pato extends Animal with Voador, Nadador, Corredor {
  Pato(String nome) : super(nome);
  
  void apresentar() {
    print('Sou $nome, um pato versátil! 🦆');
  }
}

class Peixe extends Animal with Nadador {
  Peixe(String nome) : super(nome);
  
  void apresentar() {
    print('Sou $nome, um peixe! 🐟');
  }
}

class Passaro extends Animal with Voador {
  Passaro(String nome) : super(nome);
  
  void apresentar() {
    print('Sou $nome, um pássaro! 🐦');
  }
}

class Cachorro extends Animal with Corredor, Nadador {
  Cachorro(String nome) : super(nome);
  
  void apresentar() {
    print('Sou $nome, um cachorro! 🐕');
  }
  
  void latir() {
    print('$nome: Au au! 🐕');
  }
}

void main() {
  Pato donald = Pato('Donald');
  Peixe nemo = Peixe('Nemo');
  Passaro tweety = Passaro('Tweety');
  Cachorro rex = Cachorro('Rex');
  
  // Donald pode fazer tudo!
  donald.apresentar();
  donald.voar(100);
  donald.mergulhar(5);
  donald.correr(15);
  
  print('---');
  
  // Nemo só pode nadar
  nemo.apresentar();
  nemo.mergulhar(50);
  nemo.emergir();
  
  print('---');
  
  // Tweety só pode voar
  tweety.apresentar();
  tweety.voar(200);
  tweety.pousar();
  
  print('---');
  
  // Rex pode correr e nadar
  rex.apresentar();
  rex.correr(30);
  rex.mergulhar(2);
  rex.latir();
}

8. MIXIN CLASS - O Melhor dos Dois Mundos 🌟

Mixin classes podem ser tanto estendidas quanto usadas como mixin.

Características:

  • Podem ser usadas como classe normal ou como mixin
  • Mais flexíveis que classes regulares
  • Não podem ter cláusulas extends, with, ou on
// Definindo uma mixin class
mixin class Identificavel {
  late String _id;
  DateTime _criadoEm = DateTime.now();
  
  void gerarId() {
    _id = 'ID_${DateTime.now().millisecondsSinceEpoch}';
    print('ID gerado: $_id');
  }
  
  String get id => _id;
  DateTime get criadoEm => _criadoEm;
  
  void exibirInfo() {
    print('ID: $_id');
    print('Criado em: ${_criadoEm.toString().split('.')[0]}');
  }
}

// Usando como classe base (herança)
class Usuario extends Identificavel {
  String nome;
  String email;
  
  Usuario(this.nome, this.email) {
    gerarId();
  }
  
  @override
  void exibirInfo() {
    super.exibirInfo();
    print('Nome: $nome');
    print('Email: $email');
  }
}

// Usando como mixin
class Produto with Identificavel {
  String nome;
  double preco;
  
  Produto(this.nome, this.preco) {
    gerarId();
  }
  
  void exibirDetalhes() {
    print('=== PRODUTO ===');
    exibirInfo();
    print('Nome: $nome');
    print('Preço: R\$ ${preco.toStringAsFixed(2)}');
  }
}

// Classe que usa múltiplos mixins incluindo nossa mixin class
class Pedido with Identificavel {
  List<Produto> produtos = [];
  String status = 'Pendente';
  
  Pedido() {
    gerarId();
  }
  
  void adicionarProduto(Produto produto) {
    produtos.add(produto);
    print('Produto ${produto.nome} adicionado ao pedido $_id');
  }
  
  double get total => produtos.fold(0, (sum, produto) => sum + produto.preco);
  
  void exibirResumo() {
    print('=== PEDIDO ===');
    exibirInfo();
    print('Status: $status');
    print('Produtos: ${produtos.length}');
    print('Total: R\$ ${total.toStringAsFixed(2)}');
    
    for (var produto in produtos) {
      print('  - ${produto.nome}: R\$ ${produto.preco.toStringAsFixed(2)}');
    }
  }
  
  void finalizarPedido() {
    status = 'Finalizado';
    print('Pedido $_id finalizado! 🎉');
  }
}

void main() {
  // Usando como herança
  Usuario joao = Usuario('João Silva', 'joao@email.com');
  print('=== USUÁRIO ===');
  joao.exibirInfo();
  
  print('\n');
  
  // Usando como mixin
  Produto notebook = Produto('Notebook Dell', 2500.99);
  Produto mouse = Produto('Mouse Logitech', 89.90);
  
  notebook.exibirDetalhes();
  print('\n');
  
  // Combinando tudo em um pedido
  Pedido pedido = Pedido();
  pedido.adicionarProduto(notebook);
  pedido.adicionarProduto(mouse);
  
  print('\n');
  pedido.exibirResumo();
  
  print('\n');
  pedido.finalizarPedido();
}

Quando Usar Cada Modificador?

🎯 ABSTRACT

  • Use quando: Quiser criar uma classe base com alguns métodos implementados e outros não
  • Exemplo: Classes base para formas geométricas, animais, veículos

🎯 INTERFACE

  • Use quando: Quiser definir apenas contratos sem implementação
  • Exemplo: Protocolos de comunicação, APIs, contratos de serviços

🎯 BASE

  • Use quando: Quiser controle rígido sobre herança e manter implementação segura
  • Exemplo: Classes de infraestrutura, bibliotecas internas

🎯 FINAL

  • Use quando: Quiser uma classe completa que não deve ser modificada
  • Exemplo: Classes utilitárias, DTOs, configurações

🎯 SEALED

  • Use quando: Quiser um conjunto limitado e conhecido de subtipos
  • Exemplo: Estados, tipos de resultado, enums complexos

🎯 MIXIN

  • Use quando: Quiser funcionalidade reutilizável sem herança
  • Exemplo: Habilidades compartilhadas, funcionalidades transversais

🎯 MIXIN CLASS

  • Use quando: Quiser flexibilidade máxima (herança + mixin)
  • Exemplo: Classes utilitárias que podem ser base ou complemento

Resumo das Regras de Combinação

ModificadorPode ser instanciado?Pode ser estendido?Pode ser implementado?Pode ser usado como mixin?
abstract✅ (mesma lib)
interface
base✅ (mesma lib)
final
sealed✅ (mesma lib)
mixin
mixin class

Conclusão

Os modificadores de classe em Dart são ferramentas poderosas que ajudam você a:

  1. Controlar a arquitetura do seu código
  2. Definir contratos claros entre componentes
  3. Prevenir uso indevido das suas classes
  4. Criar código mais seguro e maintível
  5. Expressar intenções de design claramente

Escolha o modificador certo baseado no que você quer alcançar:

  • Segurança: final, base, sealed
  • Flexibilidade: mixin, mixin class
  • Contratos: abstract, interface

Lembre-se: um bom desenvolvedor não apenas escreve código que funciona, mas código que comunica sua intenção claramente. Os modificadores de classe são suas ferramentas para isso! 🚀


“Code is read much more often than it is written.” - Guido van Rossum

Use os modificadores de classe para tornar seu código não apenas funcional, mas também expressivo e seguro! 💪

✎ Morpa