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á
- Definição de classe, declaração de propriedades e métodos
- Construtores: Padrão / Nomeado / Factory / Redirecionador / Constante
- Herança e sobrescrita de métodos (
@override) - Classes abstratas e interfaces (interface implícita)
- Cenário do Bob: Classes de modelo Order / Product / Customer no DataPipeline
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.
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;
}
> 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
- Erros de tipo movidos do runtime para o tempo de compilação, reduzindo bugs em 80%.
- Construtores fornecem validação unificada de dados, eliminando verificações perdidas.
- Herança e interfaces aumentaram a taxa de reutilização de código em 60%.
3. Noções Básicas de Definição de Classe
(1) Estrutura de uma Classe
▶ Exemplo
> 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
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
}
> 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
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
> 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
> 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
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)';
}
> 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
> 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
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}');
}
> 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
> 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
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);
}
> 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
> 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
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
}
> 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
> 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
// 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
}
> 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
> 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
// 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');
}
}
> 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
> 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
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;
}
> 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
// ============================================
// 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)');
}
> 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:
=== 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 comimplements, ou misturar múltiplos mixins comwith.
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 construtorconstcria um objeto constante em tempo de compilação. Construtoresconstcom 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 classeinterface? R: Useabstract classcomextendsquando precisar compartilhar código de implementação. Useinterface(viaimplements) quando só precisar definir um contrato. O Dart não tem palavra-chaveinterfacededicada; toda classe em si é uma interface.
P: Quando devo usar
@override? R: Você deve adicionar@overrideao 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
assertdo Dart é efetivo em produção? R: Não.assertsó é 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
- Definição de classe, incluindo campos, construtores, métodos, getters/setters, é o núcleo dos modelos de dados.
- Os cinco tipos de construtores têm usos diferentes: nomeado (múltiplos métodos de criação), factory (caching/subclasses), redirecionador (conveniência), constante (imutabilidade).
- Herança simples + implementação de múltiplas interfaces + composição de múltiplos mixins permitem combinação flexível.
- Classes abstratas fornecem implementação compartilhada, enquanto interfaces implícitas definem contratos.
- Order/Product/Customer do DataPipeline formam o modelo de domínio, suportando a lógica de negócios.
📝 Exercícios
- Básico (Dificuldade ⭐): Defina uma classe
Productcom camposname,price,category. Adicione um métodoformatUSD()e um getterisExpensive. Crie 3 instâncias de produto e imprima-as. - Intermediário (Dificuldade ⭐⭐): Adicione um construtor nomeado
Order.fromMap(criar a partir de um Map) e um construtor factoryOrder.safe(retornar um pedido padrão se o parsing falhar) à classeOrder. Teste casos extremos. - Desafio (Dificuldade ⭐⭐⭐): Projete uma classe abstrata
ReportGenerator. Crie duas subclasses,JsonReporteCsvReport, para implementar geração de relatórios polimórfica. Depois crie umMockReportqueimplementsa mesma interface para testes.