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.


2 Comentários to “Métodos de classe não são métodos estáticos, e como tirar proveito disto”

Filipe Chagas diz:

Olá,

Daniel, métodos de classe podem acessar atributos de instância? Fiz um teste no irb

class Teste attr_accessor :atributo def self.alterar_atributo @atributo = 10 end end => nil teste = Teste.new => #<teste:0xb7acc954> teste.atributo = 2 => 2 puts teste.atributo 2 => nil Teste.alterar_atributo => 10 puts teste.atributo 2 => nil puts Teste.atributo NoMethodError: undefined method `atributo’ for Teste:Class from (irb):12

Fiquei simplesmente sem entender o que aconteceu…


Ved diz:

Muito bom mesmo! Um excelente complemento para a aula!


Comentário