Dart 03 – Generics no Dart

Este artigo é a parte [part not set] de 3 na série Dart

O Dart possui uma forma de codificar uma classe ou função para que ela funcione com uma variedade de tipos diferentes, por meio da adoção de Generics. Com o Generics é possível declarar uma classe ou função sem abrir mão da segurança do tipo, que ocorre ao empregar o tipo dynamic. Neste texto vou discutir um pouco sobre como empregar o Generic para criação de classes e funções.

3.1 – Criando Classes e Métodos Generics

A ideia aqui é criar um classe para gerar uma matriz que pode ser de qualquer tipo, ou seja, inteiro, string, double, … Para isto será empregado o uso de Generics para a criação da classe e de alguns métodos desta.

A classe Matrix terá três atributos sendo eles:

  • rows e columns – duas constantes inteiras para armazenar o número de linhas e colunas da matriz;
  • _matrix – uma lista de listas de tipo Generics.

A estratégia do Dart para declarar uma classe de tipo genérico é empregar a sintaxe “<Letra>” como declaração de seu tipo. A Letra é uma letra qualquer em maiúsculo, por convenção. Algo como:

class Matrix<E> {
  final rows;
  final columns;
  final List<List<E>> _matrix = [];
}

Declara uma classe Matrix que pode ser iniciada com o tipo E que pode ser um int, double, String, ou qualquer outro objeto que se deseje empregar na matriz.

Se desejado é possível estender o domínio da E para o grupo de objetos específicos com um extends

class Matrix<E extends Object> {
  final rows;
  final columns;
  final List<List<E>> _matrix = [];
}

Naturalmente o extends Object aqui não adiciona muita restrição, visto que no Dart praticamente tudo estende um Object, mas vou deixá-lo aí a fim de explicitar a declaração.

Todos os atributos são imutáveis e por isto os declarei como final. O construtor padrão é declarado a seguir, iniciando as linhas de Matrix para o número em rows:

  Matrix(this.rows, this.columns) {
    for (int r = 0; r < rows; r++) {
      _matrix.add(<E>[]);
    }
  }

Embora seja redundante a declaração da linha do tipo <E> adicionada em _matrix, a deixo explícita pelos mesmos motivos anteriores. Esta inicialização preenche a matriz com rows linhas to tipo <E>[], listas do tipo E. Esta inicialização simplificar o método add, apresentado a seguir.

Para simplificar o preenchimento da matriz vou adicionar um método add para adicionar seus elementos serialmente, linha por linha. O método é bem simples, ele apenas irá verificar o comprimento de cada linha e adicionar o elemento passado na próxima linha incompleta.

  add(E element) {
    int row = 0;
    while (_matrix[row].length == columns && row != rows) {
      row++;
    }

    if (row == rows) return;

    _matrix[row].add(element);
  }

O elemento a ser adicionado à matriz tem de se do tipo E, (E element). Este é adicionado à linha incompleta ao final do método.

O próximo passo é criar um método para retornar um dos elementos de Matrix<E> de uma dada linha x coluna:

  E item(int row, int column) {
    if (row >= rows || column >= columns) {
      throw Exception('RangeError (index): Invalid value: Not in inclusive'
          ' range [0..$columns][0..$rows]: [$row][$column]');
    }
    return _matrix[row][column];
  }

O E a frente do nome do método representa o seu tipo de retorno. Neste método tratei apenas o acesso a elementos fora do range rows x columns da matriz, erro por acesso a elementos ainda não adicionado em _matrix serão gerado no próprio acesso à linha do return.

Adicionei mais três métodos sendo eles:

  1. currentLength – para retornar o número total de elementos adicionados a matriz;
  2. lenght – um getter para retornar o comprimento máximo da matriz;
  3. toString – para formatar uma saída mais instrutiva da matriz.

O código completo da classe Matrix é apresentado a seguir:

class Matrix<E extends Object> {
  final int rows;
  final int columns;
  final List<List<E>> _matrix = [];

  Matrix(this.rows, this.columns) {
    for (int r = 0; r < rows; r++) {
      _matrix.add(<E>[]);
    }
  }

  add(E element) {
    int row = 0;
    while (_matrix[row].length == columns && row != rows) {
      row++;
    }

    if (row == rows) return;

    _matrix[row].add(element);
  }

  E item(int row, int column) {
    if (row >= rows || column >= columns) {
      throw Exception('RangeError (index): Invalid value: Not in inclusive'
          ' range [0..$columns][0..$rows]: [$row][$column]');
    }
    return _matrix[row][column];
  }

  int get currentLength {
    int count = 0;

    for (var row in _matrix) {
      count += row.length;
    }

    return count;
  }

  int get length => rows * columns;

  @override
  String toString() {
    String strOut = '';
    for (List<E> line in _matrix) {
      strOut += '${line.toString()}\n';
    }

    return strOut.trim();
  }
}

Testes e Conclusões Finais

Um código para testes é apresentado a seguir:

import 'dart:math';

import 'modules/matrix.dart';

Matrix<int> matrixIntRandom(int r, int c, int maxN) {
  Matrix<int> matrix = Matrix<int>(r, c);

  for (int count = 0; count < r * c; count++) {
    matrix.add(Random().nextInt(maxN));
  }

  return matrix;
}

Matrix<String> matrixStrRandom(int r, int c, String letters) {
  Matrix<String> matrix = Matrix<String>(r, c);

  for (int count = 0; count < r * c; count++) {
    matrix.add(letters[Random().nextInt(letters.length)]);
  }

  return matrix;
}

void main() {
  Matrix<int> m = matrixIntRandom(2, 3, 100);

  print('');
  print(m);
  print('m.length: ${m.length}');
  print('m[1][1]: ${m.item(1, 1)}');

  Matrix<String> m2 = matrixStrRandom(5, 4, 'ABCDEFGHIJKLMNOPQRSTUVWXZ');
  print('');
  print(m2);
  print('m2.length: ${m2.length}'); // print(m.item(1, 1));
  print('m2[2][2]: ${m2.item(2, 2)}');
  // print('m2[5][5]: ${m2.item(5, 5)}');
}

Neste código são implementadas duas funções, sendo a primeira para gerar uma matriz randômica de inteiros, matrixIntRandom nas linhas 5 à 13, e a segunda para gerar uma matriz randômica com as letras passadas por uma string, matrixStrRandom nas linhas 15 à 23.

O código ainda possui algumas exceções a serem tratadas, mas não é o objeto deste texto. A classe Matrix mostra de forma simples como se pode implementar uma classe e métodos de um tipo genérico, sem abrir mão da segurança do emprego de tipos declarados em pró de variáveis dinâmicas.

Observe que o tipo de elemento armazenado na classe Matrix é passada explicitando o seu tipo entre <>, como nas linhas 6 e 16 do código acima. Uma saída típica deste código é apresentada no quadro abaixo:

[73, 37, 59]
[65, 83, 1]
m.length: 6
m[1][1]: 83

[V, L, F, L]
[W, Q, E, F]
[T, C, F, I]
[O, G, T, W]
[P, B, E, S]
m2.length: 20
m2[2][2]: F

Deixe um comentário

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.