Menu
Coddy logo textTech

Estendendo o Construtor

Parte da seção Object Oriented Programming do Journey de Lua da Coddy — lição 33 de 70.

Quando uma classe filha herda de uma classe pai, você geralmente deseja que o construtor da filha inicialize tudo o que o pai inicializaria — além de quaisquer campos adicionais específicos da filha. Em vez de duplicar código, você pode chamar o construtor do pai diretamente.

A técnica envolve o uso do nome da classe pai para invocar seu método :new(), passando a classe pai como o primeiro argumento. Você pode fazer isso com a sintaxe de ponto: Vehicle.new(Vehicle, speed), ou de forma equivalente com a sintaxe de dois pontos: Vehicle:new(speed) — ambas são válidas e produzem o mesmo resultado. Isso cria o objeto base com toda a inicialização do pai, que você então customiza para o filho.

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

-- Car herda de Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Chama o construtor pai
    obj.brand = brand                         -- Adiciona campo específico do filho
    setmetatable(obj, Car)                    -- Re-vincula ao Car
    return obj
end

Observe as etapas principais no construtor filho: primeiro, chamamos Vehicle.new(Vehicle, speed) (ou equivalentemente Vehicle:new(speed)) para obter um objeto com o campo speed já definido. Em seguida, adicionamos o campo brand específico do filho. Finalmente, alteramos a metatabela de Vehicle para Car para que a instância procure os métodos na classe filha primeiro.

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- Saída: 120
print(myCar.brand)  -- Saída: Toyota

Este padrão mantém seu código DRY — a lógica de inicialização compartilhada reside no pai, enquanto cada filho lida apenas com seus atributos únicos.

challenge icon

Desafio

Fácil

Vamos construir uma hierarquia de criaturas onde uma classe filha estende o construtor de sua classe pai! Você criará uma classe pai Animal e uma classe filha Bird, onde o construtor de Bird chama o construtor de Animal para lidar com a inicialização compartilhada antes de adicionar seu próprio atributo exclusivo.

Você organizará seu código em três arquivos:

  • Animal.lua: Defina sua classe pai com um construtor :new(name) que armazena o nome do animal. Inclua um método :describe() que imprime o nome do animal. Configure o padrão de classe padrão com __index e retorne a classe.
  • Bird.lua: Defina sua classe filha que herda de Animal. O construtor :new(name, wingspan) de Bird deve chamar o construtor pai usando Animal.new(Animal, name) para inicializar o nome, depois adicionar o campo wingspan ao objeto e, finalmente, vincular novamente a metatabela a Bird. Inclua um método :info() que imprime o nome e a envergadura em um formato específico.
  • main.lua: Importe seu módulo Bird, leia duas entradas (nome e envergadura), crie uma instância de Bird e chame ambos os métodos para demonstrar que a funcionalidade herdada e a específica da classe filha funcionam juntas.

Você receberá duas entradas:

  1. O nome do pássaro
  2. A envergadura do pássaro (um número)

No seu arquivo principal, crie um Bird com o nome e a envergadura fornecidos. Em seguida, chame :describe() seguido de :info() em linhas separadas.

O método :describe() (herdado de Animal) deve imprimir:

Animal: {name}

O método :info() (definido em Bird) deve imprimir:

{name} has a wingspan of {wingspan}

Por exemplo, se as entradas forem Eagle e 200, a saída deve ser:

Animal: Eagle
Eagle has a wingspan of 200

A chave aqui é que o construtor de Bird não duplica a inicialização do nome — ele delega esse trabalho ao construtor de Animal e, em seguida, adiciona apenas o que é exclusivo dos pássaros. Isso mantém seu código DRY enquanto constrói uma cadeia de herança adequada!

Folha de consulta

Para chamar o construtor de uma classe pai a partir de uma classe filha, use o nome da classe pai com a sintaxe de ponto, passando a própria classe pai como o primeiro argumento:

local obj = ParentClass.new(ParentClass, parentArgs)

Após chamar o construtor pai, adicione campos específicos da classe filha e vincule novamente a metatabela à classe filha:

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Chama o construtor pai
    obj.brand = brand                         -- Adiciona campo específico da filha
    setmetatable(obj, Car)                    -- Vincula novamente ao Car
    return obj
end

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- Saída: 120
print(myCar.brand)  -- Saída: Toyota

Este padrão mantém a lógica de inicialização DRY — a configuração compartilhada reside no pai, enquanto os filhos lidam apenas com seus atributos exclusivos.

Experimente você mesmo

-- main.lua: Ponto de entrada

local Bird = require('Bird')

-- Ler entradas
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO: Criar uma instância de Bird com o nome e envergadura fornecidos

-- TODO: Chamar :describe() no pássaro (herdado de Animal)

-- TODO: Chamar :info() no pássaro (definido em Bird)
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Object Oriented Programming