apenas uma figura bonitinha

9.  Classes

 

Até agora, nós vimos muitos tipos diferentes de objetos, ou classes: strings, inteiros, ponto flutuante, vetores e alguns objetos especiais (true, false e nil), que vamos voltar a falar mais tarde. Em Ruby, todas essas classes sempre começam com maiúsculas: String, Integer (Inteiros), Float (Ponto Flutuante), Array (Vetores) e etc. Geralmente, se queremos criar um novo objeto de uma certa classe, nós usamos o new:

a = Array.new  + [12345]  #  Adição de Vetores.
b = String.new + 'olá'  #  Adição com Strings.
c = Time.new

puts 'a = '+a.to_s
puts 'b = '+b.to_s
puts 'c = '+c.to_s
a = 12345
b = olá
c = Fri Aug 28 11:24:25 -0300 2015

Como nós podemos criar vetores e strings usando [...] e '...', respectivamente, nós raramente usamos o new para isso (De qualquer forma, não está muito claro, no exemplo anterior, que String.new cria uma string vazia e que Array.new cria um vetor vazio). Números, porém, são uma exceção: você não pode criar um inteiro usando Integer.new. Você apenas tem que digitar o número.

A classe Time

Está bem, e a classe Time? Objetos Time representam momentos de tempo. Você pode adicionar (ou subtrair) números para (ou de) tempos para conseguir novos instantes: adicionando 1.5 a um instante, retorna um novo instante de um segundo e meio depois:

tempo  = Time.new    #  O instante em que você carrega esta página.
tempo2 = tempo + 60  #  Um minuto depois.

puts tempo
puts tempo2
Fri Aug 28 11:24:25 -0300 2015
Fri Aug 28 11:25:25 -0300 2015

Você pode, também, fazer um tempo para um momento específico usando Time.mktime:

puts Time.mktime(2000, 1, 1)          #  Ano 2000.
puts Time.mktime(1976, 8, 3, 10, 11)  #  Ano em que nasci.
Sat Jan 01 00:00:00 -0200 2000
Tue Aug 03 10:11:00 -0300 1976

Nota: quando eu nasci, estava em uso o Horário de Verão do Pacífico (PDT, em Inglês). Quanto o ano 2000 chegou, porém, estava em uso o Horário Padrão do Pacífico (PST, em Inglês), pelo menos para nós, da costa Oeste. Os parênteses servem para agrupar os parâmetros para o mktime. Quanto mais parâmetros você adicionar, mais preciso o seu instante se tornará.

Você pode comparar dois tempos utilizando os métodos de comparação (um tempo anterior é menor que

Algumas Coisinhas Para Tentar

• Um bilhão de segundos... Encontre o segundo exato do seu nascimento (se você puder). Descubra quando você fará (ou quando você fez?) um bilhão de segundos de idade. Então vá marcar na sua folhinha.

• Feliz Aniversário! Pergunte o ano de nascimento em que uma pessoa nasceu. Então pergunte o mês e, finalmente, o dia. Então descubra a idade dessa pessoa e lhe dê um PUXÃO DE ORELHA! para cada aniverśario que ela fez.

A Classe Hash

Outra classe muito útil é a classe Hash. Hashes são muito parecidos com vetores: eles têm um monte de espaços que podem conter vários objetos. Porém, em um vetor, os espaços são dispostos em uma linha, e cada um é numerado (iniciando pelo zero). Em um Hash, porém, os espaços não estão dispostos em uma linha (eles estão apenas juntos), e você pode usar qualquer objeto para se referir a um espaço, não apenas um número. É bom usar hashes quando você tem uma porção de coisas que você quer armazenar, mas que não têm, realmente, uma ordem. Por exemplo, as cores que eu uso em diversas partes desse tutorial:

colorArray = []  #  o mesmo que Array.new
colorHash  = {}  #  o mesmo que Hash.new

colorArray[0]         = 'vermelho'
colorArray[1]         = 'verde'
colorArray[2]         = 'azul'
colorHash['strings']  = 'vermelho'
colorHash['numbers']  = 'verde'
colorHash['keywords'] = 'azul'

colorArray.each do |color|
  puts color
end
colorHash.each do |codeType, color|
  puts codeType + ':  ' + color
end
vermelho
verde
azul
keywords:  azul
numbers:  verde
strings:  vermelho

Se eu usar um vetor, eu tenho que me lembrar que o espaço 0 é para strings, o slot 1 é para números e etc. Mas se eu usar um Hash, fica fácil! O espaço 'strings' armazena a cor das strings, claro. Nada para lembrar. Você deve ter notado que quando eu usei o each, os objetos no hash não vieram na mesma ordem que eu os coloquei (Pelo menos não quando eu escrevi isso. Talvez agora esteja em ordem... você nunca sabe a ordem com os hashes). Vetores servem para colocar as coisas em ordem, os Hashes não.

Apesar das pessosas normalmente usarem strings para nomear os espaços em um hash, você pode usar qualquer tipo de objeto, até mesmo vetores e outros hashes (apesar de eu não conseguir achar uma razão para você fazer isso...):

hashBizarro = Hash.new

hashBizarro[12] = 'macacos'
hashBizarro[[]] = 'totalmente vazio'
hashBizarro[Time.new] = 'nada melhor que o Presente'

Hashes e vetores são bons para coisas diferentes: a escolha sobre qual resolve o seu problema melhor é sua, e diferente para todos os problemas que você tiver.

Expandindo Classes

No fim do último capítulo, você escreveu um método para retornar um número por extenso. Porém, esse não era um método de inteiros: era um método genérico do programa. Não seria mais legal se você pudesse escrever 22.ext ao invés de porExtenso 22? Olha só como você pode fazer isso:

class Integer

    def ext
    if self == 5
      porExtenso = 'cinco'
    else
      porExtenso = 'cinqüenta e oito'
    end

    porExtenso
  end

end

#  Eu prefiro testar sempre em duplas...
puts 5.ext
puts 58.ext
cinco
cinqüenta e oito

Bem, eu testei; e nada explodiu. :)

Nós definimos um método inteiro apenas "pulando" dentro da classe Integer, definindo o método lá dentro e caindo fora. Agora todos os inteiros tem esse sensacional (incompleto) método. Na verdade, se você não gostar da forma como o método nativo to_s faz as coisas, você pode simplesmente redefini-lo da mesma forma... mas eu não recomendo isso! É melhor deixar os métodos antigos quietos em seu canto e fazer novos quando você precisar de uma coisa nova.

Confuso ainda? Deixe-me voltar até o último programa mais um pouco. Até agora, quando nós executamos qualquer código ou definido um método, nós o fizemos no objeto "programa" padrão. No nosso último programa, nós saímos daquele objeto pela primeira vez e fomos para dentro da classe Integer. Nós definimos um método lá (o que o tornou um método inteiro) e todos os inteiros podem usar ele. Dentro daquele métodos, nós usamos o self para nos referir ao objeto (o inteiro) que estiver usando o método.

Criando Classes

Nós já vimos um monte de objetos de classes diferentes. Porém, é fácil criar tipos de objeto que o Ruby não tenha. Por sorte, criar uma classe nova é tão fácil como expandir uma classe já existente. Vamos supor que eu queira rodar alguns dados no Ruby. Olhe como podemos fazer uma classe chamada Dado:

class Dado

  def rolar
    1 + rand(6)
  end

end

#  Vamos fazer dois dados...
dados = [Dado.new, Dado.new]

#  ...e rolar cada um deles.
dados.each do |dado|
  puts dado.rolar
end
6
4

(Se você pulou a parte que falava sobre números aleatórios, rand(6) apenas devolve um número aleatório entre 0 e 5).

Só isso! Objetos de nossa própria autoria. Role os dados algumas vezes (utilizando o botão de "Atualizar" do seu navegador) e veja o que acontece.

Nós podemos definir todo o tipo de métodos para os nossos objetos... mas tem alguma coisa errada. Trabalhando com esses objetos não mudou grande coisa desde que aprendemos a mexer com variáveis. Olhe o nosso dado, por exemplo. Cada vez que rolamos ele, nós temos um número diferente. Mas se nós quisermos salvar aquele número, nós temos que criar uma variável e apontar para aquele número. E qualquer dado que preste deve ter um número, e rolando o dado deve mudar o número. Se nós armazenarmos o dado, nós não temos como saber qual número ele está mostrando.

Porém, se nós tentarmos armazenar o número que nós tirmaos no dado em uma variável (local) dentro de rolar, o valor será perdido assim que o rolar acabar. Nós precisamos salvar esse número em um tipo diferente de variável:

Variáveis de Instância

Normalmente quando falamos sobre strings, nós apenas nos referimos a elas como strings. Porém, nós poderíamos chamá-las de Objetos do tipo String. Algumas vezes, alguns programadores podem chamá-las de instâncias da classe String, mas essa é uma forma exagerada (e muito longa) de dizer string. Uma instância de uma classe é apenas um objeto daquela classe.

Portanto, variáveis de instância são como variáveis de objeto. Uma variável local de um método ficam vivas até que o método termine. Uma variável de instância de um objeto, por outro lado, ficará viva enquanto o objeto estiver vivo. Para diferenciar variáveis de instância de variáveis locais, elas têm uma @ na frente dos seus nomes:

class Dado

  def rolar
    @numeroMostrado = 1 + rand(6)
  end

  def mostrado
    @numeroMostrado
  end

end

dado = Dado.new
dado.rolar
puts dado.mostrado
puts dado.mostrado
dado.rolar
puts dado.mostrado
puts dado.mostrado
6
6
4
4

Muito legal! Agora o rolar rola o dado e o mostrado nos diz qual é o número que saiu. Mas e se quisermos ver qual número saiu antes de rolar o dado (antes de termos definido @numeroMostrado)?

class Dado

  def rolar
    @numeroMostrado = 1 + rand(6)
  end

  def mostrado
    @numeroMostrado
  end

end

#  Já que eu não vou mais usar esse dado,
#  eu não preciso salvá-lo em uma variável.
puts Dado.new.mostrado
nil

Hum... Bem, pelo menos não deu erro. Espera aí, não faz muito sentido um dado "não-rolado" ou o que quer que nil signifique aqui. Seria muito mais bacana se nós pudessemos rolar o dado assim que ele for criado. É isso que o initialize faz:

class Dado

  def initialize
    #  Eu vou apenas rolar o dado, apesar de
    #  podermos fazer qualquer coisa que
    #  queiramos fazer, como colocar a face '6'
    #  para cima
    rolar
  end

  def rolar
    @numeroMostrado = 1 + rand(6)
  end

  def mostrado
    @numeroMostrado
  end

end

puts Dado.new.mostrado
4

Quando um objeto é criado, o método initialize (se foi definido) é sempre chamado.

Nosso dado está quase perfeito. A única coisa que falta é uma maneira de arrumar qual número está sendo mostrado... Por que você não escreve o método trapaca que faça isso? Volte quando tiver terminado (e quando você testar e funcionar, lógico). Apenas tenha certeza de que ninguém pode fazer com o que o dado mostre um 7!

Foi muito legal o que fizemos até agora. Mas foi apenas uma brincadeira, mesmo assim. Deixe-me mostrar um exemplo mais interessante. Vamos fazer um bichinho virtual, um dragão bebê. Assim como todos os bebês, ele deve conseguir comer, dormir e "atender à natureza", o que significa que vamos ter que ter como alimentá-lo, colocá-lo pra dormir e levar ele até o quintal. Internamente, o nosso dragão precisa saber se está com fome, cansado ou se precisa ir lá fora, mas nós não poderemos ver isso enquanto estivermos interagindo com ele, assim como você não pode perguntar a um bebê "você está com fome?". Então nós vamos adicionar algumas maneiras legais para interagir com nosso dragão bebê, e quando ele nascer nós vamos dar um nome para ele (Qualquer coisa que você passe como parâmetro para o método new será passado para o método initialize para você). Certo, vamos tentar:

class Dragao

  def initialize nome
    @nome = nome
    @dormindo = false
    @comidaEstomago  = 10 #  Ele está cheio
    @comidaIntestino =  0 #  Ele não precisa ir ao quintal

    puts @nome + ' nasceu.'
  end

  def alimentar
    puts 'Você alimentou o ' + @nome + '.'
    @comidaEstomago = 10
    passagemDeTempo
  end

  def quintal
    puts 'Você levou o ' + @nome + ' até o quintal.'
    @comidaIntestino = 0
    passagemDeTempo
  end

  def colocarNaCama
    puts 'Você colocou o ' + @nome + ' na cama.'
    @dormindo = true
    3.times do
      if @dormindo
        passagemDeTempo
      end
      if @dormindo
        puts @nome + ' está roncando e enchendo o quarto de fumaça.'
      end
    end
    if @dormindo
      @dormindo = false
      puts @nome + ' está acordando.'
    end
  end

  def jogar
    puts 'Você joga o ' + @nome + ' no ar.'
    puts 'Ele dá uma risadinha e queima suas sobrancelhas.'
    passagemDeTempo
  end

  def balancar
    puts 'Você balança o ' + @nome + ' gentilmente.'
    @dormindo = true
    puts 'Ele começa a cochilar...'
    passagemDeTempo
    if @dormindo
      @dormindo = false
      puts '...mas acorda quando você pára.'
    end
  end

  private

  #  "private" significa que os métodos definidos aqui
  #  são métodos internos do objeto. (Você pode
  #  alimentá-lo, mas você não pode perguntar se
  #  ele está com fome.)

  def comFome?
    #  Nomes de métodos podem acabar com "?".
    #  Normalmente, nós fazemos isso apenas
    #  se o métodos retorna verdadeiro ou falso,
    #  como esse:
    @comidaEstomago <= 2
  end

  def precisaSair?
    @comidaIntestino >= 8
  end

  def passagemDeTempo
    if @comidaEstomago > 0
      #  Mover a comida do estômago para o intestino.
      @comidaEstomago  = @comidaEstomago  - 1
      @comidaIntestino = @comidaIntestino + 1
    else  #  Nosso dragão está faminto!
      if @dormindo
        @dormindo = false
        puts 'Ele está acordando!'
      end
      puts @nome + ' está faminto! Em desespero, ele comeu VOCÊ!'
      exit  #  Isso sai do programa.
    end

    if @comidaIntestino >= 10
      @comidaIntestino = 0
      puts 'Ops!  ' + @nome + ' teve um acidente...'
    end

    if comFome?
      if @dormindo
        @dormindo = false
        puts 'Ele está acordando!'
      end
      puts 'O estômago do ' + @nome + ' está roncando...'
    end

    if precisaSair?
      if @dormindo
        @dormindo = false
        puts 'Ele está acordando!'
      end
      puts @nome + ' faz a dança para ir ao quintal...'
    end
  end

end

bichinho = Dragao.new 'Norbert'
bichinho.alimentar
bichinho.jogar
bichinho.quintal
bichinho.colocarNaCama
bichinho.balancar
bichinho.colocarNaCama
bichinho.colocarNaCama
bichinho.colocarNaCama
bichinho.colocarNaCama
Norbert nasceu.
Você alimentou o Norbert.
Você joga o Norbert no ar.
Ele dá uma risadinha e queima suas sobrancelhas.
Você levou o Norbert até o quintal.
Você colocou o Norbert na cama.
Norbert está roncando e enchendo o quarto de fumaça.
Norbert está roncando e enchendo o quarto de fumaça.
Norbert está roncando e enchendo o quarto de fumaça.
Norbert está acordando.
Você balança o Norbert gentilmente.
Ele começa a cochilar...
...mas acorda quando você pára.
Você colocou o Norbert na cama.
Ele está acordando!
O estômago do Norbert está roncando...
Você colocou o Norbert na cama.
Ele está acordando!
O estômago do Norbert está roncando...
Você colocou o Norbert na cama.
Ele está acordando!
O estômago do Norbert está roncando...
Norbert faz a dança para ir ao quintal...
Você colocou o Norbert na cama.
Ele está acordando!
Norbert está faminto! Em desespero, ele comeu VOCÊ!

UAU! Claro que seria muito mais legal se esse fosse um programa interativo, mas você pode fazer essa parte depois. Eu apenas estava tentando mostrar as partes relacionadas diretamente a criar uma nova classe do tipo Dragão.

Nós dissemos um monte de coisas novas nesse exemplo. A primeira é simples: exit termina o programa onde estiver. A segunda é a palavra private, que nós colocamos bem no meio da nossa classe. Eu podia ter deixado ela de fora, mas eu apenas quis reforçar a idéia de que certos métodos você podia fazer com um dragão, enquanto que outros aconteciam com o dragão. Você pode pensar nisso como "coisas por trás dos panos": a não ser que você seja um mecânico de automóveis, tudo o que você precisa saber sobre carros é o acelerador, o freio e a direção. Um programador chama isso de interface pública

Agora, para um exemplo mais concreto nessa linha de raciocínio, vamos falar um pouco sobre como você representaria um carro em um jogo (o que é a minha linha de trabalho). Primeiro, você precisa decidir como irá se parecer sua interface pública; em outras palavras, quais métodos as pessoas podem chamar do seus objetos do tipo carro? Bem, eles devem podem acelerar e freiar, mas eles precisam, também, poder definir a força que estão aplicando no pedal (Há uma grande diferença entre tocar o acelerador e afundar o pé). Eles vão precisar também guiar, e novamente, e dizer que força estão aplicando na direção. Eu acho que você pode ir ainda mais longe e adicionar uma embreagem, piscas, lançador de foguetes, incinerador traseiro, um condensador de fluxo e etc... depende do tipo de jogo que você está fazendo.

Os objetos internos a um carro, porém, são mais complexos: outras coisas que um carro precisa são a velocidade, a direção e a posição (ficando no básico). Esses atributos serão modificados pressionando o pedal do acelerador ou o de freio e girando o volante, claro, mas o usuário não deve poder alterar essas informações diretamente (o que seria uma distorção). Você pode querer checar a derrapagem ou o dano, a resistência do ar e por aí vai. Tudo isso diz respeito apenas ao carro. Tudo isso é interno ao carro.

Algumas Coisinhas Para Tentar

• Faça uma classe de ArvoreDeLaranja. Ela deve ter um método altura que retorne sua altura, um método chamado passar_um_ano que, quando chamado, faz a árvore completar mais um ano de vida. Cada ano, a árvore cresce mais magra (não importa o quão grande você ache que uma árvore de laranja possa crescer em um ano), e depois de alguns anos (novamente, você faz as chamadas) a árvore deve morrer. Nos primeiros anos, ela não deve produzir frutos, mas depois de um tempo ela deve, e eu acho que as árvores mais velhas produzem muito mais frutos do que uma mais jovem com o passar dos anos... ou o que você achar mais lógico. E, é claro, você deve poder contar_as_laranjas (o número de laranjas na árvore), e pegar_uma_laranja (que irá reduzir o @numero_de_laranjas em um e retornar uma string dizendo quão deliciosa a laranja estava, ou então irá dizer que não há mais laranjas esse ano). Lembre-se de que as laranjas que você não pegar esse ano devem cair antes do próximo ano.

• Escreva um programa para que você possa interagir com o seu filhote de dragão. Você deve ser capaz de inserir comandos como alimentar e quintal, e esses métodos devem ser chamados no seu dragão. Logicamente que, como toda a entrada será por strings, você deve ter uma forma de repassar os métodos, onde seu programa deve validar a string digitada e chamar o método apropriado.

E isso é tudo! Mas espere um pouco... Eu não disse nada a você sobre classes para fazer coisas como mandar um e-mail, ou salvar e carregar arquivos do seu computador, ou como criar janelas e botões, ou mundos em 3D ou qualquer coisa! Bem, há apenas muitas classes que você pode usar, e isso torna impossível que eu mostre todas para você; mesmo eu não conheço todas elas. O que eu posso dizer para você é onde encontrar mais sobre elas, assim você pode aprender mais sobre as que você quiser usar. Mas antes de mandar você embora, há mais um recurso do Ruby que você deveria saber, algo que a maioria das outras linguagens não tem, mas que eu simplesmente não posso viver sem: blocos e procs.

 

© 2003-2015 Chris Pine