Dart: Enums e Métodos de Extensão em Dart — Enhanced Enums
Última atualização: 2026-08-26
Enums dão nomes a estados finitos, extensões dão novas capacidades a tipos antigos — ambos são ferramentas poderosas para aprimorar o código sem modificar sua fonte.
1. O que Você Aprenderá
- Enhanced Enums: propriedades, construtores, métodos
- Enums com
switch - Extension Methods: definição e uso
- Extensões e privacidade, resolução de conflitos de nomenclatura
- Cenário do Bob: enum
OrderStatus+ extensão de String (formatação de valores em USD)
2. A História Real de um Desenvolvedor
(1) O Problema: Usar Strings para Simular Estados Gera Erros de Digitação
Alice usou strings para representar status de pedidos em seu código: 'pending', 'shipped', 'delivered'. Um erro de digitação 'shiped' não foi detectado pelo compilador, fazendo com que o pedido permanecesse preso no status "não enviado", resultando em 200 reclamações de clientes. Ela também frequentemente escrevia verificações como if (status == 'pending' || status == 'processing'), que eram fáceis de esquecer.
(2) A Solução com Enums
Os Enhanced Enums do Dart dão a cada estado um nome tipo-seguro, junto com propriedades e métodos anexados. Expressões switch garantem exaustividade; omitir um estado causa um erro do compilador.
enum OrderStatus {
pending(label: 'Awaiting Processing', isFinal: false),
shipped(label: 'In Transit', isFinal: false),
delivered(label: 'Completed', isFinal: true),
cancelled(label: 'Cancelled', isFinal: true);
final String label;
final bool isFinal;
const OrderStatus({required this.label, required this.isFinal});
}
// Switch exaustivo - compilador verifica todos os casos
String handle(OrderStatus status) => switch (status) {
OrderStatus.pending => 'Queue for processing',
OrderStatus.shipped => 'Track shipment',
OrderStatus.delivered => 'Send survey',
OrderStatus.cancelled => 'Process refund',
};
> **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 dependendo da versão do SDK.
(3) Benefícios
- Erros de digitação são detectados em tempo de compilação em vez de runtime, reduzindo bugs relacionados a estados em 90%
- Switch exaustivo garante que nenhum estado seja esquecido
- Métodos de extensão permitem adicionar métodos de negócio a String e num sem subclasses
3. Enhanced Enums
(1) Enums Básicos
▶ Exemplo
> **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 dependendo da versão do SDK.
: Enum Simples
enum OutputFormat {
json,
csv,
html,
}
void main() {
final format = OutputFormat.json;
// Valores do enum
print(format.name); // json
print(format.index); // 0
print(OutputFormat.values); // [OutputFormat.json, OutputFormat.csv, OutputFormat.html]
// Analisar a partir de string
final parsed = OutputFormat.values.byName('csv');
print(parsed); // OutputFormat.csv
}
> **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 dependendo da versão do SDK.
(2) Enhanced Enums
▶ Exemplo
> **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 dependendo da versão do SDK.
: Enhanced Enum com Propriedades
enum OrderStatus {
pending(label: 'Awaiting Processing', isFinal: false, priority: 1),
processing(label: 'Being Processed', isFinal: false, priority: 2),
shipped(label: 'In Transit', isFinal: false, priority: 3),
delivered(label: 'Completed', isFinal: true, priority: 0),
cancelled(label: 'Cancelled', isFinal: true, priority: 0);
final String label;
final bool isFinal;
final int priority;
const OrderStatus({required this.label, required this.isFinal, required this.priority});
bool get isActive => !isFinal;
String get displayName => '${name.toUpperCase()} - $label';
}
void main() {
final status = OrderStatus.shipped;
print(status.label); // In Transit
print(status.isFinal); // false
print(status.isActive); // true
print(status.displayName); // SHIPPED - In Transit
}
> **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 dependendo da versão do SDK.
▶ Exemplo
> **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 dependendo da versão do SDK.
: Enhanced Enum com Métodos
enum TaxCategory {
standard(rate: 0.08, label: 'Standard Rate'),
reduced(rate: 0.05, label: 'Reduced Rate'),
zero(rate: 0.0, label: 'Zero Rate'),
exempt(rate: 0.0, label: 'Tax Exempt');
final double rate;
final String label;
const TaxCategory({required this.rate, required this.label});
double calculate(double amount) => amount * rate;
double applyTo(double amount) => amount * (1 + rate);
String formatRate() => '${(rate * 100).toStringAsFixed(1)}%';
}
void main() {
final tax = TaxCategory.standard;
print(tax.calculate(1500.0)); // 120.0
print(tax.applyTo(1500.0)); // 1620.0
print(tax.formatRate()); // 8.0%
// Todas as categorias
for (final cat in TaxCategory.values) {
print('${cat.label}: ${cat.formatRate()}');
}
}
> **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 dependendo da versão do SDK.
4. Enums com Switch
▶ Exemplo
> **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 dependendo da versão do SDK.
: Switch Exaustivo
enum DataSourceType {
api,
file,
database,
}
String describeSource(DataSourceType type) => switch (type) {
DataSourceType.api => 'REST API endpoint',
DataSourceType.file => 'Local file system',
DataSourceType.database => 'SQL database connection',
};
// Com verificação exaustiva - compilador força todos os casos
bool canRetry(DataSourceType type) => switch (type) {
DataSourceType.api => true, // API pode tentar novamente
DataSourceType.file => false, // Erros de arquivo precisam de correção manual
DataSourceType.database => true, // DB pode tentar novamente com backoff
};
void main() {
print(describeSource(DataSourceType.api)); // REST API endpoint
print(canRetry(DataSourceType.file)); // false
}
> **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 dependendo da versão do SDK.
| Recurso | if-else | switch statement | switch expression |
|---|---|---|---|
| Verificação de exaustividade | Não | Não | Sim (para enums) |
| Garantia em tempo de compilação | Não | Não | Sim |
| Ao adicionar novo valor de enum | Pode ser esquecido | Pode ser esquecido | Erro de compilação |
5. Extension Methods
(1) Extensões Básicas
▶ Exemplo
> **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 dependendo da versão do SDK.
: Extensão de String
extension StringCurrency on String {
String toUSD() => '\$$this USD';
String toEUR() => '€${this} EUR';
String truncate(int maxLength) =>
length <= maxLength ? this : '${substring(0, maxLength)}...';
String get capitalized =>
isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}
void main() {
print('1500.00'.toUSD()); // $1500.00 USD
print('1200.00'.toEUR()); // €1200.00 EUR
print('Very long product name'.truncate(10)); // Very long...
print('electronics'.capitalized); // Electronics
}
> **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 dependendo da versão do SDK.
▶ Exemplo
> **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 dependendo da versão do SDK.
: Extensão de num (Formatação de Valores)
extension NumFormatting on num {
String toUSD() => '\$${toStringAsFixed(2)} USD';
String toCompact() {
if (this >= 1000000) return '\$${(this / 1000000).toStringAsFixed(1)}M USD';
if (this >= 1000) return '\$${(this / 1000).toStringAsFixed(1)}K USD';
return toUSD();
}
double get asK => this / 1000;
double get asM => this / 1000000;
bool isBetween(num from, num to) => from <= this && this <= to;
}
void main() {
print(1500.0.toUSD()); // $1500.00 USD
print(1500000.0.toCompact()); // $1.5M USD
print(5000.asK); // 5.0
print(1500.0.isBetween(1000, 2000)); // true
}
> **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 dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
: Extensão de List
extension ListStats on List``<double>`` {
double get sum => fold(0, (a, b) => a + b);
double get average => isEmpty ? 0 : sum / length;
double get median {
final sorted = [...this]..sort();
final mid = length ~/ 2;
return length.isEven
? (sorted[mid - 1] + sorted[mid]) / 2
: sorted[mid];
}
}
void main() {
final amounts = [1500.0, 3200.0, 890.0, 50.0];
print('Sum: ${amounts.sum.toUSD()}'); // Sum: $5640.00 USD
print('Average: ${amounts.average.toUSD()}'); // Average: $1410.00 USD
print('Median: ${amounts.median.toUSD()}'); // Median: $1195.00 USD
}
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
6. Extensões, Privacidade e Conflitos de Nomenclatura
(1) Resolução de Conflitos de Nomenclatura
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
: Resolução de Conflito via Namespaces
extension MathExtras on num {
int get squared => (this * this).toInt();
}
extension StringExtras on String {
String get reversed => split('').reversed.join('');
}
// Se duas extensões têm o mesmo nome de método
extension DoubleExtras on double {
String toMoney() => '\$${toStringAsFixed(2)}';
}
extension IntExtras on int {
String toMoney() => '\$${this}.00';
}
void main() {
// Chamada direta - compilador resolve por tipo
print(5.squared); // 25
print('hello'.reversed); // olleh
// Resolução explícita quando ambíguo
print(DoubleExtras(1500.5).toMoney()); // $1500.50
print(IntExtras(1500).toMoney()); // $1500.00
}
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
| Cenário de Conflito | Resolução |
|---|---|
| Duas extensões definem o mesmo nome de método | Chamar explicitamente via ExtensionName(obj).method() |
| Método de extensão e método da classe com mesmo nome | Método da classe tem prioridade, método de extensão é oculto |
| Duas extensões em arquivos diferentes | Prioridade das extensões importadas depende da ordem de import |
7. Cenário do Bob: Enum OrderStatus + Extensão de String
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
: Enum e Extensão práticos para DataPipeline
// Enum de status do pedido com lógica de negócio
enum OrderStatus {
pending(label: 'Awaiting Processing', isFinal: false),
processing(label: 'Being Processed', isFinal: false),
shipped(label: 'In Transit', isFinal: false),
delivered(label: 'Completed', isFinal: true),
cancelled(label: 'Cancelled', isFinal: true),
refunded(label: 'Refunded', isFinal: true);
final String label;
final bool isFinal;
const OrderStatus({required this.label, required this.isFinal});
bool get isActive => !isFinal;
bool get canCancel => this == pending || this == processing;
bool get canRefund => this == delivered;
}
// Extensão de String para formatação do DataPipeline
extension DataPipelineString on String {
String get asOrderId => 'ORD-$this';
String toUSD() => '\$$this USD';
String toCategoryLabel => split('_').map((w) => w.capitalizeFirst).join(' ');
}
extension StringCap on String {
String get capitalizeFirst =>
isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}
// Extensão de num para formatação de receita
extension RevenueFormatting on num {
String toRevenue() => '\$${toStringAsFixed(2)} USD';
String toCompactRevenue() {
if (this >= 1000000) return '\$${(this / 1000000).toStringAsFixed(1)}M USD';
if (this >= 1000) return '\$${(this / 1000).toStringAsFixed(1)}K USD';
return toRevenue();
}
}
void main() {
// Uso do enum
final status = OrderStatus.shipped;
print('Status: ${status.label}'); // In Transit
print('Active: ${status.isActive}'); // true
print('Can cancel: ${status.canCancel}'); // false
// Extensões de String
print('001'.asOrderId); // ORD-001
print('1500.00'.toUSD()); // $1500.00 USD
// Formatação de receita
print(1500000.toCompactRevenue()); // $1.5M USD
print(52500.75.toRevenue()); // $52500.75 USD
// Switch exaustivo no enum
for (final s in OrderStatus.values) {
final action = switch (s) {
OrderStatus.pending => 'Queue for processing',
OrderStatus.processing => 'Monitor progress',
OrderStatus.shipped => 'Track delivery',
OrderStatus.delivered => 'Send confirmation',
OrderStatus.cancelled => 'Process cancellation',
OrderStatus.refunded => 'Update records',
};
print(' ${s.name}: $action');
}
}
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
8. Exemplo Completo: Máquina de Estados de Pedidos do DataPipeline
// ============================================
// DataPipeline Order State Machine
// Enhanced enums + extensões em ação
// ============================================
enum OrderStatus {
pending(label: 'Awaiting Processing', isFinal: false, color: 'yellow'),
processing(label: 'Being Processed', isFinal: false, color: 'blue'),
shipped(label: 'In Transit', isFinal: false, color: 'orange'),
delivered(label: 'Completed', isFinal: true, color: 'green'),
cancelled(label: 'Cancelled', isFinal: true, color: 'red'),
refunded(label: 'Refunded', isFinal: true, color: 'gray');
final String label;
final bool isFinal;
final String color;
const OrderStatus({
required this.label,
required this.isFinal,
required this.color,
});
bool get isActive => !isFinal;
bool get canTransition => !isFinal;
List``<OrderStatus>`` get allowedTransitions => switch (this) {
pending => [processing, cancelled],
processing => [shipped, cancelled],
shipped => [delivered],
delivered => [refunded],
cancelled => [],
refunded => [],
};
bool canTransitionTo(OrderStatus target) =>
allowedTransitions.contains(target);
}
extension NumRevenue on num {
String toUSD() => '\$${toStringAsFixed(2)} USD';
}
class Order {
final String id;
final double amount;
OrderStatus status;
Order({required this.id, required this.amount, this.status = OrderStatus.pending});
bool transitionTo(OrderStatus newStatus) {
if (!status.canTransitionTo(newStatus)) {
print(' Cannot transition from ${status.name} to ${newStatus.name}');
return false;
}
print(' $id: ${status.name} → ${newStatus.name}');
status = newStatus;
return true;
}
String get summary => '$id: ${status.label} (${amount.toUSD()})';
}
void main() {
final order = Order(id: 'ORD-001', amount: 1500.0);
print('=== Order State Machine ===');
print('Initial: ${order.summary}');
// Transições válidas
order.transitionTo(OrderStatus.processing); // OK
order.transitionTo(OrderStatus.shipped); // OK
order.transitionTo(OrderStatus.delivered); // OK
// Transição inválida
order.transitionTo(OrderStatus.cancelled); // Cannot: delivered → cancelled
// Reembolso válido
order.transitionTo(OrderStatus.refunded); // OK
print('\nFinal: ${order.summary}');
// Imprimir todos os estados e transições
print('\n=== State Transition Table ===');
for (final status in OrderStatus.values) {
final targets = status.allowedTransitions.map((t) => t.name).join(', ');
print(' ${status.name.padRight(12)} → ${targets.isEmpty ? '(final)' : targets}');
}
// Estatísticas de status
print('\n=== Status Properties ===');
final activeCount = OrderStatus.values.where((s) => s.isActive).length;
final finalCount = OrderStatus.values.where((s) => s.isFinal).length;
print('Active states: $activeCount');
print('Final states: $finalCount');
print('Total states: ${OrderStatus.values.length}');
}
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
Saída:
=== Order State Machine ===
Initial: ORD-001: Awaiting Processing ($1500.00 USD)
ORD-001: pending → processing
ORD-001: processing → shipped
ORD-001: shipped → delivered
Cannot transition from delivered to cancelled
ORD-001: delivered → refunded
Final: ORD-001: Refunded ($1500.00 USD)
=== State Transition Table ===
pending → processing, cancelled
processing → shipped, cancelled
shipped → delivered
delivered → refunded
cancelled → (final)
refunded → (final)
=== Status Properties ===
Active states: 3
Final states: 3
Total states: 6
❓ Perguntas Frequentes
P: Qual a diferença entre um Enhanced Enum e um enum regular? R: Enhanced Enums podem ter propriedades, construtores e métodos. Enums regulares possuem apenas
nameeindex. Dart 2.17+ recomenda usar Enhanced Enums em todos os lugares.
P: Enums podem implementar interfaces? R: Sim. Enums podem implementar interfaces, por exemplo, `enum Status implements Comparable``
```. No entanto, eles não podem estender outras classes (enums herdam implicitamente de Enum).
P: Métodos de extensão podem acessar membros privados? R: Não. Métodos de extensão são definidos fora da classe e só podem acessar membros públicos. Esta é a diferença fundamental entre extensões e métodos de classe.
P: Métodos de extensão são despachados estática ou dinamicamente? R: Estaticamente. O compilador determina qual método de extensão chamar com base no tipo declarado da variável em tempo de compilação. O tipo em runtime não importa. Esta é a diferença essencial dos métodos de classe, que são despachados dinamicamente.
P: Extensões podem adicionar propriedades? R: Elas podem adicionar propriedades computadas (getters) mas não podem adicionar variáveis de instância (propriedades armazenadas). Extensões não modificam o layout de memória de um objeto.
P: O que acontece se duas extensões definirem um método com o mesmo nome? R: Se o compilador puder distinguir pelo tipo do receptor, ele seleciona automaticamente o correto. Se não puder distinguir (ambíguo), você deve especificar explicitamente usando
ExtensionName(obj).method().
P: Há diferença de desempenho entre
valuesebyNamepara enums? R:valuesretorna uma lista em cache, O(1).byNameitera sobrevaluespara encontrar uma correspondência, O(n). Para buscas frequentes, considere criar seu próprio cache Map.
📖 Resumo
- Enhanced Enums permitem que valores de enum tenham propriedades, construtores e métodos, tornando-os mais seguros que constantes string simples.
- Expressões switch combinadas com enums garantem exaustividade verificada pelo compilador; adicionar um novo valor de enum não será esquecido.
- Métodos de extensão adicionam funcionalidade a tipos existentes sem modificar o código-fonte ou alterar o layout de memória.
- Métodos de extensão são despachados estaticamente e só podem acessar membros públicos. Conflitos de nomenclatura exigem resolução explícita.
- O DataPipeline usa o enum
OrderStatuspara definir uma máquina de estados, e extensões de String/num para formatar valores.
📝 Exercícios
- Básico (Dificuldade ⭐): Defina um Enhanced Enum
OutputFormatcontendo os valoresjson,csvehtml, cada um com uma propriedadefileExtension(ex:.json) e uma propriedademimeType(ex:application/json). - Intermediário (Dificuldade ⭐⭐): Adicione métodos de extensão a
String:toOrderId(formata como ORD-XXX),isValidEmail(valida formato de email),truncateWithEllipsis(int max)(trunca e adiciona reticências), e teste-os. - Desafio (Dificuldade ⭐⭐⭐): Use um Enhanced Enum para implementar uma máquina de estados de workflow completa (Draft → Review → Approved → Published). Cada estado deve definir seus alvos de transição permitidos. Implemente um método
transitionTo()e verifique que transições ilegais são rejeitadas.