Classe class

O que são classe e objeto

Uma classe representa uma categoria, uma classificação. Nós abstraímos os elementos ao nosso redor para agrupá-los em um único tipo. Por exemplo, abstraímos todos os seres humanos do planeta dentro da classe «Pessoa». Uma pessoa concreta como Bob ou Tom é um objeto.

Uma classe funciona como um «projeto» ou um «molde», definindo a estrutura e o comportamento de todos os objetos vinculados a ela.

Vamos usar novamente a classe Pessoa como exemplo: esse modelo define tudo o que uma pessoa possui e tudo o que ela consegue fazer. Ela tem nome, braços, pernas e outros órgãos; consegue falar e raciocinar. Dados como nome ou idade são as propriedades da classe, enquanto ações como falar ou pensar são seus comportamentos. Em resumo, uma classe extrai características comuns de objetos reais. Com esse modelo, podemos criar vários objetos individuais como Jack ou Jim.

As propriedades da classe (nome, idade) e seus comportamentos (falar, pensar) são representados respectivamente por variáveis e funções.

Ela define quais propriedades (variáveis) e quais métodos (funções) cada objeto deve ter.

Uma classe pode ser comparada a uma forma de pão: ela não é um produto final. Com uma única forma, fabricamos vários pães reais, assim como uma classe gera diversos objetos.

No início da história da programação, o conceito de classe não existia. A linguagem C é um exemplo clássico: ela só trabalha com funções, variáveis e instruções — chamamos isso de programação procedural. À medida que os softwares se tornavam mais complexos, surgiram muitos problemas com esse modelo: muito código duplicado, estrutura desorganizada e falta de hierarquia. Depois, surgiu o paradigma orientado a objetos: abstraímos a lógica do programa em objetos, igual ao mundo real. Podemos comparar com uma empresa: no começo, o chefe cuida de todas as tarefas sozinho. Ao contratar funcionários, ele delega a contabilidade ao setor financeiro e o recrutamento ao RH. Com a divisão de funções, todo o fluxo fica muito mais organizado.

Um objeto é uma instância de uma classe.

Ao usar a palavra-chave new ou um construtor, a classe gera um novo objeto.

O objeto é uma entidade concreta em tempo de execução, capaz de armazenar dados e invocar métodos.

Relação entre classes e objetos

  • A classe é o projeto arquitetônico; o objeto é a casa construída a partir desse projeto.
  • A partir de uma única classe, criamos vários objetos, cada um com dados independentes.
  • Todos os objetos criados da mesma classe compartilham os comportamentos (métodos) definidos na classe.

Agora vamos mudar para a linguagem Dart

Vamos estudar os tópicos abaixo

Dart é uma linguagem totalmente orientada a objetos: todo valor é uma instância de classe. Com exceção de Null, todas as classes herdam da classe base Object.

Dart implementa herança através da combinação de classes e Mixins

Herança simples: cada classe só pode ter uma única classe pai, mas os Mixins permitem reutilizar vários blocos de lógica de diferentes classes

Métodos de extensão: adicionar funcionalidades a uma classe sem alterar seu código fonte ou criar uma subclasse

Modificadores de classe: controlar se outras bibliotecas podem herdar ou implementar essa classe

Vamos começar a aprender sobre classes em Dart

Membros da classe

Os membros de uma classe englobam seus métodos e variáveis: são os elementos que compõem a classe.

Use o operador . para acessar membros de instância; o operador de segurança nula ?. evita exceções de ponteiro nulo

Aqui está um exemplo completo que demonstra a definição de classe, criação de objetos a partir da classe e chamada de membros pelo nome do objeto

// Definir uma classe, equivalente a criar um molde
class Point {
  double x, y;
  Point(this.x, this.y);

  // Método de instância: calcular o quadrado da distância entre dois pontos
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  // Criar o objeto p a partir da classe Point para executar operações
  var p = Point(2, 2);

  // Ler variável de instância (getter implícito)
  print(p.y); //2.0

  // Invocar método de instância
  double dist = p.distanceTo(Point(4, 4));
  print(dist); // 8.0

  // Acesso seguro com ?.
  Point? nullablePoint;
  var a = nullablePoint?.y; // Se o objeto for null, a recebe null sem lançar erro
  print(a); // null
}Code language: JavaScript (javascript)

Construtores (Constructors)

Construtores padrão

Dart se diferencia de outras linguagens tipo C por suportar construtores nomeados. Na maioria das linguagens derivadas de C, o construtor precisa ter o mesmo nome da classe; já o Dart permite criar construtores com identificadores diferentes do nome da classe.

Sintaxe: NomeClasse() / NomeClasse.identificador() ;

A palavra-chave new é opcional (disponível desde o Dart 2)

class Point {
  double x, y; // Duas variáveis membros para armazenar propriedades da classe

  // Construtor padrão: inicializar as variáveis x e y
  Point(this.x, this.y);

  // Construtor nomeado Point.fromJson: inicializar x e y com dados JSON
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;

}

void main() {
  var p1 = Point(2, 2);// Chamar o construtor padrão, passar 2 e 2 para definir x=2, y=2 em p1

  var p2 = Point.fromJson({'x': 1, 'y': 2}); // Passar JSON para o construtor nomeado fromJson

  // new é opcional, o comportamento é idêntico
  var p3 = new Point(3, 3); // Criar objeto via construtor padrão

  print("p1:(${p1.x},${p1.y}), p2:(${p2.x},${p2.y}), p3:(${p3.x},${p3.y})");
  // Saída: p1:(2.0,2.0), p2:(1.0,2.0), p3:(3.0,3.0)
}Code language: JavaScript (javascript)

Construtores constantes com const

Coloque a palavra-chave const antes de um construtor para criar objetos constantes em tempo de compilação; constantes com parâmetros idênticos reutilizam a mesma instância.

class ImmutablePoint {
  final double x, y;
  // Regra do construtor constante: todos os membros devem ser marcados com final
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // Criar instâncias constantes
  var a = const ImmutablePoint(1, 1); // A palavra const é obrigatória aqui
  var b = const ImmutablePoint(1, 1);
  print(identical(a, b)); // true, apontam para o mesmo objeto

  var m = const ImmutablePoint(1, 1); 
  var n = const ImmutablePoint(2, 1);
  print(identical(m, n)); // false, valores diferentes geram objetos separados

  // Sem const: objeto normal, sem compartilhamento de instância
  var c = ImmutablePoint(1, 1);
  print(identical(a, c)); //false

  // Dentro de um contexto const, o const interno pode ser omitido
  const pointMap = {
    'p1': [ImmutablePoint(0, 0)],
    'p2': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)]
  };

  print(pointMap); //{p1: [Instance of 'ImmutablePoint'], p2: [Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']}

  print(identical(p1, p2)); // false
  print(identical(p2, p3)); // false
  print(identical(p1, p3)); // false
}Code language: PHP (php)
Um construtor constante só reutiliza a mesma instância se todos os parâmetros passados forem exatamente iguais; basta um parâmetro diferente para criar dois objetos independentes.

Obter o tipo de tempo de execução de um objeto com runtimeType

Use objeto.runtimeType para obter o tipo em tempo de execução. Prefira o operador de verificação de tipo is em vez de comparações com runtimeType

class Point {
  double x, y;
  Point(this.x, this.y);

  // Método de instância: calcular o quadrado da distância entre dois pontos
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  var p = Point(2, 3);
  print("Tipo de p: ${p.runtimeType}"); // Point

  // Método recomendado e estável
  if (p is Point) {
    print("p é uma instância de Point");
  }

  // Não recomendado para código de produção
  if (p.runtimeType == Point) {
    print("p é uma instância de Point ");
  }
}Code language: JavaScript (javascript)

Variáveis de instância Instance Variables

  1. Tipos anuláveis não inicializados têm valor padrão null; tipos não anuláveis devem ser obrigatoriamente inicializados
  2. Todas as variáveis de instância geram um getter implícito automaticamente; variáveis não final ou late final sem inicialização ganham um setter automático
  3. Expressões de inicialização padrão de variáveis de instância não podem acessar this; variáveis late permitem acesso a this na sua inicialização
  4. Variáveis de instância final só recebem valor uma única vez: na declaração, por parâmetro do construtor ou na lista de inicialização do construtor

Getters e setters implícitos

class Point {
  double? x; // Valor inicial null, getter + setter gerados automaticamente
  double? y;
  double z = 0; // Inicializado com 0
}

void main() {
  var point = Point();
  point.x = 4; // Invocar o setter implícito
  assert(point.x == 4); // Invocar o getter implícito
  assert(point.y == null);
  print(point.z); // 0
}
Code language: JavaScript (javascript)

Restrição de acesso a this em expressões de inicialização

double globalX = 1.5;
class Point {
  // Permitido: acessar variável global externa sem usar this
  double? x = globalX;

  // Erro de compilação: inicialização padrão não aceita this
  // double? y = this.x;

  // Permitido: variáveis late podem referenciar this na inicialização
  late double? z = this.x;

  // Permitido: o açúcar sintático this.x nos parâmetros do construtor não é uma expressão de inicialização
  Point(this.x, this.y);
  double? y;
}

void main() {
  var p = Point(null, 5);
  print(p.z); // null
}
Code language: JavaScript (javascript)

Três formas de inicializar variáveis de instância final

class ProfileMark {
  // Forma 1: inicialização direta na declaração
  final DateTime start = DateTime.now();
  final String name;

  // Forma 2: atribuição por parâmetro do construtor
  ProfileMark(this.name);

  // Forma 3: atribuição na lista de inicialização do construtor; o construtor nomeado unnamed define uma string vazia para name por padrão
  ProfileMark.unnamed() : name = '';
}

void main() {
  var m1 = ProfileMark("user1");
  var m2 = ProfileMark.unnamed();
  print(m1.name);
  print(m2.name);
}
Code language: PHP (php)

Interfaces implícitas Implicit interfaces

Cada classe Dart possui uma interface implícita que contém todos os membros públicos de instância da classe.

  • implements: implementar uma interface, é obrigatório sobrescrever todos os métodos da interface
  • extends: herdar de uma classe pai para reutilizar sua implementação
  • Variáveis privadas com prefixo _xxx não são expostas na interface

1 Implementar uma única interface

class Person {
  final String _name;
  Person(this._name);
  // Método público da interface
  String greet(String who) =&gt; "Hello $who, I'm $_name";
}

// Implementar a interface Person: obrigatório sobrescrever todos os membros da interface
class Impostor implements Person {
  String get _name =&gt; "";
  @override
  String greet(String who) =&gt; "Hi $who, guess who?";
}

String greetBob(Person p) =&gt; p.greet("Bob");

void main() {
  print(greetBob(Person("Kathy")));
  print(greetBob(Impostor()));
}
Code language: PHP (php)

Quando uma subclasse sobrescreve um método da interface pai, use a anotação @override, similar ao Java. Para implementar uma interface usamos a palavra-chave implements.

2 Implementar várias interfaces

// Classe abstrata
abstract class Comparable {
  int compareTo(Object other);
}

// Classe abstrata
abstract class Location {
  double get x;
  double get y;
}

// Implementar duas interfaces simultaneamente
class Point implements Comparable, Location {

  @override
  double x, y;

  Point(this.x, this.y);

  @override
  int compareTo(Object other) {
    if (other is Point) {
      return (x + y).compareTo(other.x + other.y);
    }
    return -1;
  }
}

void main() {
  var p = Point(2, 3);
  print(p.x);
  print(p.compareTo(Point(1, 1)));
}
Code language: PHP (php)

@override
double x, y;

Essa anotação significa que essas duas propriedades sobrescrevem os getters abstratos definidos na interface pai Location.

Variáveis estáticas & métodos estáticos static

1. Variáveis estáticas static (variáveis de classe, única globalmente)

  • Pertencem à classe, não às instâncias; inicialização tardia: só são criadas no primeiro acesso
  • Constantes estáticas devem ser nomeadas em camelCase minúsculo
class Queue {
  static const initialCapacity = 16; // Constante estática
  static int count = 0; // Variável estática mutável
  Queue() {
    count++;
  }
}

void main() {
  assert(Queue.initialCapacity == 16); // Inicializa no acesso, valor padrão 16
  Queue(); // Incrementar count em 1
  Queue();
  print(Queue.count); // 2
}
Code language: JavaScript (javascript)

2. Métodos estáticos static (métodos de classe)

  • Não possuem referência this, não conseguem acessar membros de instância; apenas variáveis estáticas
  • Chamada direta pela sintaxe NomeClasse.metodo()
  • Podem ser passados como constantes de compilação para construtores constantes
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  // Método estático: calcular a distância reta entre dois pontos
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var dist = Point.distanceBetween(a, b);
  print(dist); // ~2,828
}Code language: JavaScript (javascript)

Métodos estáticos pertencem à classe, por isso devem ser chamados através do nome da classe, passando duas instâncias de Point como parâmetros.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *