Construtor
Na programação orientada a objetos baseada em classes, um construtor (abreviação: ctor) é um tipo especial de função chamada para a criação de um objeto. Ele prepara o novo objeto para uso, frequentemente aceitando argumentos que o construtor utiliza para definir variáveis de membro obrigatórias.
Um construtor assemelha-se a um método de instância, mas difere de um método por não ter um tipo de retorno explícito, não ser herdado implicitamente e, geralmente, possuir regras diferentes para modificadores de escopo. Os construtores frequentemente têm o mesmo nome da classe declarada. Eles têm a tarefa de inicializar os membros de dados do objeto e de estabelecer a invariante da classe, falhando caso a invariante seja inválida. Um construtor devidamente escrito deixa o objeto resultante em um estado válido. Objetos imutáveis devem ser inicializados em um construtor.
A maioria das linguagens permite a sobrecarga do construtor, de modo que pode haver mais de um construtor para uma classe, com parâmetros diferentes. Algumas linguagens levam em conta alguns tipos especiais de construtores. Construtores, que utilizam concretamente uma única classe para criar objetos e retornar uma nova instância da mesma, são abstraídos por fábricas (factories), que também criam objetos, mas podem fazê-lo de várias maneiras, usando múltiplas classes ou diferentes esquemas de alocação, como um pool de objetos.
Tipos
[editar | editar código]Construtores parametrizados
[editar | editar código]Construtores que podem receber pelo menos um argumento são denominados construtores parametrizados. Quando um objeto é declarado em um construtor parametrizado, os valores iniciais devem ser passados como argumentos para a função construtora. A forma normal de declaração de objetos pode não funcionar. Os construtores podem ser chamados de forma explícita ou implícita. O método de chamar o construtor implicitamente também é chamado de método abreviado (shorthand).
class Point {
private:
int x;
int y;
public:
Point() = default;
Point(int x, int y):
x{x}, y{y} {} // Construtor parametrizado
};
Point p = Point(0, 50); // Chamada explícita.
Point p2(0, 50); // Chamada implícita.
Construtores padrão
[editar | editar código]Se o programador não fornecer um construtor para uma classe instanciável, o compilador (como o do Java) insere um construtor padrão no código. Este construtor não é encontrado no código-fonte (o arquivo .java), pois é inserido durante a compilação e existe no arquivo .class. O comportamento do construtor padrão depende da linguagem; ele pode inicializar os membros de dados com zero ou outros valores predefinidos, ou não fazer absolutamente nada. Em Java, um "construtor padrão" refere-se a um construtor sem argumentos (nulário) que é gerado automaticamente se nenhum outro construtor for definido. Todos os campos são deixados com seu valor inicial de 0 (tipos inteiros), 0.0 (tipos de ponto flutuante), false (tipo booleano) ou null (tipos de referência), etc.
class Point {
private:
int x;
int y;
public:
Point(int x = 0, int y = 0); // Construtor padrão (com valores padrão).
};
Construtores de cópia
[editar | editar código]Assim como o C++, o Java também suporta "Construtores de Cópia". No entanto, diferentemente do C++, o Java não cria um construtor de cópia padrão se nenhum for especificado. Os construtores de cópia definem as ações executadas pelo compilador ao copiar objetos de uma classe. Um construtor de cópia possui um parâmetro formal que é do tipo da própria classe. Ele é usado para criar uma cópia de um objeto já existente da mesma classe.
Construtores de conversão
[editar | editar código]Os construtores de conversão fornecem um meio para o compilador criar implicitamente um objeto pertencente a uma classe com base em um objeto de um tipo diferente. Esses construtores são geralmente invocados implicitamente para converter argumentos ou operandos para um tipo apropriado, mas também podem ser chamados explicitamente.
Construtores de movimento
[editar | editar código]No C++, os construtores de movimento (move constructors) recebem uma "referência de Rvalue" para um objeto da classe e são usados para implementar a transferência de propriedade (ownership transfer) dos recursos do objeto de parâmetro.
Organização da memória
[editar | editar código]Em Java, C# e VB .NET, o construtor cria objetos de tipo de referência no heap, enquanto tipos primitivos (como int, double, etc.) são armazenados na pilha (embora algumas linguagens permitam a alocação manual de objetos na pilha por meio de um modificador stackalloc). O VB .NET e o C# também permitem o uso do operador new para criar objetos de tipo de valor, mas esses objetos são criados na pilha, independentemente de o operador ser usado ou não. Nessas linguagens, a destruição do objeto ocorre quando ele não possui mais referências, sendo então removido pelo coletor de lixo (garbage collector).
No C++, os objetos são criados na pilha quando o construtor é invocado sem o operador new, e criados no heap quando o construtor é invocado com o operador new (que retorna um ponteiro para o objeto). Objetos na pilha são excluídos implicitamente quando saem do escopo, enquanto objetos no heap devem ser excluídos implicitamente por um destrutor ou explicitamente usando o operador delete. Ao utilizar o idioma "Recurso de Aquisição é Inicialização" (RAII), o gerenciamento de recursos pode ser amplamente simplificado.
Exemplos
[editar | editar código]JavaScript e TypeScript
[editar | editar código]A partir do ES6, o JavaScript passou a ter construtores diretos como muitas outras linguagens de programação. Eles são escritos da seguinte forma:
class FooBar {
constructor(baz) {
this.baz = baz;
}
}
A classe pode ser instanciada assim:
const foo = new FooBar('7');
O equivalente a isso antes do ES6 era criar uma função que instancia um objeto da seguinte forma:
function FooBar (baz) {
this.baz = baz;
}
A instanciação é feita da mesma maneira que no exemplo anterior.
O equivalente em TypeScript seria:
class FooBar {
baz: string;
constructor(baz: string) {
this.baz = baz;
}
}
const foo: FooBar = new FooBar('7');
C
[editar | editar código]Na linguagem C, não existem construtores. No entanto, é comum definir funções que criam um objeto (geralmente alocado dinamicamente), simulando o comportamento de um construtor.
#include <stdlib.h>
#include <string.h>
typedef struct {
char* name;
int age;
} Person;
// Este método cria uma instância de Person
Person* createPerson(const char name[], int age) {
Person* p = (Person*)malloc(sizeof(Person));
if (!p) {
return NULL;
}
p->name = strdup(name); // Aloca e copia a string
p->age = age;
return p;
}
// Este método destrói uma instância de Person
void destroyPerson(Person* p) {
if (p) {
free(p->name);
free(p);
}
}
class Ponto
{
public:
// Constructor
Ponto()
: x ( 0 )
: y ( 0 )
{
}
float x; // ponto no eixo das abscissas
float y; // ponto no eixo das ordenadas
};
class Ponto:
ponto1, ponto2, nome = None, None, None
def __init__(self, nome):
# referências do construtor
self.ponto1 = None
self.ponto2 = None
self.nome = nome
Java
[editar | editar código]Em Java, os construtores diferem de outros métodos nos seguintes aspectos:
- Construtores nunca possuem um tipo de retorno explícito.
- Construtores não podem ser invocados diretamente (a palavra-chave "
new" os invoca). - Construtores não devem possuir modificadores que não sejam de acesso (como
static,final, etc.).
Os construtores em Java realizam as seguintes tarefas, na ordem indicada:
- Chamam o construtor da superclasse (se nenhum for definido explicitamente, o compilador insere uma chamada ao construtor padrão da superclasse).
- Inicializam as variáveis de membro com os valores especificados.
- Executam o corpo do construtor.
O Java permite que usuários chamem um construtor dentro de outro usando a palavra-chave this(), mas ela deve ser obrigatoriamente a primeira instrução do bloco.[1]
class X {
public X() { // Construtor não parametrizado
this(1); // Chamada de outro construtor da mesma classe
System.out.println("Chamando o construtor padrão");
}
public X(int a) { // Construtor parametrizado
System.out.println("Chamando o construtor parametrizado");
}
}
public class Exemplo {
public static void main(String[] args) {
X x = new X();
}
}
A linguagem também fornece acesso ao construtor da superclasse através da palavra-chave super.
class X {
private int data;
public X() {
this(1);
}
// Sobrecarga de construtor
public X(int input) {
data = input;
}
}
class Y extends X {
private int data2;
public Y() {
super(); // Chama o construtor de X
data2 = 1;
}
public Y(int input1, int input2) {
super(input1); // Chama o construtor parametrizado de X
data2 = input2;
}
}
public class Exemplo {
public static void main(String[] args) {
Y y = new Y(42, 43);
}
}
Um construtor que não recebe argumentos é chamado de construtor "sem argumentos" (no-arg constructor).[2]
interface
type
Exemplo = class
private
//declaração da campos.
FX: Integer;
FY: Integer;
public
//declaração do construtor.
constructor Create;
end;
implementation
//definição do construtor.
constructor Exemplo.Create;
begin
FX := 0;
FY := 0;
end;
end.
Public Class Exemplo
'Declaração de variável de instância
Private variavel As Single
'construtor padrão da classe
Public Sub New()
variavel = 0
End Sub
'sobrecarga do contrutor com um argumento
Public Sub New(ByVal variavel As Single)
Me.variavel = variavel
End Sub
End Class
class Exemplo
{
// Exemplo de parametros
public function __construct($Filtro = '', $Paginar = false, $Ordem = 'id_tabela')
{
$this->funcoes =& $GLOBALS['arquivo'];
$this->bancoDeDados =& $GLOBALS['arquivo'];
if (!empty($Filtro))
{
return $this->consultar($Filtro, $Paginar, $Ordem); // consultar é um metodo da classe
}
}
}
Em Ruby, os construtores são criados definindo-se um método chamado initialize. Este método é executado para inicializar cada nova instância da classe.
irb(main):001:0> class ExampleClass
irb(main):002:1> def initialize
irb(main):003:2> puts "Olá!"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> ExampleClass.new
Olá!
=> #<ExampleClass:0x007fb3f4299118>
Rust
[editar | editar código]A linguagem Rust não possui construtores no sentido estrito da programação orientada a objetos, mas frequentemente as estruturas (structs) possuem um método new() que atua essencialmente como um construtor. O tipo de retorno é geralmente indicado como Self.
struct Point {
x: i32,
y: i32,
}
impl Point {
// Método que atua como um construtor
pub fn new(x: i32, y: i32) -> Self {
Point { x, y }
}
}
fn main() {
let p: Point = Point::new(10, 20);
println!("Ponto está em ({}, {})", p.x, p.y);
}
Eiffel
[editar | editar código]Em Eiffel, as rotinas que inicializam novos objetos são chamadas de procedimentos de criação (creation procedures). Eles possuem as seguintes características:
- Não possuem tipo de retorno explícito (pela própria definição de "procedimento").[a]
- São nomeados.
- São designados por nome como procedimentos de criação no texto da classe.
- Podem ser invocados explicitamente para reinicializar objetos existentes.
- Toda classe efetiva (ou seja, concreta ou não abstrata) deve designar pelo menos um procedimento de criação.
- Devem deixar o objeto recém-inicializado em um estado que satisfaça a invariante da classe.[b]
Embora a criação de objetos envolva algumas sutilezas,[3] a criação de um atributo com uma declaração típica x: T expressa em uma instrução de criação create x.make consiste na seguinte sequência de etapas:
1. Criar uma nova instância direta do tipo T.[c]
2. Executar o procedimento de criação make na instância recém-criada.
3. Anexar o objeto recém-inicializado à entidade x.
No primeiro trecho abaixo, a classe POINT é definida. O procedimento make é codificado após a palavra-chave feature.
A palavra-chave create introduz uma lista de procedimentos que podem ser usados para inicializar instâncias. Neste caso, a lista inclui default_create, um procedimento com implementação vazia herdado da classe ANY, e o procedimento make codificado na classe.
class
POINT
create
default_create, make
feature
make (a_x_value: REAL; a_y_value: REAL)
do
x := a_x_value
y := a_y_value
end
x: REAL
-- Coordenada X
y: REAL
-- Coordenada Y
...
No segundo trecho, uma classe que é cliente de POINT possui declarações my_point_1 e my_point_2 do tipo POINT.
No código procedural, my_point_1 é criado na origem (0.0, 0.0). Como nenhum procedimento de criação é especificado, o procedimento default_create herdado da classe ANY é utilizado. Esta linha poderia ter sido codificada como create my_point_1.default_create. Apenas procedimentos nomeados como procedimentos de criação podem ser usados em uma instrução com a palavra-chave create.
Em seguida, há uma instrução de criação para my_point_2, fornecendo valores iniciais para suas coordenadas. A terceira instrução faz uma chamada de instância comum ao procedimento make para reinicializar a instância anexada a my_point_2 com valores diferentes.
my_point_1: POINT
my_point_2: POINT
...
create my_point_1
create my_point_2.make (3.0, 4.0)
my_point_2.make (5.0, 8.0)
...
OCaml
[editar | editar código]Em OCaml, existe apenas um construtor. Os parâmetros são definidos logo após o nome da classe; eles podem ser usados para inicializar variáveis de instância e permanecem acessíveis em toda a classe. Um método oculto e anônimo chamado initializer permite avaliar uma expressão imediatamente após o objeto ter sido construído.[4]
class person first_name last_name =
object
val full_name = first_name ^ " " ^ last_name
initializer
print_endline("Olá, eu sou " ^ full_name ^ ".")
method get_last_name = last_name
end;;
let alonzo = new person "Alonzo" "Church" in (*Olá, eu sou Alonzo Church.*)
print_endline alonzo#get_last_name (*Church*)
Raku
[editar | editar código]Em Raku, é possível omitir ainda mais código repetitivo (boilerplate), visto que um método new padrão é herdado, e os atributos podem especificar se podem ser definidos, redefinidos ou se são obrigatórios. Além disso, qualquer funcionalidade extra do construtor pode ser incluída em um método BUILD, que é chamado para permitir a inicialização personalizada. Um método TWEAK pode ser especificado para processar atributos que já foram (implicitamente) inicializados.
class Person {
has Str $.first-name is required; # O nome (string) só pode ser definido na
# construção (o . significa "público").
has Str $.last-name is required; # O sobrenome (string) só pode ser definido na
# construção (um ! significaria "privado").
has Int $.age is rw; # A idade (inteiro) pode ser modificada após
# a construção ('rw') e não é obrigatória
# durante a instanciação do objeto.
# Cria um método 'full-name' que retorna o nome completo.
# Este método pode ser acessado fora da classe.
method full-name { $!first-name.tc ~ " " ~ $!last-name.tc }
# Cria um método 'has-age' que retorna verdadeiro se a idade foi definida.
# Como é usado apenas internamente, é declarado como "privado" com o prefixo !
method !has-age { self.age.defined }
# Verifica requisitos personalizados
method TWEAK {
if self!has-age && $!age < 18 { # Proíbe menores de 18
die "Nenhuma pessoa com menos de 18 anos";
}
}
}
A classe Person é instanciada desta forma:
my $p0 = Person.new( first-name => 'Sam', last-name => 'Ashe', age => 42 );
my $p1 = Person.new( first-name => 'grace', last-name => 'hopper' );
say $p1.full-name(); # SAÍDA: «Grace Hopper»
Alternativamente, os parâmetros nomeados podem ser especificados usando a sintaxe de par de dois pontos (colon-pair) do Raku:
my $p0 = Person.new( :first-name<Sam>, :last-name<Ashe>, :age(42) );
my $p1 = Person.new( :first-name<Grace>, :last-name<Hopper> );
Caso você tenha variáveis com nomes idênticos aos parâmetros nomeados, pode usar um atalho que utiliza o nome da variável para o parâmetro:
my $first-name = "Sam";
my $last-name = "Ashe";
my $age = 42;
my $p0 = Person.new( :$first-name, :$last-name, :$age );
Perl 5
[editar | editar código]No Perl versão 5, por padrão, os construtores são métodos fábrica (factory methods), ou seja, métodos que criam e retornam o objeto — o que concretamente significa criar e retornar uma referência "abençoada" (blessed reference). Um objeto típico é uma referência a um hash, embora referências a outros tipos também sejam usadas raramente. Por convenção, o único construtor é nomeado new, embora seja permitido nomeá-lo de outra forma ou ter múltiplos construtores. Por exemplo, uma classe Person pode ter um construtor chamado new, um construtor new_from_file que lê um arquivo para obter os atributos, e um new_from_person que utiliza outro objeto Person como modelo.
package Person;
# Em Perl, os construtores são chamados de 'new' por convenção.
sub new {
# O nome da classe é passado implicitamente como o primeiro argumento.
my $class = shift;
# Valores de atributos padrão, se houver.
my %defaults = ( foo => "bar" );
# Inicializa os atributos como uma combinação de valores padrão e argumentos passados.
my $self = { %defaults, @_ };
# Verifica argumentos obrigatórios, invariantes de classe, etc.
if ( not defined $self->{first_name} ) {
die "Atributo obrigatório ausente em Person->new(): first_name";
}
if ( not defined $self->{last_name} ) {
die "Atributo obrigatório ausente em Person->new(): last_name";
}
if ( defined $self->{age} and $self->{age} < 18 ) {
die "Valor de atributo inválido em Person->new(): age < 18";
}
# Perl faz um objeto pertencer a uma classe através da função 'bless'.
bless $self, $class;
return $self;
}
1;
Perl 5 com Moose
[editar | editar código]No sistema de objetos Moose para Perl, a maior parte desse código repetitivo (boilerplate) pode ser omitida. Um new padrão é criado automaticamente, permitindo especificar atributos e se eles podem ser definidos, redefinidos ou se são obrigatórios. Além disso, qualquer funcionalidade extra do construtor pode ser incluída em um método BUILD, que o construtor gerado pelo Moose chamará após verificar os argumentos. Um método BUILDARGS pode ser especificado para manipular argumentos do construtor que não estejam no formato de referência de hash ou par chave => valor.
package Person;
# Habilita a construção de objetos no estilo Moose
use Moose;
# first_name (string) só pode ser definido no momento da construção ('ro')
has first_name => (is => 'ro', isa => 'Str', required => 1);
# last_name (string) só pode ser definido no momento da construção ('ro')
has last_name => (is => 'ro', isa => 'Str', required => 1);
# age (Inteiro) pode ser modificado após a construção ('rw') e não é obrigatório.
# Também cria um método 'has_age' que retorna verdadeiro se a idade foi definida.
has age => (is => 'rw', isa => 'Int', predicate => 'has_age');
# Verifica requisitos personalizados
sub BUILD {
my $self = shift;
if ($self->has_age && $self->age < 18) { # Proíbe menores de 18
die "Nenhuma pessoa com menos de 18 anos";
}
}
1;
Em ambos os casos, a classe Person é instanciada desta forma:
use Person;
my $p = Person->new( first_name => 'Sam', last_name => 'Ashe', age => 42 );
Notas
[editar | editar código]- ↑ As rotinas em Eiffel são ou procedimentos ou funções. Procedimentos nunca possuem tipo de retorno; funções sempre possuem.
- ↑ Como a invariante da classe herdada deve ser satisfeita, não há uma chamada obrigatória aos construtores dos pais.
- ↑ O padrão Eiffel exige que os campos sejam inicializados no primeiro acesso, portanto, não é necessário realizar a inicialização padrão de campos durante a criação do objeto.
Referências
- ↑ «Details on Constructor in java»
- ↑ «Providing Constructors for Your Classes». Oracle Corporation. 2013. Consultado em 20 de dezembro de 2013
- ↑ «Eiffel ISO/ECMA specification document»
- ↑ «OCaml - The OCaml Manual». ocaml.org