Dart: Classes e Objetos no Dart

Última atualização: 2026-08-26

Uma classe é um blueprint para objetos — um bom blueprint faz bons objetos, e bons objetos constroem bons sistemas.

1. O que Você Aprenderá


2. A História Real de um Desenvolvedor

(1) Dor: Lógica Dispersa Devido a Modelos de Dados Caóticos

Nos primeiros dias do DataPipeline do Bob, todos os dados eram tratados usando Maps, sem segurança de tipo. order['amout'] (um erro de digitação) retornava null em tempo de execução em vez de lançar um erro, fazendo com que as estatísticas de valor de 100.000 pedidos ficassem completamente zeradas. Pior ainda, a lógica para criar pedidos estava espalhada por 5 arquivos, sem validação unificada.

(2) Solução com Classes

Usando classes para definir modelos de dados, construtores para centralizar a lógica de validação, e o sistema de tipos para capturar erros de digitação e incompatibilidades de tipo em tempo de compilação.

DART
class Order {
  final String id;
  final double amount;
  final DateTime date;

  Order({required this.id, required this.amount, required this.date})
      : assert(amount > 0, 'Amount must be positive');

  double calcTax(double rate) => amount * rate;
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

(3) Benefícios


3. Básicos de Definição de Classe

(1) Estrutura de uma Classe

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Definição Básica de Classe

DART
class Product {
  // Campos
  final String name;
  final double price;
  String category;

  // Construtor
  Product({required this.name, required this.price, this.category = 'General'});

  // Método
  String formatPrice() => '\$${price.toStringAsFixed(2)} USD';

  // Getter
  bool get isExpensive => price >= 1000;

  // Sobrescrever toString
  @override
  String toString() => 'Product($name, ${formatPrice()}, $category)';
}

void main() {
  final product = Product(name: 'Laptop', price: 1299.99, category: 'Electronics');
  print(product);                  // Product(Laptop, $1299.99 USD, Electronics)
  print(product.isExpensive);      // true
  print(product.formatPrice());    // $1299.99 USD
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.
Membro da Classe Sintaxe de Declaração Descrição
Campo Type name Variável de instância
Construtor ClassName() Cria um objeto
Método returnType name() {} Método de instância
Getter Type get name => expr Propriedade computada
Setter set name(Type value) Interceptação de atribuição

4. Cinco Tipos de Construtores

(1) Modelo de Domínio do DataPipeline

100%
classDiagram
  class Order {
    +String id
    +double amount
    +DateTime date
    +calcTax(double rate) double
  }
  class Product {
    +String name
    +double price
    +formatUSD() String
  }
  class Customer {
    +String name
    +List~Order~ orders
  }
  Customer "1" --> "*" Order : has
  Order "1" --> "*" Product : contains
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Construtor Padrão e Construtores Nomeados

DART
class Order {
  final String id;
  final double amount;
  final DateTime date;
  String status;

  // Construtor padrão
  Order({required this.id, required this.amount, required this.date, this.status = 'pending'});

  // Construtor nomeado - criar a partir de linha CSV
  Order.fromCsv(String csvLine)
      : id = csvLine.split(',')[0],
        amount = double.parse(csvLine.split(',')[1]),
        date = DateTime.parse(csvLine.split(',')[2]),
        status = 'pending';

  // Construtor nomeado - criar com data padrão
  Order.now({required this.id, required this.amount})
      : date = DateTime.now(),
        status = 'pending';

  double calcTax(double rate) => amount * rate;

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status)';
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Construtor Factory

DART
class DataSource {
  final String type;
  final String connection;

  // Construtor privado
  DataSource._internal(this.type, this.connection);

  // Construtor factory - retorna instância personalizada
  factory DataSource.create({required String type, required String connection}) {
    return DataSource._internal(type, connection);
  }

  // Factory com configurações padrão
  factory DataSource.api(String endpoint) =>
      DataSource._internal('api', endpoint);

  factory DataSource.file(String path) =>
      DataSource._internal('file', path);

  factory DataSource.database(String connectionString) =>
      DataSource._internal('database', connectionString);
}

void main() {
  final api = DataSource.api('https://api.example.com/orders');
  final file = DataSource.file('/data/orders.csv');
  print('${api.type}: ${api.connection}');
  print('${file.type}: ${file.connection}');
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Construtor Redirecionador

DART
class Report {
  final String title;
  final String format;
  final int recordCount;

  // Construtor principal
  Report({required this.title, this.format = 'json', this.recordCount = 0});

  // Construtores redirecionadores
  Report.json(String title, int count) : this(title: title, format: 'json', recordCount: count);
  Report.csv(String title, int count) : this(title: title, format: 'csv', recordCount: count);
  Report.html(String title, int count) : this(title: title, format: 'html', recordCount: count);
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Construtor Constante

DART
class Config {
  final String appName;
  final int maxRecords;

  // construtor const - todos os campos devem ser final
  const Config({this.appName = 'DataPipeline', this.maxRecords = 1000000});
}

void main() {
  const config1 = Config();                              // Constante em tempo de compilação
  const config2 = Config(maxRecords: 500000);
  print(identical(config1, config2));  // false (valores diferentes)
  print(config1.maxRecords);           // 1000000
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.
Tipo de Construtor Sintaxe Características Cenários
Padrão ClassName() Auto-gerado ou personalizado Criação padrão
Nomeado ClassName.name() Múltiplos métodos de criação fromCsv, now
Factory factory ClassName() Pode retornar subclasses ou instâncias em cache Singleton, caching
Redirecionador : this() Delega ao construtor principal Criação conveniente
Constante const ClassName() Criado em tempo de compilação, imutável Constantes de configuração

5. Herança e Sobrescrita de Métodos

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Hierarquia de Herança

DART
// Classe base
class DataProcessor {
  final String name;

  DataProcessor(this.name);

  // Método a ser sobrescrito
  String process(String input) => 'Processing $input with $name';

  // Método não-sobrescrevível
  String getStatus() => 'Ready';

  @override
  String toString() => '$name Processor';
}

// Subclasse
class OrderProcessor extends DataProcessor {
  final double taxRate;

  OrderProcessor(this.taxRate) : super('Order');

  @override
  String process(String input) {
    final amount = double.tryParse(input) ?? 0;
    final taxed = amount * (1 + taxRate);
    return 'Order processed: \$${taxed.toStringAsFixed(2)} USD';
  }
}

// Outra subclasse
class ProductProcessor extends DataProcessor {
  ProductProcessor() : super('Product');

  @override
  String process(String input) => 'Product cataloged: $input';
}

void main() {
  final orderProc = OrderProcessor(0.08);
  print(orderProc.process('1500'));     // Order processed: $1620.00 USD
  print(orderProc.getStatus());         // Ready

  final productProc = ProductProcessor();
  print(productProc.process('Laptop')); // Product cataloged: Laptop
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

6. Classes Abstratas e Interfaces

(1) Classe Abstrata

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Classe Abstrata e Implementação

DART
// Classe abstrata - não pode ser instanciada
abstract class ReportGenerator {
  final String format;

  ReportGenerator(this.format);

  // Método abstrato - deve ser implementado
  String generate(List<Map<String, dynamic>> data);

  // Método concreto - implementação compartilhada
  String header(String title) => '=== $title ($format) ===';
}

class JsonReportGenerator extends ReportGenerator {
  JsonReportGenerator() : super('json');

  @override
  String generate(List<Map<String, dynamic>> data) {
    final buffer = StringBuffer();
    buffer.writeln(header('DataPipeline Report'));
    for (final entry in data) {
      buffer.writeln('  ${entry['id']}: ${entry['amount']}');
    }
    return buffer.toString();
  }
}

class CsvReportGenerator extends ReportGenerator {
  CsvReportGenerator() : super('csv');

  @override
  String generate(List<Map<String, dynamic>> data) {
    final lines = <String>['id,amount'];
    for (final entry in data) {
      lines.add('${entry['id']},${entry['amount']}');
    }
    return lines.join('\n');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

(2) Interface Implícita

Toda classe no Dart define implicitamente uma interface, contendo todos os seus métodos de instância e campos. Outras classes podem implements esta interface.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

: Implementando uma Interface Implícita

DART
class FileDataSource {
  final String path;
  FileDataSource(this.path);

  List<String> readLines() => ['line1', 'line2'];
  bool get exists => true;
}

// Implementar a interface implícita de FileDataSource
class MockDataSource implements FileDataSource {
  @override
  String path = '/mock/data.csv';

  @override
  List<String> readLines() => ['mock1', 'mock2', 'mock3'];

  @override
  bool get exists => true;
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.
Aspecto classe abstrata interface (implements) mixin
Instanciação Não Não
Implementação de Método Opcional Deve re-implementar tudo Opcional
Herança Múltipla Herança única Pode implementar múltiplas Pode misturar múltiplas
Construtor Sim Não Não

7. Exemplo Completo: Modelo de Domínio do DataPipeline

DART
// ============================================
// Modelo de Domínio do DataPipeline
// Order, Product, Customer com OOP completo
// ============================================

class Product {
  final String id;
  final String name;
  final double price;
  final String category;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    this.category = 'General',
  });

  String formatUSD() => '\$${price.toStringAsFixed(2)} USD';

  @override
  String toString() => 'Product($name, ${formatUSD()})';
}

class Order {
  final String id;
  final double amount;
  final DateTime date;
  String status;
  final List<Product> products;

  Order({
    required this.id,
    required this.amount,
    required this.date,
    this.status = 'pending',
    this.products = const [],
  }) : assert(amount > 0, 'Amount must be positive');

  Order.now({required this.id, required this.amount, this.products = const []})
      : date = DateTime.now(),
        status = 'pending';

  double calcTax(double rate) => amount * rate;
  double calcTotal(double taxRate) => amount + calcTax(taxRate);

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status, ${products.length} items)';
}

class Customer {
  final String name;
  final String email;
  final List<Order> orders;

  Customer({required this.name, required this.email, List<Order>? orders})
      : orders = orders ?? [];

  double get totalSpent => orders.fold(0.0, (sum, o) => sum + o.amount);
  int get orderCount => orders.length;
  double get averageOrderValue => orderCount > 0 ? totalSpent / orderCount : 0;

  void addOrder(Order order) => orders.add(order);

  String tier() => switch (totalSpent) {
    >= 50000 => 'Enterprise',
    >= 10000 => 'Premium',
    >= 1000 => 'Standard',
    _ => 'Free',
  };

  @override
  String toString() => 'Customer($name, ${tier()}, ${orderCount} pedidos, \$${totalSpent.toStringAsFixed(2)} total)';
}

void main() {
  final laptop = Product(id: 'P001', name: 'Laptop', price: 1299.99, category: 'Electronics');
  final mouse = Product(id: 'P002', name: 'Mouse', price: 29.99, category: 'Electronics');

  final alice = Customer(name: 'Alice', email: 'alice@example.com');
  final bob = Customer(name: 'Bob', email: 'bob@example.com');

  alice.addOrder(Order(id: 'ORD-001', amount: 1500.0, date: DateTime(2024, 1, 15), products: [laptop]));
  alice.addOrder(Order(id: 'ORD-002', amount: 3200.0, date: DateTime(2024, 2, 20)));
  bob.addOrder(Order(id: 'ORD-003', amount: 890.0, date: DateTime(2024, 3, 10), products: [mouse]));

  print('=== Clientes do DataPipeline ===');
  print(alice);
  print(bob);

  print('\n--- Pedidos da Alice ---');
  for (final order in alice.orders) {
    print('  $order (imposto: \$${order.calcTax(0.08).toStringAsFixed(2)} USD)');
  }

  print('\nResumo de Níveis:');
  print('  Alice: ${alice.tier()} (\$${alice.totalSpent.toStringAsFixed(2)} total)');
  print('  Bob:   ${bob.tier()} (\$${bob.totalSpent.toStringAsFixed(2)} total)');
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente com diferentes versões do SDK.

Saída:

TEXT 📖 Somente leitura
=== Clientes do DataPipeline ===
Customer(Alice, Premium, 2 pedidos, $4700.00 total)
Customer(Bob, Standard, 1 pedidos, $890.00 total)

--- Pedidos da Alice ---
  Order(ORD-001, $1500.00, pending, 1 items) (imposto: $120.00 USD)
  Order(ORD-002, $3200.00, pending, 0 items) (imposto: $256.00 USD)

Resumo de Níveis:
  Alice: Premium ($4700.00 total)
  Bob:   Standard ($890.00 total)

❓ Perguntas Frequentes

P: O Dart tem herança múltipla? R: Não. O Dart só suporta herança simples (extends), mas você pode implementar múltiplas interfaces com implements, ou misturar múltiplos mixins com with.

P: Qual a diferença entre um construtor factory e um construtor regular? R: Um construtor regular sempre cria uma nova instância. Um construtor factory pode retornar uma instância em cache, uma instância de subclasse, ou mesmo executar lógica complexa antes de decidir o que retornar. É declarado com a palavra-chave factory.

P: Para que serve um construtor const? R: Um construtor const cria um objeto constante em tempo de compilação. Construtores const com os mesmos argumentos retornarão a mesma instância (identical). É adequado para objetos imutáveis como classes de configuração ou valores de enum.

P: Como escolher entre abstract class e interface? R: Use abstract class com extends quando precisar compartilhar código de implementação. Use interface (via implements) quando só precisa definir um contrato. O Dart não tem palavra-chave interface dedicada; toda classe em si é uma interface.

P: Quando devo usar @override? R: Você deve adicionar @override ao sobrescrever um método da classe pai. Embora não seja estritamente obrigatório, adicioná-lo permite que o compilador ajude a verificar se você está sobrescrevendo corretamente (ele reportará um erro se a classe pai não tiver esse método).

P: O assert do Dart é eficaz em produção? R: Não. assert só é executado em modo debug e é ignorado em modo release. É usado para validação em tempo de desenvolvimento e não pode substituir a validação adequada de parâmetros.

P: Um construtor nomeado pode ser factory? R: Sim. Por exemplo, factory Order.fromCsv(String line) é válido. Ele pode retornar uma instância padrão em vez de lançar uma exceção se o parsing falhar.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Defina uma classe Product com campos name, price, category. Adicione um método formatUSD() e um getter isExpensive. Crie 3 instâncias de produto e imprima-as.
  2. Intermediário (Dificuldade ⭐⭐): Adicione um construtor nomeado Order.fromMap (criar a partir de um Map) e um construtor factory Order.safe (retornar um pedido padrão se o parsing falhar) à classe Order. Teste casos extremos.
  3. Desafio (Dificuldade ⭐⭐⭐): Design uma classe abstrata ReportGenerator. Crie duas subclasses, JsonReport e CsvReport, para implementar geração de relatórios polimórfica. Depois crie um MockReport que implements a mesma interface para testes.

← Aula Anterior | Próxima Aula →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%