- Dart 03 – Generics no Dart
- Dart 02 – Exceções, Classes e Mais
- Dart 01 – Introdução
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
ecolumns
– duas constantes inteiras para armazenar o número de linhas e colunas da matriz;_matrix
– uma lista de listas de tipoGenerics
.
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:
currentLength
– para retornar o número total de elementos adicionados a matriz;lenght
– um getter para retornar o comprimento máximo da matriz;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