Programação orientada a objetos
Nestes exemplos, o código Lua gerado pode parecer avassalador. É melhor focar primeiro no significado do código YueScript e depois olhar o código Lua se desejar conhecer os detalhes da implementação.
Uma classe simples:
class Inventory new: => @items = {}
add_item: (name) => if @items[name] @items[name] += 1 else @items[name] = 1class Inventory new: => @items = {}
add_item: (name) => if @items[name] @items[name] += 1 else @items[name] = 1Uma classe é declarada com uma instrução class seguida de uma declaração semelhante a tabela onde todos os métodos e propriedades são listados.
A propriedade new é especial pois se tornará o construtor.
Observe como todos os métodos da classe usam a sintaxe de função seta fat. Ao chamar métodos em uma instância, a própria instância é enviada como primeiro argumento. A seta fat cuida da criação do argumento self.
O prefixo @ em um nome de variável é abreviação para self.. @items torna-se self.items.
O bloco da classe também suporta campos de metatable escrevendo chaves de metamétodo entre <>, como <tostring>.
class User new: (@name) => <tostring>: => "User(#{@name})"
print tostring User "Yue"class User new: (@name) => <tostring>: => "User(#{@name})"
print tostring User "Yue"Criar uma instância da classe é feito chamando o nome da classe como uma função.
inv = Inventory!inv\add_item "t-shirt"inv\add_item "pants"inv = Inventory!inv\add_item "t-shirt"inv\add_item "pants"Como a instância da classe precisa ser enviada aos métodos quando são chamados, o operador \ é usado.
Todas as propriedades de uma classe são compartilhadas entre as instâncias. Isso é bom para funções, mas para outros tipos de objetos, resultados indesejados podem ocorrer.
Considere o exemplo abaixo, a propriedade clothes é compartilhada entre todas as instâncias, então modificações nela em uma instância aparecerão em outra:
class Person clothes: [] give_item: (name) => table.insert @clothes, name
a = Person!b = Person!
a\give_item "pants"b\give_item "shirt"
-- vai imprimir tanto pants quanto shirtprint item for item in *a.clothesclass Person clothes: [] give_item: (name) => table.insert @clothes, name
a = Person!b = Person!
a\give_item "pants"b\give_item "shirt"
-- vai imprimir tanto pants quanto shirtprint item for item in *a.clothesA forma correta de evitar esse problema é criar o estado mutável do objeto no construtor:
class Person new: => @clothes = []class Person new: => @clothes = []Herança
Seção intitulada “Herança”A palavra-chave extends pode ser usada em uma declaração de classe para herdar as propriedades e métodos de outra classe.
class BackPack extends Inventory size: 10 add_item: (name) => if #@items > size then error "backpack is full" super nameclass BackPack extends Inventory size: 10 add_item: (name) => if #@items > size then error "backpack is full" super nameAqui estendemos nossa classe Inventory e limitamos a quantidade de itens que ela pode carregar.
Neste exemplo, não definimos um construtor na subclasse, então o construtor da classe pai é chamado quando criamos uma nova instância. Se definirmos um construtor, podemos usar o método super para chamar o construtor pai.
Sempre que uma classe herda de outra, ela envia uma mensagem à classe pai chamando o método __inherited na classe pai se ele existir. A função recebe dois argumentos: a classe que está sendo herdada e a classe filha.
class Shelf @__inherited: (child) => print @__name, "was inherited by", child.__name
-- vai imprimir: Shelf was inherited by Cupboardclass Cupboard extends Shelfclass Shelf @__inherited: (child) => print @__name, "was inherited by", child.__name
-- vai imprimir: Shelf was inherited by Cupboardclass Cupboard extends Shelfsuper é uma palavra-chave especial que pode ser usada de duas formas diferentes: pode ser tratado como um objeto, ou pode ser chamado como uma função. Só tem funcionalidade especial quando está dentro de uma classe.
Quando chamado como função, chamará a função de mesmo nome na classe pai. O self atual será automaticamente passado como primeiro argumento. (Como visto no exemplo de herança acima)
Quando super é usado como valor normal, é uma referência ao objeto da classe pai.
Pode ser acessado como qualquer objeto para recuperar valores na classe pai que possam ter sido sombreados pela classe filha.
Quando o operador de chamada \ é usado com super, self é inserido como primeiro argumento em vez do valor do próprio super. Ao usar . para recuperar uma função, a função bruta é retornada.
Alguns exemplos de uso de super de diferentes formas:
class MyClass extends ParentClass a_method: => -- os seguintes têm o mesmo efeito: super "hello", "world" super\a_method "hello", "world" super.a_method self, "hello", "world"
-- super como valor é igual à classe pai: assert super == ParentClassclass MyClass extends ParentClass a_method: => -- os seguintes têm o mesmo efeito: super "hello", "world" super\a_method "hello", "world" super.a_method self, "hello", "world"
-- super como valor é igual à classe pai: assert super == ParentClasssuper também pode ser usado no lado esquerdo de um Function Stub. A única diferença principal é que, em vez da função resultante estar vinculada ao valor de super, ela está vinculada a self.
Cada instância de uma classe carrega seu tipo consigo. Isso é armazenado na propriedade especial __class. Esta propriedade contém o objeto da classe. O objeto da classe é o que chamamos para construir uma nova instância. Também podemos indexar o objeto da classe para recuperar métodos e propriedades da classe.
b = BackPack!assert b.__class == BackPack
print BackPack.size -- imprime 10b = BackPack!assert b.__class == BackPack
print BackPack.size -- imprime 10Objetos de classe
Seção intitulada “Objetos de classe”O objeto da classe é o que criamos quando usamos uma instrução class. O objeto da classe é armazenado em uma variável com o mesmo nome da classe.
O objeto da classe pode ser chamado como uma função para criar novas instâncias. É assim que criamos instâncias de classes nos exemplos acima.
Uma classe é composta por duas tabelas. A própria tabela da classe e a tabela base. A base é usada como metatable para todas as instâncias. Todas as propriedades listadas na declaração da classe são colocadas na base.
A metatable do objeto da classe lê propriedades da base se não existirem no objeto da classe. Isso significa que podemos acessar funções e propriedades diretamente da classe.
É importante notar que atribuir ao objeto da classe não atribui à base, então não é uma forma válida de adicionar novos métodos às instâncias. Em vez disso, a base deve ser alterada explicitamente. Veja o campo __base abaixo.
O objeto da classe tem algumas propriedades especiais:
O nome da classe quando foi declarada é armazenado como string no campo __name do objeto da classe.
print BackPack.__name -- imprime Backpackprint BackPack.__name -- imprime BackpackO objeto base é armazenado em __base. Podemos modificar esta tabela para adicionar funcionalidade a instâncias que já foram criadas e às que ainda serão criadas.
Se a classe estende de algo, o objeto da classe pai é armazenado em __parent.
Variáveis de classe
Seção intitulada “Variáveis de classe”Podemos criar variáveis diretamente no objeto da classe em vez da base usando @ na frente do nome da propriedade em uma declaração de classe.
class Things @some_func: => print "Hello from", @__name
Things\some_func!
-- variáveis de classe não visíveis em instânciasassert Things().some_func == nilclass Things @some_func: => print "Hello from", @__name
Things\some_func!
-- variáveis de classe não visíveis em instânciasassert Things().some_func == nilEm expressões, podemos usar @@ para acessar um valor armazenado no **class de self. Assim, @@hello é abreviação para self.**class.hello.
class Counter @count: 0
new: => @@count += 1
Counter!Counter!
print Counter.count -- imprime 2class Counter @count: 0
new: => @@count += 1
Counter!Counter!
print Counter.count -- imprime 2A semântica de chamada de @@ é semelhante a @. Chamar um nome @@ passará a classe como primeiro argumento usando a sintaxe de dois pontos do Lua.
@@hello 1,2,3,4@@hello 1,2,3,4Instruções de declaração de classe
Seção intitulada “Instruções de declaração de classe”No corpo de uma declaração de classe, podemos ter expressões normais além de pares chave/valor. Neste contexto, self é igual ao objeto da classe.
Aqui está uma forma alternativa de criar variável de classe comparada ao descrito acima:
class Things @class_var = "hello world"class Things @class_var = "hello world"Estas expressões são executadas após todas as propriedades terem sido adicionadas à base.
Todas as variáveis declaradas no corpo da classe são locais às propriedades da classe. Isso é conveniente para colocar valores privados ou funções auxiliares que apenas os métodos da classe podem acessar:
class MoreThings secret = 123 log = (msg) -> print "LOG:", msg
some_method: => log "hello world: " .. secretclass MoreThings secret = 123 log = (msg) -> print "LOG:", msg
some_method: => log "hello world: " .. secretValores @ e @@
Seção intitulada “Valores @ e @@”Quando @ e @@ são prefixados na frente de um nome, eles representam, respectivamente, esse nome acessado em self e self.__class.
Se forem usados sozinhos, são aliases para self e self.__class.
assert @ == selfassert @@ == self.__classassert @ == selfassert @@ == self.__classPor exemplo, uma forma rápida de criar uma nova instância da mesma classe a partir de um método de instância usando @@:
some_instance_method = (...) => @@ ...some_instance_method = (...) => @@ ...Promoção de propriedade no construtor
Seção intitulada “Promoção de propriedade no construtor”Para reduzir o código repetitivo na definição de objetos de valor simples. Você pode escrever uma classe simples como:
class Something new: (@foo, @bar, @@biz, @@baz) =>
-- O que é abreviação para
class Something new: (foo, bar, biz, baz) => @foo = foo @bar = bar @@biz = biz @@baz = bazclass Something new: (@foo, @bar, @@biz, @@baz) =>
-- O que é abreviação para
class Something new: (foo, bar, biz, baz) => @foo = foo @bar = bar @@biz = biz @@baz = bazVocê também pode usar esta sintaxe para uma função comum para inicializar os campos de um objeto.
new = (@fieldA, @fieldB) => @obj = new {}, 123, "abc"print objnew = (@fieldA, @fieldB) => @obj = new {}, 123, "abc"print objExpressões de classe
Seção intitulada “Expressões de classe”A sintaxe de classe também pode ser usada como expressão que pode ser atribuída a uma variável ou retornada explicitamente.
x = class Bucket drops: 0 add_drop: => @drops += 1x = class Bucket drops: 0 add_drop: => @drops += 1Classes anônimas
Seção intitulada “Classes anônimas”O nome pode ser omitido ao declarar uma classe. O atributo __name será nil, a menos que a expressão da classe esteja em uma atribuição. O nome no lado esquerdo da atribuição é usado em vez de nil.
BigBucket = class extends Bucket add_drop: => @drops += 10
assert Bucket.__name == "BigBucket"BigBucket = class extends Bucket add_drop: => @drops += 10
assert Bucket.__name == "BigBucket"Você pode até omitir o corpo, significando que pode escrever uma classe anônima em branco assim:
x = classx = classMistura de classes
Seção intitulada “Mistura de classes”Você pode fazer mistura com a palavra-chave using para copiar funções de uma tabela simples ou de um objeto de classe predefinido para sua nova classe. Ao fazer mistura com uma tabela simples, você pode sobrescrever a função de indexação da classe (metamétodo __index) para sua implementação personalizada. Ao fazer mistura com um objeto de classe existente, os metamétodos do objeto da classe não serão copiados.
MyIndex = __index: var: 1
class X using MyIndex func: => print 123
x = X!print x.var
class Y using X
y = Y!y\func!
assert y.__class.__parent ~= X -- X não é pai de YMyIndex = __index: var: 1
class X using MyIndex func: => print 123
x = X!print x.var
class Y using X
y = Y!y\func!
assert y.__class.__parent ~= X -- X não é pai de Y