Métodos de classe não são métodos estáticos

Posted by Daniel Lopes on 06/11/2009

Quando trabalhamos com OOP a divisão do código é bem clara. Classes definem um Objeto que possui atributos e comportamentos, comportamento é representado por métodos. Em Ruby tudo segue esta linha sem exceções (blocos não se encaixam perfeitamente assim).

Mas também é comum por exemplo situações onde talvez um método não precise estar associado a um objeto. Ou seja, não faz muito sentido precisar instanciar um objeto desta classe para poder acessar estes métodos.

Esta capacidade é conhecida de outras linguagens como classes estáticas ou métodos estáticos. Em Ruby não temos isto, temos apenas métodos de classe e métodos de instância. Mas o curioso que mesmo os métodos de classe são métodos de instância mas da classe em si.

Por exemplo:

class Utilities

  def self.resize_images(images=[])
    ...
  end

end

Entendendo o que é um método de classe

Acima temos um método resize_images que é criado em Utilities. O self no momento da definição do método é Utilities, por este motivo seria o mesmo que definir o método assim def Utilities.resize_images . Como método foi criado em Utilities, você poderá acessa-lo como Utilities.resize_images sem a necessidade de enviar um .new para Utilities.

Uma pequena curiosidade é que as próprias definições de classe em Ruby são objetos, então Utilities (que é uma constante) também é um objeto, e estes objetos possuem como Metaclasse Class. Então por este motivo o que se assemelha a métodos estáticos de linguagens como Java ou AS3 é nada mais do que um método criado no objeto Utilities que é uma instância de Class armazenada em uma constante chamada Utilities.

Por exemplo, pelo IRB crie uma nova classe assim

class Carro ; end
e depois execute Object.constants.sort e você vera que agora Object também possui uma constante chamada Carro.

Agora que entendemos como funcionam os métodos de classe, existem outras formas de criar métodos de classe que é através da abertura da metaclasse, por exemplo:

class Utilities

  def self.resize_images(images=[])
    ...
  end

  def self.rename_images(images=[])
    ...
  end
end

Seria o mesmo que:

class Utilities

  class << self
    def resize_images(images=[])
      ...
    end

    def rename_images(images=[])
      ...
    end
  end
end

Você poderia também abrir a classe Utilities com instance_eval para adicionar métodos de classe nela, ou utilizar um módulo chamando-o com extend. Não vou entrar nestes detalhes para não me prolongar de mais.

Formas de empacotar seus métodos

Também não é incomun casos onde você tenha uma classe apenas com métodos de classe, o que é o caso da Utilities. Criando uma classe assim estariámos modularizando o nosso código, mas o que muita gente diria que é melhor criar um módulo ao invés de uma classe e estender as classes com o módulo Utilities. Também seria uma boa opção.

Mas talvez existem casos onde você não deseja ter que abrir a classe e extende-la apenas para ter acesso ao um método, então uma outra forma de modularizar seu código é através de um método pouco conhecido. Como módulos são classes que não podem ser instanciadas você não poderia fazer algo como abaixo:

module Utilities
  def resize_images(images=[])
    ...
  end

  def rename_images(images=[])
    ...
  end
end

Utilities.resize_images
Utilities.rename_images

Mas você poderia utilizar um module_function para permitir que estes métodos se tornem métodos acessíveis como métodos de classe. Como abaixo:

module Utilities
  module_function

  def resize_images(images=[])
    ...
  end

  def rename_images(images=[])
    ...
  end
end

E agora você poderá chamar os métodos em qualquer lugar, desta forma:

Utilities.resize_images
Utilities.rename_images

Então se você possui uma classe que tem apenas métodos de classe, não possui atributos ou acessors talvez seja interessante pensar nesta solução. Acaba sendo um pouco fora da idéia convencional de OOP, mas se pensarmos a fundo veremos que módulos são nada mais que classes que não podem ser instanciadas e já que não precisamos instanciar Utilities este método se encaixa bem.

Uma outra forma de ter o mesmo resultado seria estendendo o módulo com ele mesmo através de extend self , mas para explicar todas as formas e o que está por trás seria necessário um livro sobre Ruby object model e metaprogramação.

blog comments powered by Disqus