Executando verificação de segurança...
1

Meu aprendizado em Dart! Introdução: parte 3!

Antes de começar a ler…

A ideia inicial era criar um artigo/tutorial, que contivesse os conhecimentos basico de Dart, e também algumas aplicações no final, mas, acabei percebendo que começou a ficar muito extenso, e por esse motivo decidi dividir em partes, essa primeira em até cinco partes ao longo da semana, essa sendo a terceira parte. Originalmente com mais de 80000(oitenta mil) caracteres para esse texto...

E como eu sempre falo, leia a documentação oficial, se for possível para você!

Até o atual momento da publicação deste texto, o TabNews, não formata o código em Dart corretamente, peço de antemão desculpas por qualquer inconveniência causada pela formatação incorreta do código.

Requisitos

Ter lido as primeiras partes do artigo/tutorial:

Meu aprendizado em Dart! Introdução: parte 1!

Meu aprendizado em Dart! Introdução: parte 2!

Valores de retorno

Todas as funções retornam um valor. Se nenhum valor de retorno for especificado, a instrução return null; É anexada implicitamente ao corpo da função.

foo() {}
 
assert(foo() == null);

Operadores

Dart suporta os operadores mostrados na tabela a seguir. A tabela mostra a associatividade do operador do Dart e a precedência do operador do maior para o menor, que são uma aproximação das relações do operador do Dart. Você pode implementar muitos desses operadores como membros de classe.

DescriptionOperatorAssociativity
unary postfixexpr++ expr-- () [] ?[] . ?. !None
unary prefix-expr !expr ~expr ++expr --expr await exprNone
multiplicative* / % ~/Left
additive+ -Left
shift<< >> >>>Left
bitwise AND&Left
bitwise XOR^Left
bitwise OR|Left
relational and type test>= > <= < as is is!None
equality== !=None
logical AND&&Left
logical OR||Left
if null??Left
conditionalexpr1 ? expr2 : expr3Right
cascade.. ?..Right
assignment= *= /= += -= &= ^= etc.Right

Aviso: A tabela anterior deve ser usada apenas como um guia útil. A noção de precedência de operador e associatividade é uma aproximação da verdade encontrada na gramática da linguagem. Você pode encontrar o comportamento autoritativo dos relacionamentos de operador do Dart na gramática definida na especificação da linguagem Dart.

Ao usar operadores, você cria expressões. Aqui estão alguns exemplos de expressões de operador:

a++
a + b
a = b
a == b
c ? a : b
a is T

Na tabela de operadores, cada operador tem precedência maior do que os operadores nas linhas que o seguem. Por exemplo, o operador multiplicativo % tem precedência maior que (e, portanto, é executado antes) o operador de igualdade ==, que tem precedência maior que o operador AND lógico &&. Essa precedência significa que as duas linhas de código a seguir são executadas da mesma maneira:

// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...
 
// Harder to read, but equivalent.
if (n % i == 0 && d % i == 0) ...

Aviso: Para operadores que usam dois operandos, o operando mais à esquerda determina qual método é usado. Por exemplo, se você tiver um objeto Vector e um objeto Point, então aVector + aPoint usa adição de vetor (+).

Declarações de fluxo de controle

Você pode controlar o fluxo do seu código Dart usando qualquer um dos seguintes:

  • if e else
  • for loops
  • while e do-while loops
  • break e continue
  • switch e case
  • assert
    • Durante o desenvolvimento, use uma instrução assert— assert(condição, optionalMessage); —para interromper a execução normal se uma condição booleana for falsa.

Você também pode afetar o fluxo de controle usando try-catch e throw.

Exceções

Seu código Dart pode lançar e capturar exceções. Exceções são erros que indicam que algo isolate aconteceu. Se a exceção não for capturada, o isolate que gerou a exceção é suspenso e, normalmente, o isolate e seu programa são finalizados.

Classes

Dart é uma linguagem orientada a objetos com classes e herança baseada em mixing. Todo objeto é uma instância de uma classe e todas as classes, exceto Null, descendem de Object. A herança baseada em mixin significa que, embora cada classe (exceto a classe principal, Object?) tenha exatamente uma superclasse, o corpo de uma classe pode ser reutilizado em várias hierarquias de classes. Os métodos de extensão são uma maneira de adicionar funcionalidade a uma classe sem alterar a classe ou criar uma subclasse.

Usando membros da classe

Os objetos têm membros que consistem em funções e dados (métodos e variáveis ​​de instância, respectivamente). Quando você chama um método, você o invoca em um objeto: O método tem acesso às funções e aos dados desse objeto.

Use um ponto (.) para se referir a uma variável de instância ou método:

var p = Point(2, 2);
 
// Get the value of y.
assert(p.y == 2);
 
// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));

Usar ?. em vez de . para evitar uma exceção quando o operando mais à esquerda for nulo:

// If p is non-null, set a variable equal to its y value.
var a = p?.y;

Usando construtores

Você pode criar um objeto usando um construtor. Os nomes dos construtores podem ser ClassName ou ClassName.identifier. Por exemplo, o código a seguir cria objetos Point usando os construtores Point() e Point.fromJson():

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

O código a seguir tem o mesmo efeito, mas usa a palavra-chave new opcional antes do nome do construtor:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

Algumas classes fornecem construtores constantes. Para criar uma constante de tempo de compilação usando um construtor constante, coloque a palavra-chave const antes do nome do construtor:

var p = const ImmutablePoint(2, 2);

Obtendo o tipo de um objeto

Para obter o tipo de um objeto em tempo de execução, você pode usar a propriedade RuntimeType do objeto, que retorna um objeto Type.

print('The type of a is ${a.runtimeType}');

Variáveis ​​de instância

Veja como você declara variáveis ​​de instância:

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
  double z = 0; // Declare z, initially 0.
}

Todas as variáveis ​​de instância não inicializadas têm o valor nulo.

Todas as variáveis ​​de instância geram um método getter implícito. Variáveis ​​de instância não finais e variáveis ​​de instância finais atrasadas sem inicializadores também geram um método setter implícito.

Se você iniciar uma variável de instância não atrasada onde ela é declarada, o valor é definido quando a instância é criada, que ocorre antes da execução do construtor e de sua lista de inicializadores.

class Point {
  double? x; // Declare instance variable x, initially null.
  double? y; // Declare y, initially null.
}
 
void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

As variáveis ​​de instância podem ser finais, caso em que devem ser definidas exatamente uma vez. Inicialize as variáveis ​​de instância finais e não atrasadas na declaração, usando um parâmetro de construtor ou uma lista de inicializadores do construtor:

class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();
 
  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

Constructors

Declare um construtor criando uma função com o mesmo nome de sua classe (mais, opcionalmente, um identificador adicional conforme descrito em Construtores nomeados). A forma mais comum de um construtor, o construtor generativo, cria uma nova instância de uma classe:

class Point {
  double x = 0;
  double y = 0;
 
  Point(double x, double y) {
    // See initializing formal parameters for a better way
    // to initialize instance variables.
    this.x = x;
    this.y = y;
  }
}

A palavra-chave this refere-se à instância atual.

Nota: Use o this somente quando houver um conflito de nome. Caso contrário, o estilo Dart omite o this.

Inicializando parâmetros formais

O padrão de atribuir um argumento de construtor a uma variável de instância é tão comum que o Dart iniciou parâmetros formais para facilitar.

A inicialização de parâmetros também pode ser usada para inicializar variáveis ​​de instância não anuláveis ​​ou finais, que devem ser inicializadas ou fornecidas com um valor padrão.

class Point {
  final double x;
  final double y;
 
  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);
}

As variáveis ​​introduzidas pelos formais de inicialização são implicitamente finais e apenas no escopo da lista inicializadora.

Construtores padrão

Se você não declarar um construtor, um construtor padrão será fornecido para você. O construtor padrão não tem argumentos e invoca o construtor sem argumentos na superclasse.

Construtores não são herdados

Subclasses não herdam construtores de suas superclasses. Uma subclasse que não declara um construtor tem apenas o construtor padrão (sem argumento, sem nome).

Construtores nomeados

Use um construtor nomeado para implementar vários construtores para uma classe ou para fornecer clareza extra:

const double xOrigin = 0;
const double yOrigin = 0;
 
class Point {
  final double x;
  final double y;
 
  Point(this.x, this.y);
 
  // Named constructor
  Point.origin()
      : x = xOrigin,
        y = yOrigin;
}

Lembre-se de que os construtores não são herdados, o que significa que o construtor nomeado de uma superclasse não é herdado por uma subclasse. Se você deseja que uma subclasse seja criada com um construtor nomeado definido na superclasse, deve implementar esse construtor na subclasse.

Invocando um construtor da superclasse não padrão

Por padrão, um construtor em uma subclasse chama o construtor sem nome e sem argumentos da superclasse. O construtor da superclasse é chamado no início do corpo do construtor. Se uma lista de inicializadores também estiver sendo usada, ela será executada antes da chamada da superclasse. Em resumo, a ordem de execução é a seguinte:

  1. lista de inicializadores
  2. construtor sem argumento da superclass
  3. construtor sem argumento da classe principal

Se a superclasse não tiver um construtor sem nome e sem argumentos, você deverá chamar manualmente um dos construtores da superclasse. Especifique o construtor da superclasse após dois pontos (:), logo antes do corpo do construtor (se houver).

Aviso: Os argumentos para o construtor da superclasse não têm acesso a isso. Por exemplo, argumentos podem chamar métodos estáticos, mas não métodos de instância.

Para evitar ter que passar manualmente cada parâmetro para a super invocação de um construtor, você pode usar parâmetros de super inicializador para encaminhar parâmetros para o construtor de superclasse especificado ou padrão. Esse recurso não pode ser usado com construtores de redirecionamento. Os parâmetros do super inicializador têm sintaxe e semântica semelhantes para inicializar parâmetros formais:

class Vector2d {
  final double x;
  final double y;
 
  Vector2d(this.x, this.y);
}
 
class Vector3d extends Vector2d {
  final double z;
 
  // Forward the x and y parameters to the default super constructor like:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  Vector3d(super.x, super.y, this.z);
}

Os parâmetros do super inicializador não podem ser posicionais se a invocação do super construtor já tiver argumentos posicionais, mas sempre podem ser nomeados:

class Vector2d {
  // ...
 
  Vector2d.named({required this.x, required this.y});
}
 
class Vector3d extends Vector2d {
  // ...
 
  // Forward the y parameter to the named super constructor like:
  // Vector3d.yzPlane({required double y, required this.z})
  //       : super.named(x: 0, y: y);
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}

Nota: O uso de parâmetros do super inicializador requer uma versão da linguagem de pelo menos 2.17. Se você estiver usando uma versão da linguagem anterior, deverá passar manualmente todos os parâmetros do super construtor.

Métodos

Métodos são funções que fornecem comportamento para um objeto.

Métodos de instância

Métodos de instância em objetos podem acessar variáveis ​​de instância e this. O método distanceTo() no exemplo a seguir é um exemplo de método de instância:

import 'dart:math';
 
class Point {
  final double x;
  final double y;
 
  Point(this.x, this.y);
 
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Operadores

Operadores são métodos de instância com nomes especiais. Dart permite definir operadores com os seguintes nomes:

<, >, <=, >=, -, +, /, ~/, *, %, |, ^, &, <<, >>, >>>, [ ], [ ] =, ~, ==

Uma declaração de operador é identificada usando o operador de identificador integrado. O exemplo a seguir define adição vetorial (+), subtração (-) e igualdade (==):

class Vector {
  final int x, y;
 
  Vector(this.x, this.y);
 
  Vector operator + (Vector v) => Vector(x + v.x, y + v.y);
  Vector operator - (Vector v) => Vector(x - v.x, y - v.y);
 
  @override
  bool operator == (Object other) =>
      other is Vector && x == other.x && y == other.y;
 
  @override
  int get hashCode => Object.hash(x, y);
}
 
void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);
 
  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

Getters e setters

Getters e setters são métodos especiais que fornecem acesso de leitura e gravação às propriedades de um objeto. Lembre-se de que cada variável de instância tem um getter implícito, mais um setter, se apropriado. Você pode criar propriedades adicionais implementando getters e setters, usando as palavras-chave get e set:

class Rectangle {
  double left, top, width, height;
 
  Rectangle(this.left, this.top, this.width, this.height);
 
  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;

  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}
 
void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

Nota: Operadores como incremento (++) funcionam da maneira esperada, independentemente de um getter ser definido explicitamente ou não. Para evitar efeitos colaterais inesperados, o operador chama o getter exatamente uma vez, salvando seu valor em uma variável temporária.

Métodos abstratos

Os métodos de instância, getter e setter podem ser abstratos, definindo uma interface, mas deixando sua implementação para outras classes. Métodos abstratos só podem existir em classes abstratas.

Para tornar um método abstrato, use um ponto e vírgula (;) em vez do corpo do método:

abstract class Doer {
  // Define instance variables and methods...
 
  void doSomething(); // Define an abstract method.
}
 
class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

Classes abstratas

Use o modificador abstract para definir uma classe abstrata, uma classe que não pode ser instanciada. Classes abstratas são úteis para definir interfaces, muitas vezes com alguma implementação. Se você deseja que sua classe abstrata pareça ser instanciável, defina um construtor de fábrica.

As classes abstratas geralmente têm métodos abstratos. Aqui está um exemplo de declaração de uma classe abstrata que possui um método abstrato:

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...
 
  void updateChildren(); // Abstract method.
}

Interfaces implícitas

Cada classe define implicitamente uma interface contendo todos os membros de instância da classe e de quaisquer interfaces que ela implemente. Se você deseja criar uma classe A que suporte a API da classe B sem herdar a implementação de B, a classe A deve implementar a interface B.

Uma classe implementa uma ou mais interfaces declarando em uma cláusula implementada e, em seguida, fornecendo as APIs exigidas pelas interfaces. Por exemplo:

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final String _name;
 
  // Not in the interface, since this is a constructor.
  Person(this._name);
 
  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}
 
// An implementation of the Person interface.
class Impostor implements Person {
  String get _name => '';
 
  String greet(String who) => 'Hi $who. Do you know who I am?';
}
 
String greetBob(Person person) => person.greet('Bob');
 
void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

Aqui está um exemplo de especificação de que uma classe implementa várias interfaces:

class Point implements Comparable, Location {...}

Estendendo uma classe

Use extends para criar uma subclasse e super para se referir à superclasse:

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}
 
class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

Continua No Próximo Episódio...

Carregando publicação patrocinada...