Guia abrangente do Dart e Flutter – Dominando os “Extension Types”

Tempo de leitura: 5 minutes

Bem-vindo ao Guia abrangente do Dart e do Flutter! Nesta edição, vamos nos aprofundar em um recurso poderoso do Dart: “Extension Types.”. Esses tipos oferecem uma maneira dinâmica de aprimorar e modificar os tipos existentes sem a necessidade de objetos de proteção em tempo de execução.

Vamos explorar a sintaxe, o uso e as considerações dos tipos de extensão por meio de exemplos práticos.

 

Este guia se aprofunda no mundo dos tipos de extensão em Dart, um conceito poderoso para aprimorar a expressividade e a capacidade de manutenção do código. Exploraremos sua sintaxe, uso e aplicações práticas, equipando-o para usá-los com confiança em seus projetos Dart.

 

Entendendo o Extension Types

Imagine um cenário em que você queira trabalhar com estruturas de dados existentes, como números inteiros, mas restringir o comportamento delas para atender às suas necessidades específicas. É aí que entram os tipos de extensão. Eles atuam como um invólucro em tempo de compilação em torno de um tipo existente, fornecendo uma interface restrita e modificada sem alterar os dados subjacentes.

Pense nisso como se estivesse colocando um par de óculos especiais. Você ainda pode ver o mundo (o tipo original), mas os óculos (o tipo de extensão) filtram e destacam aspectos específicos, facilitando a interação com o mundo de uma maneira específica.

Os tipos de extensão funcionam como invólucros em tempo de compilação, permitindo modificações na interface dos tipos existentes sem a necessidade de objetos adicionais em tempo de execução. Eles impõem disciplina no conjunto de operações disponíveis para o tipo subjacente, fornecendo uma maneira de criar uma interface mais específica e controlada.

 

Exemplo

Considere um tipo de extensão que envolve o tipo int para criar uma interface adequada para números de identificação:

extension type IdNumber(int id) {
  operator <(IdNumber other) => id < other.id;
}

Com esse tipo de extensão, garantimos que somente as operações relevantes para os números de ID sejam permitidas.

 

Declaração e uso

Para definir um novo tipo de extensão, use a declaração de extension type seguida de um nome e do tipo de representação entre parênteses:

extension type E(int i) {
  // Define set of operations.
}

O tipo de representação especifica o tipo subjacente do tipo de extensão. Os construtores e getters implícitos são gerados com base no tipo de representação.

Portanto, a declaração de um tipo de extensão envolve:

  1. Keyword – tipo de extensão seguido de um nome para o tipo de extensão.
  2. Representation type – especifica o tipo existente que o tipo de extensão envolve, entre parênteses. Por exemplo, (int).
  3. Body – Esse bloco define os membros (methods, getters, setters, e operators) que formam a interface do seu tipo de extensão.

Veja um exemplo:

extension type IdNumber(int id) {
  // Restringe as operações àquelas relevantes para os números de identificação
  bool isValid() => !id.isNegative;
  
  // Sobrecarga de operador para comparação
  bool operator <(IdNumber other) => id < other.id;
}

Neste exemplo, o tipo de extensão IdNumber envolve o tipo int. Ele define um método isValid para garantir que o ID não seja negativo e sobrecarrega o operador < para uma comparação significativa dos números de ID.

 

Uso – Criar uma instância de um tipo de extensão é semelhante a criar um objeto de uma classe:

var myId = IdNumber(424242);

Agora, você pode usar os membros definidos no tipo de extensão na variável myId:

if (myId.isValid()) {
  print("Valid ID");
}

if (myId < IdNumber(424243)) {
  print("ID is smaller");
}

Entretanto, a tentativa de usar métodos ou operadores não definidos no tipo de extensão resultará em um erro:

// Error: 'IdNumber' não tem um método '+'
var sum = myId + 10;

 

Construtores

Você pode declarar construtores no corpo de um tipo de extensão. A própria declaração de representação serve como um construtor implícito, e outros construtores nomeados podem ser adicionados:

extension type E(int i) {
  E.n(this.i);

  E.m(int j, String foo) : i = j + foo.length;
}

 

Membros

Declare membros no tipo de extensão para definir sua interface, como methods, getters, setters, ou operators:

extension type NumberE(int value) {
  NumberE operator +(NumberE other) => NumberE(value + other.value);

  NumberE get myNum => this;

  bool isValid() => !value.isNegative;
}

 

Implementos

Use a cláusula implements para introduzir uma relação de subtipo em um tipo de extensão, adicionando membros do objeto de representação à interface do tipo de extensão:

extension type NumberI(int i) implements int {
  // 'NumberI' pode invocar todos os membros de 'int',
  // além de qualquer outra coisa que ele declare aqui.
}

 

@redeclare

Use @redeclare para indicar o uso intencional do mesmo nome como membro de um supertipo:

extension type MyString(String _) implements String {
  @redeclare
  int operator [](int index) => codeUnitAt(index);
}

 

Uso

Para usar um extension type, crie uma instância chamando um construtor:

extension type NumberE(int value) {
  NumberE operator +(NumberE other) =>
      NumberE(value + other.value);
}

void testE() { 
  var num = NumberE(1);
  var sum = num + NumberE(2); // Extension member invocation.
}

Você pode invocar membros no objeto de tipo de extensão como faria com um objeto de classe.

 

Fornecimento de uma interface estendida

Quando um tipo de extensão implementa seu tipo de representação, ele se torna “transparente”, permitindo o acesso aos membros do tipo subjacente:

extension type NumberT(int value) 
  implements int {
  NumberT get i => this;
}

 

Fornecimento de uma interface diferente

Se um tipo de extensão não implementar seu tipo de representação, ele será tratado como um tipo completamente novo, permitindo a substituição da interface do tipo existente:

extension type NumberE(int value) {
  NumberE operator +(NumberE other) =>
      NumberE(value + other.value);
}

void testE() { 
  var num1 = NumberE(1);
  int num2 = NumberE(2); // Erro: Não é possível atribuir 'NumberE' a 'int'.
  var sum1 = num1 + num1; // OK: 'NumberE' defines '+'.
}

 

Considerações sobre tipos

Os tipos de extensão são uma construção de tempo de compilação sem rastreamento de tempo de execução. Os testes de tipo dinâmico e as consultas de tipo avaliam o objeto de representação subjacente em tempo de execução:

var n = NumberE(1);

if (n is int) print(n.value); // Prints 1.

Lembre-se de que os tipos de extensão são uma abstração insegura, pois o tipo de representação pode ser acessado em tempo de execução.

 

 

Conclusão

O domínio dos tipos de extensão no Dart fornece uma ferramenta poderosa para modificar e aprimorar os tipos existentes, oferecendo flexibilidade e benefícios de desempenho. Ao compreender a sintaxe, os cenários de uso e as considerações sobre os tipos, os desenvolvedores podem aproveitar esse recurso de forma eficaz em seus projetos Dart e Flutter.

 

Perguntas frequentes

1. O que são tipos de extensão no Dart?
Os tipos de extensão no Dart são uma abstração de tempo de compilação que permite aos desenvolvedores modificar a interface de um tipo existente sem criar objetos de invólucro em tempo de execução. Eles desempenham um papel fundamental na interoperabilidade estática de JS, fornecendo uma abordagem disciplinada às operações disponíveis para o tipo subjacente.

2. Qual é a diferença entre os tipos de extensão e as classes de cobertura?
Os Extension Types, assim como as classes wrapper, modificam a interface dos tipos existentes. No entanto, diferentemente das classes wrapper, os Extension Types são apenas estáticos e não têm custo de tempo de execução. Eles existem apenas em tempo de compilação, o que proporciona uma solução mais eficiente quando vários objetos precisam ser modificados.

3. Os Extension Types podem ter construtores?
Sim, os Extension Types podem ter construtores definidos em seu corpo. Os construtores podem ser implícitos, substituindo um construtor sem nome, ou explícitos, permitindo personalização e parâmetros adicionais.

4. Como os tipos de extensão lidam com a implementação de tipos de representação?
Os tipos de extensão podem implementar seu tipo de representação ou um supertipo do tipo de representação usando a cláusula implements. Isso introduz uma relação de aplicabilidade, tornando os membros do supertipo disponíveis para o tipo de extensão.

5. Que considerações os desenvolvedores devem ter em mente ao usar os tipos de extensão?
Os desenvolvedores devem estar cientes de que os tipos de extensão são uma construção de envolvimento em tempo de compilação. No tempo de execução, não há nenhum rastro do tipo de extensão, e qualquer consulta de tipo funciona no tipo de representação. Isso torna os tipos de extensão uma abstração poderosa, mas potencialmente insegura, exigindo um equilíbrio entre desempenho e segurança.