Classes e Objetos no Dart — Núcleo da Orientação a Objetos

Uma classe é uma planta para objetos — uma boa planta 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

No início 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 valores de 100.000 pedidos fossem completamente zero. Pior ainda, a lógica de criação de 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
> Saída: Execute localmente em 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. Noções Básicas de Definição de Classe

(1) Estrutura de uma Classe

▶ Exemplo

TEXT
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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 ou em cache
  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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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() Gerado automaticamente ou customizado 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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
> Saída: Execute localmente em 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 simples 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} orders, \$${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('=== DataPipeline Customers ===');
  print(alice);
  print(bob);

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

  print('\nTier Summary:');
  print('  Alice: ${alice.tier()} (\$${alice.totalSpent.toStringAsFixed(2)} total)');
  print('  Bob:   ${bob.tier()} (\$${bob.totalSpent.toStringAsFixed(2)} total)');
}
TEXT
> Saída: Execute localmente em 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
=== DataPipeline Customers ===
Customer(Alice, Premium, 2 orders, $4700.00 total)
Customer(Bob, Standard, 1 orders, $890.00 total)

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

Tier Summary:
  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 até 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ó precisar 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 (reportará um erro se a classe pai não tiver aquele método).

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

P: Um construtor nomeado pode ser factory? R: Sim. Por exemplo, factory Order.fromCsv(String line) é válido. 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 ⭐⭐⭐): Projete 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%