Dart: Classes e Objetos
O Dart possui diversos recursos para escrever código orientado a objetos, classes, objetos, herança, mixins, etc….
No Dart tudo é objeto:
class Dog {}
void main(List<String> args) {
String foo = 'hello world';
int bar = 10;
bool span = true;
var rex = Dog();
print(foo.runtimeType); // String
print(bar.runtimeType); // int
print(span.runtimeType); // bool
print(rex.runtimeType); // Dog
}
Até funções são objetos.
fn(){};
print(fn is Function); //true
Construtores
Contructor é um método especial que é executado quando um objeto é criado. Através do construtor é possível definir valores iniciais para os atributos de um objeto. No Dart é possível criar vários tipos de construtores.
Construtor default
O construtor default é criado automaticamente quando uma classe é criada. Esse construtor não recebe nenhum parâmetro e não faz nada além de criar o objeto.
class Point{
var x;
var y;
Point(num x, num y){
this.x = x;
this.y = y;
}
}
void main(List<String> args) {
var point = Point(10, 20);
print(point.x); // 10
print(point.y); // 20
print(point.runtimeType); // Point
}
Esse construtor pode ser escrito de forma reduzida usando a keyword this
.
class Point {
var x, y;
Point(this.x, this.y);
}
Construtor nomeado
O construtor nomeado permite criar vários construtores para a mesma classe.
import 'dart:developer';
class Point{
var x;
var y;
Point(num this.x, num this.y);
Point.string(String this.x, String this.y);
Point.origin(){this.x = 0; this.y = 0;}
Point.middle(){this.x = 10; this.y = 10;}
}
void main(List<String> args) {
var p1 = Point(10, 20);
var p2 = Point.string('23', '56');
var origin = Point.origin();
var middle = Point.middle();
print(inspect(p1)); //Instance of 'Point'
print(inspect(p2)); //Instance of 'Point'
print(inspect(origin)); //Instance of 'Point'
print(inspect(middle)); //Instance of 'Point'
}
Construtor e herança
Quanto temos uma situação de herança entre classes, o construtor da classe pai é executado
antes do construtor da classe filho. Usamos a keyword super
para acessar o construtor da classe pai.
class Person {
String name;
int age;
Person(this.name, this.age);
}
class Programmer extends Person {
var language;
Programmer(String name, int age, String language): super(name, age){
this.language = language;
}
}
void main(List<String> args) {
var JohnDoe = Programmer('John Doe', 20, 'dart');
print(JohnDoe.runtimeType);
print(JohnDoe is Person); // true
print(JohnDoe is Programmer); // true
}
Construtor e validações
No dart também é permitido usar assert
para fazer a validação de valores que serão passados
ao construtor.
Nesse exemplo existe um assert
para fazer a verificação de entrada do atributo age
, caso
o valor for menos que 25
ocorrerá um erro.
class Hero {
var name, age;
Hero({this.name, this.age}) : assert(age > 25);
}
void main(List<String> args) {
var peter = Hero(name: 'Peter Parker', age: 23);
}
👇 Executando.
💀 app.dart': Failed assertion: line 4 pos 40: 'age > 25': is not true.
Para que o assert
funcione corretamente emitindo os erros é preciso habilitar a flag --enable-asserts
, exemplo:
dart run --enable-asserts app.dart
Redirecionamento de construtor
Também é possível redirecionar a responsabilidade da criação de um objeto de um construtor para o outro.
class Point{
var x;
var y;
// Construtor principal
Point(int this.x, int this.y);
// Construtor secundário redirecionando ao construtor principal
Point.origin() : this(0, 0);
}
void main(List<String> args) {
var origin = Point.origin();
print(origin);
}
Construtores constantes
Construtores constantes são construtores que criam objetos imutáveis.
Declarando os atributos como final
e o construtor como const
o objeto
não poderá ser alterado.
class Point{
final int x;
final int y;
const Point(this.x, this.y);
}
Métodos
Métodos são funções que determinam o comportamento do objeto.
class Calc {
int value = 0;
Calc(this.value);
add(int value) {
this.value += value;
return this;
}
sub(int value) {
this.value -= value;
return this;
}
mul(int value) {
this.value *= value;
return this;
}
div(int value) {
this.value ~/= value;
return this;
}
}
void main(List<String> args) {
Calc calc = Calc(0);
calc.add(10).sub(2).mul(3).div(2);
print(calc.value); //12
}
get
e set
são métodos especiais que permitem a leitura e escrita de atributos. Também
é possível criar métodos get
e set
para atributos que não foram declarados. Exemplo:
class Person {
String name;
int age;
Person(this.name, this.age);
//custom getter
String get description {
return 'My name is $name and I am $age years old';
}
//custom setter
set description(String description) {
var split = description.split(' ');
name = split[0];
age = int.parse(split[1]);
}
}
void main(List<String> args) {
var person = Person('John', 30);
print(person.description); //My name is John and I am 30 years old
}
Metodos de extensão
Métodos de extensão permitem adicionar novos métodos a uma classe sem precisar alterar a classe original.
// extensão para a classe int
extension IntExtension on int {
int add(int value) => this + value;
}
// extensão para a classe String
extension StringExtension on String {
String add(String value) => this + value;
}
class Person {
String name;
Person(this.name);
}
// extensão para a classe Person
extension PersonExtension on Person {
String get nameWithPrefix => 'Mr. $name';
}
void main(List<String> args) {
print(1.add(2)); //3
print('Hello'.add(' World')); //Hello World
var person = Person('John');
print(person.nameWithPrefix); //Mr. John
}
Sobrecarga de operadores
No dart é possível definir operadores especiais para um objeto.
Por exemplo: os operadores (+
, -
, >>
) podem ser configurados para
realizar operações customizadas.
Lista de operadores que podem ser sobrecarregados.
👇 | 👇 | 👇 | 👇 |
---|---|---|---|
> | + | | | >>> |
< | / | ^ | [] |
>= | ~/ | & | []= |
<= | * | << | ~ |
- | % | >> | == |
Vejamos o seguinte exemplo:
class Point{
final int x;
final int y;
Point(this.x, this.y);
}
Neste exemplo temos uma classe que define um objeto Point(x, y)
. Para somar dois pontos
precisamos fazer a seguinte operação:
p1 + p2 = (p1.x + p2.x, p1.y + p2.x)
Logo, não é possível fazer essa operação com o operado de soma padrão +
, precisamos criar o nosso
próprio operador +
para somar os pontos.
class Point{
final int x;
final int y;
Point(this.x, this.y);
// sobrecarregando o operador +
Point operator +(Point p) => Point(p.x + x, p.y + y);
}
void main(List<String> args) {
final p1 = Point(10, 20);
final p2 = Point(30, 40);
// somando os pontos de forma direta
Point p3 = p1 + p2;
print("${p3.x}, ${p3.y}"); //40, 60
}
Mais um exemplo usando string. Vamos criar os seguinte operadores:
- (
-
) Subtrain
caracteres do final de uma string. - (
/
) Divide a string emn
partes e retorna a primeira parte.
class Text{
String str;
Text(this.str);
// substrai (n) caracteres da string
Text operator -(int num) => Text(this.str.substring(0, this.str.length - 10));
// divide a string em (n) partes
Text operator /(int num) => Text(this.str.substring(0, (this.str.length / num).round()));
}
void main(List<String> args) {
var text = Text("dart is the best programming language");
var result1 = text - 10;
var result2 = text / 3;
print(result1.str); //dart is the best programmin
print(result2.str); //dart is the
}
Classes abstratas
Classes abstratas são classes que servem para definir o comportamento de outras classes através da herança. Esse tipo de classe não pode instanciada e seus métodos devem apenas ser assinados não implementados.
abstract class Hero {
String secretIdentity(String name);
}
class Person extends Hero {
String name;
Person(this.name);
@override
String secretIdentity(String identity) {
return "${this.name} is the ${identity}";
}
}
void main(List<String> args) {
var peter = Person('Petter Parker');
print(peter.secretIdentity('Spiderman'));
}
Interfaces Implicitas
Todas as classes no dart podem ser implementadas como interfaces, ou seja, toda classe no dart é uma interface implicita.
class PostgresAdapter {
String connectionString;
PostgresAdapter(this.connectionString);
void connect() {
print('Connecting to $connectionString');
}
}
class MySqlAdapter implements PostgresAdapter {
String connectionString;
MySqlAdapter(this.connectionString);
void connect() {
print('Connecting to $connectionString');
}
}
class Database {
PostgresAdapter adapter;
Database(this.adapter);
void connect() {
adapter.connect();
}
}
void main(List<String> args) {
var db1 = Database(PostgresAdapter('postgres://localhost:5432'));
var db2 = Database(MySqlAdapter('mysql://localhost:3306'));
db1.connect();
db2.connect();
}
Nesse exemplo a classe MysqlAdapter
implementa diretamente a classe PostgresAdapter
.
dessa forma é possível passar uma instância de MysqlAdapter
para a class Database
que,
em sua definição espera uma instância de PostgresAdapter
.
Herança
Herança é um recurso que permite que uma classe herde atributos e métodos de outra classe.
class Persona {
String name;
int age;
Persona(this.name, this.age);
}
class Hero extends Persona {
final String superpower;
Hero(String name, int age, this.superpower): super(name, age);
}
void main(List<String> args) {
var peter = Hero('Peter parker', 25, 'stick web');
print(peter.name);
print(peter.age);
print(peter.superpower);
}
Um ponto a ser observado no exemplo anterior, é que o construtor(super
) da classe pai
pode ser integrado ao construtor(Hero
) da classe filho para ser escrito de forma reduzida.
Mixins
Mixin é um recurso que permite que classes sejam extendidas sem a necessidade de herança.
mixin Info {
void showCarInfo(Car car) {
print('Car name: ${car.name}, Car color: ${car.color}');
}
void showPersonInfo(Person person) {
print('Person name: ${person.name}, Person age: ${person.age}');
}
}
class Person with Info {
String name;
int age;
Person(this.name, this.age);
}
class Car with Info {
String name;
String color;
Car(this.name, this.color);
}
Nesse exemplo duas classes distintas(Person
e Car
) estão sendo extendidas pelo mesmo mixin(Info
).
Atributos e métodos estáticos
Atributos/métodos estáticos podem ser acessados diretamente da classe sem a necessidade de uma instância.
class Console {
static final String info = "Console class";
static log(String arg){
print(arg);
}
static error(String arg){
print("Error: ${arg}");
}
}
void main(List<String> args) {
Console.log('hello world');
Console.log(Console.info);
}