métodos privados e alto acoplamento

131 views
Skip to first unread message

Guilherme Silveira

unread,
Nov 15, 2010, 6:59:59 PM11/15/10
to ruby-sp
Oi pessoal,

Existe uma característica do Ruby que sempre que (re)encontro me
questiono da utilidade, sem saber se é uma feature esperada um buraco
que virou feature (negativa):

class Pai
def fala
imprime
end
private
def imprime
puts "sou papai"
end
end

class Filho < Pai
private
def imprime
puts "sou filho"
end
end

Filho.new.imprime

O que imprime? "sou filho". O que há de errado com isso?

Quando a classe pai invoca o método imprime ela espera que o método
privado seja chamado. A primeira "esperança" para quem vem do mundo OO
é que um método privado seja um
comportamento fechado da minha classe, mas não é. Em Ruby (mri e jruby
pelo menos) ele é fechado ao meu
objeto, então em classes filhas ele pode ser sobrescrito
tranquilamente (com o modificador private ou não, tanto faz).

Em outras palavras, private/protected/public não possuem efeito
em relaçao a limitação do que pode ser sobrescrito, somente aquilo que
pode ser invocado. (sendo que o mesmo pode ser quebrado via :send)

O grande perigo dessa abordagem de escopo baseado em objeto ao invés de classe?
É que o acoplamento não fica somente na interface pública e a implementação dos
métodos públicos de uma classe, mas ao herdarmos *estamos nos acoplando
também a interface E implementação dos métodos privados*.

Isto é, o acoplamento a classe pai é total, sem dó.

Agora leve isso ao extremo: mix-ins. Ao invés de herdar uma classe pai
suportamos herança múltipla com
mixins: o potencial de conflito é gigante (diamond problem a vista)...
nesse caso o acoplamento passa a ser entre
todos os módulos que eu mixei. Isto é, módulos de diferentes providers
(um do Rails, um de um plugin que resolvi usar, outro meu etc)
estão acoplados completamente por sua interface pública, privada e
ambas implementações.

Me soa como herança é ainda mais perigoso para manutenabilidade nessa
abordagem, o que me faz querer recorrer ainda mais a composição.

Opiniões?

Abraço

Guilherme Silveira
Caelum | Ensino e Inovação
http://www.caelum.com.br/

Stefano Diem

unread,
Nov 16, 2010, 12:04:02 AM11/16/10
to rub...@googlegroups.com
Pergunta bastante interessante, eu nunca havia notado esse comportamento...

Sem alterar a sintaxe da linguagem, acho que faz sentido sim que funcione da forma como funciona. Seguindo o funcionamento padrão de herança de métodos, de que quando se chama um método e o mesmo não é encontrado no objeto em questão (receiver), este pergunta à seu ancestral e assim em diante que até alguém responda, aliado à existência de definições dinâmicas de métodos e ao fato de que métodos privatos usam a mesma sintaxe que métodos públicos, não é possível determinar se um método chamado é private ou não antes de receber uma resposta. Ou seja, quando fala ( o método herdado por Filho de Pai ) chama o método imprime, não há como saber se imprime é um método privado ou não, pois pode estar definido de forma dinâmica dentro de Filho ou dentro de Pai (por sua vez de forma dinâmica ou não) ou em qualquer outro lugar da cadeia de heranças e pode ser público ou privado...

Para haver essa distinção de comportamento entre métodos private ou não, seria necessário uma mudança na sintaxe para deixar claro que essa chamada é referente a um método com uma visibilidade diferenciada. Em alguns lugares li uma sugestão de se adicionar @ no nome do método tal qual uma variável de instância para indicar que ele é privado (def fala; @imprime; end)

Resolvi dar uma olhada em como isso é feito em python, e verifiquei que de fato é realizado distinguindo-se métodos privatos (através de underlines ao nome da função, e ao fazer isso, o nome da class é adicionado antes do nome do método para evitar conflitos. (e.g.: o método __imprime dentro da classe Papai se torna _Papai__imprime)). Para mais informações, vide http://docs.python.org/tutorial/classes.html#private-variables

E em python suas classes ficariam assim:

class Papai:
  def fala(self):
    return self.__imprime()
  def __imprime(self):
    print("sou papai")

class Filho(Papai):
  def __imprime(self):
    print("sou filho")

Filho().fala()

Retornando "sou papai". Não consigo imaginar como fazer isso em Ruby sem utilizar de algum mecanismo parecido de distinção de métodos privates de públicos.

Mas realizar da forma como Python faz também gera problemas. o seguinte código:

class Papai:
  def fala(self):
    return self.__imprime()
  def __imprime(self):
    print(self.__texto())

class Filho(Papai):
  def __imprime(self):
    print("sou filho")
  def __texto(self):
    return "sou filho"

Retorna AttributeError: Filho instance has no attribute '_Papai__texto', justamente porque os nomes de métodos privados são encapsulados com o nome da classe em que foram definidos, e portanto métodos que precisam ser implementados obrigatoriamente têm de ser públicos ( pois o filho nunca vai conseguir sobrescrever um método privado do pai ).

Em Ruby, isso significaria que métodos como <=>, <<, to_s entre outros diversos métodos que costumam são pedidos para implementar diversas classes NUNCA poderiam ser privados.

Não tenho uma opinião muito formada a respeito (depois de ler até que bastante coisa a respeito), pois tem tanto pontos positivos (quebrar subclasses / minxins fica um pouco mais difícil) como negativos (introdução de uma nova sintaxe, não é backwards compatible, e adiciona restrições).

Existe um tópico que rolou por uns tempos no Ruby Core sobre o assunto (http://osdir.com/ml/lang.ruby.core/2007-06/msg00075.html), mas infelizemente a discussão original parece ter sido tirada do ar, então sobram apenas alguns mirrors que perdem parte do contexto e parecem terminar do nada.

Abraços,
Stefano Diem Benatti

TAiLORBiRDS
phone: 55 11 9343 0994
skype:  teonimesic



2010/11/15 Guilherme Silveira <guilherme...@caelum.com.br>

--
Você recebeu esta mensagem porque está inscrito no Grupo "ruby-sp" em
Grupos do Google.
Para postar neste grupo, envie um e-mail para rub...@googlegroups.com
Para cancelar a sua inscrição neste grupo, envie um e-mail para
ruby-sp+u...@googlegroups.com
Para ver mais opções, visite este grupo em
http://groups.google.com.br/group/ruby-sp?hl=pt-BR

Maurício Szabo

unread,
Nov 16, 2010, 6:49:33 AM11/16/10
to rub...@googlegroups.com
Guilherme, seguem minhas opiniões sobre o assunto. Como são muitas perguntas, vou responder uma por uma:


Quando a classe pai invoca o método imprime ela espera que o método
privado seja chamado. A primeira "esperança" para quem vem do mundo OO
é que um método privado seja um 
comportamento fechado da minha classe, mas não é. Em Ruby (mri e jruby
pelo menos) ele é fechado ao meu
objeto, então em classes filhas ele pode ser sobrescrito
tranquilamente (com o modificador private ou não, tanto faz).

Então, o "mundo OO" é muito, MUITO relativo. O mundo OO que você se refere é o de Java, de C++, de Smalltalk? Em algum lugar (não lembro se no livro The Ruby Programming Language ou no próprio site do Ruby) eles dizem que, em Ruby, todas as classes são abstratas, portanto o comportamento da linguagem está correto. Se você quisesse o comportamento de Java, por exemplo, você poderia fazer algo assim:

class Pai
 def fala
  m = Pai.instance_method(:imprime)
  m.bind(self).call

 end
 private
 def imprime
  puts "sou papai"
 end
end

class Filho < Pai
 private
 def imprime
  puts "sou filho"
 end
end

Em outras palavras, private/protected/public não possuem efeito
em relaçao a limitação do que pode ser sobrescrito, somente aquilo que
pode ser invocado. (sendo que o mesmo pode ser quebrado via :send)
Yep. E me parece que nunca foi a intenção da linguagem limitar o que pode ser sobrescrito.

O grande perigo dessa abordagem de escopo baseado em objeto ao invés de classe?
É que o acoplamento não fica somente na interface pública e a implementação dos
métodos públicos de uma classe, mas ao herdarmos *estamos nos acoplando
também a interface E implementação dos métodos privados*.
Não entendi muito bem essa observação. Até onde eu me lembro de ter lido no Design Patterns, Clean Code e praticamente qualquer outro livro sobre o assunto, eles informam que você deve preferir instância ao invés de herança, se o objetivo é desacoplar... é estranho porque eu não consigo imaginar situações na qual o uso de herança é o mais correto, e que eu tenha que me preocupar com esse tipo de "acoplamento"... 

Agora leve isso ao extremo: mix-ins. Ao invés de herdar uma classe pai
suportamos herança múltipla com
mixins: o potencial de conflito é gigante (diamond problem a vista)...
Nops. Diamond Problem ocorre em C++ e Python. Em Ruby, não é possível porque ele segue a ordem dos "include". Logo:

class Pai #Possui um initialize assim: "puts 'Pai'"
  include Filho1 #Possui um initialize assim: "puts 'Filho1'; super"
  include Filho2 #Possui um initialize assim: "puts 'Filho2'; super"
  include Filho3 #Possui um initialize assim: "puts 'Filho3'; super"
end

ao fazer "Pai.new", teríamos na tela:
Filho3
Filho2
Filho1
Pai

No diamond problem :)
 
nesse caso o acoplamento passa a ser entre
todos os módulos que eu mixei. Isto é, módulos de diferentes providers
(um do Rails, um de um plugin que resolvi usar, outro meu etc)
estão acoplados completamente por sua interface pública, privada e
ambas implementações. 
Acho que você está usando os mix-ins de forma incorreta. Por exemplo, mixins como "Enumerable" e "Comparable" são, para mim, exemplos bem claros de qual é a idéia de um mix-in: promover adição de funcionalidades. Nada mais que isso. Usar mix-ins como forma de herança múltipla não me parece a abordagem correta, e mesmo que um módulo tiver essa funcionalidade em mente, sempre há um work-around.

Me soa como herança é ainda mais perigoso para manutenabilidade nessa
abordagem, o que me faz querer recorrer ainda mais a composição.

Opiniões?
Delegation Pattern é uma saída. Mas, ao que me parece, o real problema é que você está querendo fazer Ruby funcionar como alguma outra linguagem (que me parece ser Java). Talvez, se você mostrar alguns códigos aonde isso é um problema, com uma explicação de qual seria o comportamento desejado, ficaria mais fácil indicar o que mudar.

Abraços,
Maurício Szabo

2010/11/16 Stefano Diem <teoni...@gmail.com>



--
A question that sometimes drives me hazy: am I or are the others crazy?

Any intelligent fool can make things bigger and more complex... It takes a touch of genius - and a lot of courage to move in the opposite direction.
(Albert Einstein)

Apenas peixes mortos nadam a favor da maré (Malcolm Muggeridge)

Guilherme Silveira

unread,
Nov 16, 2010, 6:53:56 AM11/16/10
to rub...@googlegroups.com
Oi Stefano,

Isso mesmo... o funcionamento padrão da busca de método virtual sem
verificar o escopo do mesmo faz com que o resultado seja esse.

Se (somente se) fosse interessante minimizar esse acoplamento, tem uma
maneira simples de fazer que é só no lookup dinâmico verificar de onde
está método está sendo chamado e se na classe (ou módulo) onde foi
definido existia um privado com mesmo nome, se sim, é ele. É uma única
mudança na regra de lookup e visa diminuir o acoplamento entre pais e
filhos, mas é uma quebra de funcionalidade na linguagem - só não sei
se era uma funcionalidade esperada ou não.

Nessa abordagem, os operadores e to_s que você citou poderiam ser
protected que é o que desejariamos na maior parte das vezes
(acessibilidade e possibilidade de sobrescrita por filhos, mas
invisivel para o exterior).

Não consigo ver vantagem nela como está (exceto a backwards compatible
que você citou mesmo) por isso imagino que não fosse esperada, mas sim
consequência do lookup dinamico de metodos ser implementado dessa
maneira.

Mas como a discussão no post que você linkou, parece que a idéia é não
quebrar a compatibilidade mesmo sabendo desses corner cases.

> Retorna AttributeError: Filho instance has no attribute '_Papai__texto',

Perfeito, é o caso em que o desenvolvedor está escrevendo algo que não
faz sentido, ai o interpretador chia... faz sentido (no Java também é
assim, em compile time enquanto o interpetador compila as classes
abstratas e concretas nesse caso).

> Existe um tópico que rolou por uns tempos no Ruby Core sobre o assunto
> (http://osdir.com/ml/lang.ruby.core/2007-06/msg00075.html)

Muito bom o resumo dele... parece que a conclusão é não querer mudar
pois está claro que herança é herança, com todos os seus males. Com a
ótima frase dele:

"Back when I was evangelizing OOP for IBM, I used to talk about this
and say that inheritance was like sex, it's much more socially
acceptable when it's done between committed consensual adults."

Acho que é isso mesmo que falamos, o problema da herança é quem causa
tudo isso e ai ficam esses corner cases difíceis de lidar (ele cita
outros no post).

Valeu

Abraço!

Guilherme Silveira
Caelum | Ensino e Inovação
http://www.caelum.com.br/

2010/11/16 Stefano Diem <teoni...@gmail.com>:

Guilherme Silveira

unread,
Nov 16, 2010, 9:36:17 AM11/16/10
to rub...@googlegroups.com
Opa Mauricio,

Desculpa acho que não fiquei claro e pareceu que eu achei que algo é
certo e errado e que eu quero herança múltipla: deus me livre... não
quero usar dessa maneira. A crítica é a mesma que você fez no final:
não queremos usar dessa maneira, mas a linguagem permite usar dessa
maneira (esse que era o ponto do email, que não ficou claro).

> Yep. E me parece que nunca foi a intenção da linguagem limitar o que pode ser sobrescrito.

Yep. Mas parece que tenho boas novas :) Eles querem mudar :)

O Kung acabou de passar os links das palestras do Shugo e do Matz que
falam exatamente sobre esse caso que citei! Inclusive a palestra do
Shugo tem o nome "real private methods"... como você e eu, concordamos
que o acoplamento maior nessa abordagem de implementação é negativo e
por isso estão tentando mudar essa intenção inicial deles.

O caminho deles, como o Stefano sugeriu é de criar uma nova sintaxe:

http://shugo.net/tmp/rc2010-refinements.pdf
http://www.slideshare.net/ShugoMaeda/rc2010-refinements (slide 44)
http://www.slideshare.net/yukihiro_matz/rubyconf-2010-keynote-by-matz
(slides 47 e 52)

-----

Sobre os outros assunto (muitas perguntas mesmo, desculpe):

O Diamond Problem que conheço é um pouco diferente do exemplo que você
citou, é quando dois modulos inclusos em uma classe ja incluiam um
outro modulo comum. Isso é independente de linguagem, só herança. O
Diamond Problem existe em toda linguagem que tem herança múltipla, e a
abordagem do Ruby (com mixin) é uma, do Perl é outra, de C++ é outra
etc.
Como você citou, o resultado final é uma única chamada ao método de A
(como Python), ao invés de duas (C++?)

module A
def a
puts "miau"
end
end
module B
include A
def a
puts "b"
super
end
end
module C
include A
def a
puts "c"
super
end
end
class D
include C
include B
end
D.new.a

> Nada mais que isso. Usar mix-ins como forma de herança múltipla não me parece a abordagem correta, e mesmo que um módulo tiver essa funcionalidade em mente, sempre há um work-around.

Concordamos po completo. Esse era o objetivo do meu primeiro email.
Citar que a possibilidade de fazer isso é negativa e devemos fugir
dela pq tem outras maneiras de atingir o mesmo objetivo.

Abraço!

Stefano Diem

unread,
Nov 16, 2010, 11:20:57 AM11/16/10
to rub...@googlegroups.com
Nossa, eu estava respondendo algo parecido com o que o Guilherme acabou de escrever! Viva o 'show' do Gmail...

Eu lembrei que tinha visto em algum lugar a respeito do problema de conflitos no ruby 2.0, e aí lembrei que era do keynote do Matz: http://www.youtube.com/watch?v=t9LMOydfc4k ( ele fala a respeito de traits e namespace lá pelo minuto 17 ). A transcrição do keynote que o Guilherme colocou está mais bem melhor estruturada que esse keynote.

Essa mesma preocupação já vinha desde a concepção do Ruby 1.9, então discordo do fato de que a linguagem não foi pensada para lidar com sobrescrição e conflitos, acho que apenas não havia aparecido nada que tivesse agradado ao Matz até o momento.

O conceito de traits é bem bacana e resolve boa parte do problema de conflitos em mixins, e as outras features esperadas de ruby 2.0 parecem caminhar ainda mais para diminuir os edge cases ( com namespace selector e prepend ), mas não são bala de prata. Também achei bem bacana as idéias sugeridas pelo Shugo, vamos ver se elas entram pro core. Em específico a forma demonstrada de usar nested methods (def a; def b; ... end; end) me surpreendeu positivamente (pag. 62 do pdf), dado que atualmente neste methods não faz nada de especial além de confundir o programador.

Agora, já que estamos numa discussão de herança e comportamentos peculiares, se possível gostaria que fossem dadas opiniões de o porquê disso:

class Artrópode
  @@numero_de_patas = 6
  def ande
    puts "Andando com as #{@@numero_de_patas} patas"
  end
end

class Aranha < Artrópode; @@numero_de_patas = 8 end
Formiga = Class.new(Artrópode)

Formiga.new.ande
#=> Andando com as 8 patas

Basicamente o que isso diz, é que sobrecrever uma variável de classe sobrescreve na classe ancestral, ou seja, o pai e todos os seus filhos compartilham a mesma variável de classe, o que eu acho muito estranho.

Para contornar esse problema, a forma é utilizar não utilizar variáveis de classe mas sim uma variável de instância na metaclasse:

class Artrópode
  singleton_class.class_eval do
    def numero_de_patas
      @numero_de_patas || 6
    end
    def numero_de_patas=(n)
      @numero_de_patas = n
    end
  end
end

Aranha = Class.new(Artrópode)
Formiga = Class.new(Artrópode)
Aranha.numero_de_patas = 8
Formiga.numero_de_patas
#=> 6

Que é mais ou menos o que o cattr_accessor do ActiveSupport faz, e algo como um  singleton_class.class_eval { attr_accessor :numero_de_patas }

Discutamos!

Abraços,
Stefano Diem Benatti

TAiLORBiRDS
phone: 55 11 9343 0994
skype:  teonimesic



2010/11/16 Guilherme Silveira <guilherme...@caelum.com.br>

Maurício Szabo

unread,
Nov 16, 2010, 11:33:07 AM11/16/10
to rub...@googlegroups.com
Bom, eu vou ler com cuidado. No slide do Shugo, por exemplo, ele apresenta uma nova sintaxe mas a antiga continua valendo, então seria aquilo que mencionamos: a linguagem vai continuar permitindo, cabe a cada um programar com o mínimo de side-effects...

Bom, mas eu não achei q vc tenha desejado herança múltipla não :-). Eu só mencionei que, embora seja possível, vale de cada programador entender os contras de abusar de mixins e usá-los como herança, quando ao meu ver, mixins tem outra funcionalidade bem diferente (inclusive, não gosto da abordagem do DataMapper e de outros mapeadores de usar "include" ao invés de herança para transformar uma classe num modelo).

O que eu falei de Diamond Problem é esse aqui: http://en.wikipedia.org/wiki/Diamond_problem, um problema de resolução da chamada de métodos, não de repetição. O que você me apresentou como Diamond Problem acho que é outra coisa, a idéia de repetir uma chamada de método porque ele foi "herdado" em várias sub-classes. E eu não vejo como um problema, pra ser sincero... embora o que você apresentou poderia dar problema, eu acho que aí já é mais da responsabilidade do programador.

Só que eu não concordo que a abordagem da implementação é negativa, apenas a acho diferente. Eu mesmo nunca tive problemas com a abordagem de Ruby nesse sentido, e inclusive prefiro esse "suposto maior acoplamento" do que a versão de C++ ou Java. Por isso que eu pedi exemplos, pra ver como eu faria para não cair nesses problemas no futuro :-)

2010/11/16 Guilherme Silveira <guilherme...@caelum.com.br>

Guilherme Silveira

unread,
Nov 16, 2010, 12:34:37 PM11/16/10
to rub...@googlegroups.com
> Eu só
> mencionei que, embora seja possível, vale de cada programador entender os
> contras de abusar de mixins e usá-los como herança, quando ao meu ver,
> mixins tem outra funcionalidade bem diferente (inclusive, não gosto da
> abordagem do DataMapper e de outros mapeadores de usar "include" ao invés de
> herança para transformar uma classe num modelo).
Perfeito! Estavamos de acordo mesmo...

Eu posso estar interpretando a wikipedia errado - e provavelmente
devo... mas imagino que seria essa parte? (when two classes B and C
inherit from A, and class D inherits from both B and C)...

Dos exemplos, posso dar um generico? Vou fazer mais a noite por causa
da correria agora...

Abraço!

Guilherme Silveira
Caelum | Ensino e Inovação
http://www.caelum.com.br/

2010/11/16 Maurício Szabo <maurici...@gmail.com>:

Maurício Szabo

unread,
Nov 16, 2010, 12:57:37 PM11/16/10
to rub...@googlegroups.com
Na verdade eu me referia a esse pedaço:

If a method in D calls a method defined in A (and does not override the method), and B and C have overridden that method differently, then from which class does it inherit: B, or C?

Mas também, sendo sincero, eu não sei se há algum consenso sobre o q é exatamente o problema... aliás, quanto mais eu estudo OO, mais eu descubro que não há consenso sobre nada hahahhaa. De uns tempos pra cá vi que, no início da Orientação a Objeto a essência era a passagem de mensagens entre os objetos... e hoje, qualquer professor de faculdade fala que a essencia da orientação a objeto é "Encapsulamento", "Herança" e "Polimorfismo", e muitos nem sabem o que é "passagem de mensagens".

E pode dar um exemplo genérico sim, sem problema :-) Valeu mesmo :-)

Abração,
Maurício Szabo

2010/11/16 Guilherme Silveira <guilherme...@caelum.com.br>

Guilherme Silveira

unread,
Nov 16, 2010, 12:50:24 PM11/16/10
to rub...@googlegroups.com
Oi Stefano,

O Java tem a mesma operação quando se utiliza variáveis estáticas e
serve para uma bela confusão por uma decisão de sintaxe da linguagem
poder acessar a mesma diretamente (sem os @@). A boa prática lá é não
acessar diretamente sem o prefixo justamente para não confundir o dev
que ler o código (uma correção para uma decisão não muito feliz).

Se não me engano foi o George que me citou uma vez para evitar o uso
direto do @@ justamente por dificultar a compreensão em algumas
situações. Imagino que devam ser situações como essa que você citou.

Acho que prefiro a solução onde Artropode é um modulo que é mix-inzado
na sua meta-classe, o que acha? Sei que isso implica não poder
instanciar Artropode diretamente... então depende do que você pode
brincar com seu design. Se não, a tua sugestão parece ser bem
razoável, tratando a classe como objeto diretamente e dizendo para ela
incluir o comportamento desejado (no caso a variavel bonus).

Outras sugestões de como implementar o Artropode?

ps; Mauricio e Stefano, muito boa a discussao.

Guilherme Silveira
Caelum | Ensino e Inovação
http://www.caelum.com.br/

2010/11/16 Stefano Diem <teoni...@gmail.com>:

Maurício Szabo

unread,
Nov 16, 2010, 1:34:00 PM11/16/10
to rub...@googlegroups.com
Pois é, adoro Ruby mas eu literalmente faço de conta que não existem as variáveis com o @@, porque já tive TANTO problema com ele que eu simplesmente ignoro.

Só uma dica, eu particularmente prefiro implementar o Artropode assim:

class Artropode
  class << self
    attr_writer :numero_de_patas
    def numero_de_patas
      @numero_de_patas ||= 6
    end
  end
end

class Formiga < Artropode
end

class Aranha < Artropode
  self.numero_de_patas = 8
end
p Artropode.numero_de_patas #6
p Formiga.numero_de_patas #6
p Aranha.numero_de_patas #8

Abraços!

2010/11/16 Guilherme Silveira <guilherme...@caelum.com.br>

Rodrigo Mendonça

unread,
Nov 16, 2010, 1:16:17 PM11/16/10
to rub...@googlegroups.com
Quando você se refere a passagem de mensagens seria quando a classe pai e/ou filha saem procurando (sobrescrita/sobrecarga) pelo método que foi chamado??

Valeus
Rodrigo Mendonça
(62) 8567-3142

Mauricio Szabo

unread,
Nov 16, 2010, 3:22:43 PM11/16/10
to rub...@googlegroups.com
Na verdade, não. Pra isso eu prefiro ou usar o nome em inglês (method lookup). Eu me refiro a chamada de métodos mesmo, tal como "pessoa.fala" (passando a mensagem "fala" para o objeto "pessoa"). 

A ideia de passagem de mensagens vem por causa de smalltalk, uma das primeiras linguagens orientadas a objeto. Vale a pena vê-la, a passagem de mensagens na linguagem é bem interessante! A ideia é que não há design-time e run-time, tudo é uma coisa só (se vc tiver q subir um servidor web, basta chamar um método de um objeto e o servidor está no ar, sem necessidade de rodar um comando qq no shell, por exemplo).

Ruby meio que herdou essa característica, tanto é que ele tem o método "send" que envia uma "mensagem" a um objeto. 

Abraços! 

Emerson Macedo

unread,
Nov 16, 2010, 3:48:24 PM11/16/10
to rub...@googlegroups.com
Guilherme,

quero expressar a minha opinião sobre o assunto de 2 formas.

1 - curta e grossa:

Isso é um defeito da implementação de O.O em Ruby

2 - Mais detalhada:

Na minha opinião, problema grave está exatamenta no ponto que você mesmo mencionou, que destaco aqui:

".. *estamos nos acoplando

também a interface E implementação dos métodos privados*.

Isto é, o acoplamento a classe pai é total, sem dó."

Realmente isso é péssimo. As literaturas mais populares sobre orientação a objetos definem que uma subclasse deve apenas herdar o comportamento que já é externamente visível, ou seja, a herança jamais deve deixar rastro de detalhes de implementação. Eu considero um método private um detalhe de implementação.

Eu sei que você também é muito ligado ao mundo Java, assim como eu, portanto sabemos que em Java isso não acontece e nem precisamos colar código aqui. Eu entendi o que o Stefano falou sobre o código de Python, mas sinceramente eu não gosto muito da implementação O.O de Python, principalmente por não ter membros privados de fato, mas que fique claro que é apenas questão de gosto, para não começarmos uma guerra de linguagens aqui :). Assim sendo fui dar uma olhada em Javascript ...

Ao contrário do que muitos pensam, Javascript é bem Orientado a Objetos por sinal e o fato de não ter classes ajuda a ilustrar essa situação de uma outra forma. Eu fiz um código aqui rapidinho e vou colocar meus pontos em cima dele:

  var Pai = function() {
   var imprime = function () {console.log('sou papai');}; //private
   return {
   fala: function() { imprime(); } // public
   };
  };
  
  var Filho = function() {
    var imprime = function () {console.log('sou filho');}; //private

    //Herança
    function F() {}
    F.prototype = new Pai();
    return new F();
  };

  var p = new Pai();
  p.fala(); //Imprime sou papai
  
  var f = new Filho();
  f.fala(); //Imprime sou papai


O conceito de closures deixa claro que as instâncias de Filho não tem possibilidade de sobrescrever o método privado imprime, pois o contexto foi fechado para as instâncias de Pai e não cabe modificação. Eu considero isso uma implementação correta.

Transportando para o mundo Java, apesar de eu não conhecer a implementação interna de classes essa implementação com closures poderia ser uma implementação válida para o constructo das classes.

Voltando ao Ruby, podemos argumentar que essa foi uma decisão para deixar a linguagem mais "aberta" e continuar seguindo o princípio de que Ruby é para "adultos que sabem o que estão fazendo", e pode até ser útil para fazer algumas alterações menos dramáticas em APIs dos outros mau feitas. Por outro lado pode incentivar gambiarras ao invés de incentivar colaboração (criar um patch para corrigir a API mau feita). Sinceramente não tenho opinião definida sobre isso.

Bem, essas são as minhas opiniões. Estou gostando da discussão e aguardo outras opiniões sobre o assunto.

[]s


2010/11/16 Guilherme Silveira <guilherme...@caelum.com.br>



--
Emerson Macedo
http://codificando.com

Stefano Diem

unread,
Nov 16, 2010, 9:56:06 PM11/16/10
to rub...@googlegroups.com
Nossa, faz muito tempo que não tenho vontade de chegar em casa para ver se alguém respondeu um e-mail!
Anyway, eu acho que a discussão está legal "pra caralho", me fazendo revisar vários conceitos e pesquisar um pouquinho pra não falar muita besteira :)

Boa sacada Maurício, o ||= faz uma boa diferença pois já inicializa a variável. O singleton_class eu coloquei só ver como ficaria, dado que não é backwards compatible em geral eu ainda fico no class << self para abrir a metaclasse.

Em relação à troca de mensagens entre objetos, no Programming Ruby tem a seguinte passagem que segue o que o Maurício falou:

Methods are invoked by sending a message to an object. The message contains the method's name, along with any parameters the method may need.[This idea of expressing method calls in the form of messages comes from Smalltalk.] When an object receives a message, it looks into its own class for a corresponding method. If found, that method is executed.

Além do método send, existe também o método call usado em blocos que também traz a idéia de passagem de mensagens, e o respond_to? que permite verificar se um objeto sabe responder a determinadas mensagens. Inclusive a idéia de troca de mensagens é o que permite (conceitualmente) que um objeto responda a uma mensagem para o qual não tem um método equivalente (que pode ser definido dinamicamente ou no method_missing). Mas particularmente gostaria que esse lado de Ruby ( de passagens de mensagens e também de tratamento de eventos) fosse mais evidenciado / utilizado, como por exemplo o fato de que mensagens ainda são chamadas de métodos (methods) em vários lugares da documentação, inclusive na documentação do método send:

obj.send(symbol [, args...]) => obj 
Invokes the method identified by symbol

Como tudo em Ruby é um Objeto, falar a respeito de mensagens faz bastante sentido (da mesma forma como em Smalltalk), mas já não faz tanto sentido esta nomenclatura quando se trata de linguagens híbridas como Java, na qual existem métodos que não pertencem à objetos e portanto o receptor da mensagem é indefinido.


Voltando agora à resposta do Guilherme, em relação à possibilidade do uso de mixins na classe Artropode, nem havia pensado na possibilidade pois sempre associo reino animal com herança simples, mas é uma sugestão válida.

Mas ao invés de fazer o mixin na metaclasse, usaria o método extend ( que faz exatamente isso, mas tem outro nome ):

module Artropode
  attr_writer :numero_de_patas

  def numero_de_patas
    @numero_de_patas ||= 6
  end
end

class Aranha
  extend Artropode
  self.numero_de_patas = 8
end

class Formiga
  extend Artropode
end
p Formiga.numero_de_patas #=> 6
p Aranha.numero_de_patas #=> 8

A maior diferença fica mesmo no fato de que Artropode deixou de ser algo concreto e virou um conceito, uma "classe abstrata" que não pode ser instanciada diretamente.
 
Abraços,
Stefano Diem Benatti

TAiLORBiRDS
phone: 55 11 9343 0994
skype:  teonimesic



2010/11/16 Emerson Macedo <emer...@gmail.com>

Stefano Diem

unread,
Nov 16, 2010, 11:07:57 PM11/16/10
to rub...@googlegroups.com
Eu não sei muito fundo a respeito de javascript em termos de orientação a objetos, então passei a última hora lendo uma pá de artigos a respeito de OO em Javascript para tentar discutir a respeito do que o Emerson falou, mas cheguei a conclusão de que a sintaxe de javascript para trabalhar com orientação a objetos me é estranha demais para eu conseguir compreender às 2 da manhã :D

Li o seguinte artigo que mostra como criar uns métodos mais normais para usar orientação à objeto em javascript, que foi mais ou menos quando decidi parar com as drogas :)

Mas dando uma olhada por cima, e portanto posso estar redondamente enganado, li que ao utilizar a forma prototypal (que faz uso de prototype) existe um problema de encapsulamento inverso no javascript: de que o atributo privato somente está disponível no escopo em que foi definido, e portanto não é possível definir um método público que acesse o atributo privato fora de seu escopo original.

No caso, o exemplo passado é o seguinte:

function MyClass() {
  var value = 1;
}
MyClass.prototype.getValue = function() { return value; }
m = new MyClass();
m.getValue(); // ReferenceError: value is not defined

Para de fato conseguir pegar o value de volta, é necessário fazer uso do this (e dessa forma torná-lo público):

function MyClass() {
  this.value = 1;
}
MyClass.prototype.getValue = function() { return this.value; }

m = new MyClass();
m.getValue(); // 1

Mas independente disso, achei super interessante a forma como é realizado a diferenciação de público ou privado nos métodos em javascript, que é basicamente uma questão de escopo (se são locais ao objeto ou não). Vou dormir com isso na cabeça e quem sabe amanhã brota alguma idéia.

Abraços,
Stefano Diem Benatti

TAiLORBiRDS
phone: 55 11 9343 0994
skype:  teonimesic



2010/11/17 Stefano Diem <teoni...@gmail.com>

Maurício Szabo

unread,
Nov 16, 2010, 8:57:15 PM11/16/10
to rub...@googlegroups.com
Sendo um pouquinho ousado, acho que a "literatura" não vale quase nada na informática. Trabalho numa universidade federal onde todos os professores são doutores, e a maioria dos professores de informática sabem 4, 5 linguagens no máximo (C, C++, Java e mais uma ou duas linguagens), inclusive alguns confundem Java com Javascript. Fora isso, sabiam que o tal "método cascata" foi proposto pela primeira vez como uma forma de não se modelar software?

Sobre o seu código de Javascript, me desculpe, mas ele não é orientado a objetos. Ele aproveita o conceito de closures, e a linha:

var imprime = function () {console.log('sou papai');};

NÃO cria um método "privado" e sim uma função anônima. O código equivalente em Ruby seria:

class Pai
  imprime = proc { puts "Papai" }
  define_method :fala do
    imprime.call
  end
end

Aproveitando o conceito de closures. O ponto que eu quero chegar é: usando  um exemplo em C++, quando se faz em Ruby:

class Algo
  def metodo
    #blabla
  end
end

O código equivalente em C++ é o seguinte:

class Algo {
  virtual void metodo() { //Note o "virtual" aqui...
    //blabla
  }
};

Porque, em Ruby, todas as classes se comportam como abstratas. Chamar isso de "erro grave" é complicado, ao meu ver, porque é a forma como a linguagem se comporta. Seria a mesma coisa que falar que Java é errado porque, comparado com C++, não permite "default parameters" como:

void metodo(int a1, int a2=0) {}

Ao mesmo tempo, esse tipo de implementação de Ruby permite uma abordagem muito mais DRY em muitos casos. Por exemplo, olhando para a gem SexpProcessor, tudo o que se precisa fazer é sobrescrever métodos "process_<algo>", e voilá! Temos um processador de S-Expressions sem nenhuma dor de cabeça. O mesmo vale para gems como FuseFS, Gosu, etc...


Só abrindo um parênteses: na minha carreira profissional, eu nunca trabalhei profissionalmente nem com Java nem com .NET... então, Java foi a linguagem que eu aprendi por conta, e a linguagem que eu achei bizarra e incomum, até descobrir que 80% das pessoas aprendem ela como primeira linguagem... então, quando me passam um código, eu nunca uso Java como forma de comparação, e pode ser por isso que eu não esteja vendo o problema que parece tão evidente pra maior parte do pessoal daqui da lista :-).

Por exemplo, Smalltalk até onde eu sei nem sequer tem métodos privados, e é uma das primeiras linguagens orientadas a objeto... então, classicamente, o comportamento de um método privado é um "plus", não um "must" (termos horríveis hahaahah) para definição de qual correta uma linguagem é, no quesito "orientação a objetos", ao meu ver...

Abraços!
Maurício Szabo.

2010/11/16 Emerson Macedo <emer...@gmail.com>



--

Maurício Szabo

unread,
Nov 17, 2010, 7:26:15 AM11/17/10
to rub...@googlegroups.com
Fazendo um pouco de propaganda, comecei a estudar bastante JS recentemente, e postei no meu blog alguns exemplos: http://mauricioszabo.wordpress.com/tag/javascript/

Sobre a passagem de mensagens em Ruby, e agora é a parte que eu reclamo da linguagem hahahaha, a idéia da linguagem talvez fosse essa, mas não é explicitada. Ao contrário de Smalltalk, aonde você tem "passagem de mensagens que funcionam como métodos", em Ruby você tem algo como "chamada de métodos que funcionam como mensagens". Tanto é que não é possível (até onde eu sei, sem o uso do Kernel#set_trace_func) interceptar mensagens para as classes, para digamos assim, inserir um "puts" antes de todos os métodos, sem recorrer a black-magic tal como:

Fixnum.instance_methods.each do |method|
  method = method.to_sym
  next if [:respond_to?, :define_method].include?(method)
  old_method = "old_#{method}"
  Fixnum.send :alias_method, old_method, method
  Fixnum.send :define_method, method do |*args, &b|
    puts "Tracing method #{method}"
    old_send old_method, *args, &b
  end
end

#Imprime:
#Tracing method times
1.times do
  puts "WOW"
end

Mas isso já é OUTRA história hahahaha.
Abraços!

2010/11/17 Stefano Diem <teoni...@gmail.com>



--

Emerson Macedo

unread,
Nov 17, 2010, 2:56:06 PM11/17/10
to rub...@googlegroups.com
Olá Maurício,

Não entendi muito bem alguns pontos que você colocou. Poderiamos nos aprofundar nesses tópicos?

@mauricio
endo um pouquinho ousado, acho que a "literatura" não vale quase nada na informática. Trabalho numa universidade federal onde todos os professores são doutores, e a maioria dos professores de informática sabem 4, 5 linguagens no máximo (C, C++, Java e mais uma ou duas linguagens), inclusive alguns confundem Java com Javascript. Fora isso, sabiam que o tal "método cascata" foi proposto pela primeira vez como uma forma de não se modelar software?

Sinceramente eu não encaro aberrações como regra. Continuo achando a literatura extremamente importante e uma grande base para nosso conhecimento. Leitura de livros de Orientação a Objetos são muito importantes para uma boa compreensão. Dificilmente se vê bons materiais de O.O fora dos livros (apesar de existir alguns poucos).

@mauricio
Sobre o seu código de Javascript, me desculpe, mas ele não é orientado a objetos. Ele aproveita o conceito de closures, e a linha:

var imprime = function () {console.log('sou papai');};

NÃO cria um método "privado" e sim uma função anônima. ...

Não compreendi muito bem o motivo pelo qual você chamou a linha de função anônima. A função tem um nome "imprime" e é chamada em seguida no método público "fala" como podemos ver abaixo:

return {
   fala: function() { imprime(); } // public
};

Agora segue o código completo:

  var Pai = function() {
   var imprime = function () {console.log('sou papai');}; //private
   return {
   fala: function() { imprime(); } // public
   };
  };

  var p = new Pai();
  p.fala(); //Imprime sou papai

Esse código é um código O.O sim. Javascript não tem classes mais é uma linguagem baseada em protótipo, closures e funções de primeira classe, o que permite uma utilização O.O ótima. Não entendi muito bem seu ponto, poderia explicar melhor?

@mauricio
Porque, em Ruby, todas as classes se comportam como abstratas. Chamar isso de "erro grave" é complicado, ao meu ver, porque é a forma como a linguagem se comporta. Seria a mesma coisa que falar que Java é errado porque, comparado com C++, não permite "default parameters" como:

void metodo(int a1, int a2=0) {}

Me permita discordar, mas classes abstratas são aquelas que não permitem instanciação e classes Ruby permitem isso diretamente. O acoplamento total a classe Pai realmente é muito ruim. Isso gera diversos problemas que vários colegas já descreveram aqui na lista. Basta agente dar uma lida. Quanto a dizer que uma linguagem é errada e outra certa eu acho que ninguém disse isso e está fora de escopo discutirmos se uma linguagem é pior ou melhor aqui nesse forum, até porque todos nós aqui somos rubistas. O que estamos discutindo é uma implementação de O.O em Ruby em específico, o que não faz a linguagem melhor ou pior que outras.

@mauricio
Ao mesmo tempo, esse tipo de implementação de Ruby permite uma abordagem muito mais DRY em muitos casos. Por exemplo, olhando para a gem SexpProcessor, tudo o que se precisa fazer é sobrescrever métodos "process_<algo>", e voilá! Temos um processador de S-Expressions sem nenhuma dor de cabeça. O mesmo vale para gems como FuseFS, Gosu, etc...

Como eu havia dito no post anterior, usar esse recurso para fazer essas coisas pode quebrar toda a sua aplicação caso a gem mude sua implementação interna e isso pode te dar uma bela dor de cabeça.

@mauricio
Por exemplo, Smalltalk até onde eu sei nem sequer tem métodos privados, e é uma das primeiras linguagens orientadas a objeto... então, classicamente, o comportamento de um método privado é um "plus", não um "must" (termos horríveis hahaahah) para definição de qual correta uma linguagem é, no quesito "orientação a objetos", ao meu ver...

Eu sou daqueles que acho que todos os atributos deveriam ser privados e todos os métodos públicos, mas isso é apenas a minha opinião, baseada na experiência de que isso força você a dividir melhor a responsabilidade dos seus objetos. Um objeto com muitos métodos private pode estar mostrando um sintoma de que deveria existir outro objeto e uma colaboração entre eles. Nesse ponto eu concodo contigo, Smalltalk é uma ótima referência. A única coisa que eu não concordo é que ninguém aqui ta tentando definir qual linguagem é mais O.O que a outra, se é que eu entendi bem o que você quis dizer.

Continuemos então nessa ótima discussão.

Abraços,

2010/11/16 Maurício Szabo <maurici...@gmail.com>

Emerson Macedo

unread,
Nov 17, 2010, 2:59:49 PM11/17/10
to rub...@googlegroups.com
Stefano,

Não pretendo fazer disso uma thread de Javascript. Só usei o exemplo pra dizer que nessa linguagem também funciona o conceito de que herança deve se basear somente na interface pública. Me desculpem se isso prejudicou a thread de alguma forma. A intenção era outra.

Abraços,

2010/11/17 Stefano Diem <teoni...@gmail.com>

Maurício Szabo

unread,
Nov 17, 2010, 6:54:40 PM11/17/10
to rub...@googlegroups.com
Bom, vamos lá :-)

Sobre a literatura, fui infeliz no comentário mesmo. Eu quis dizer, na verdade, livros baseados na opinião de cientistas da computação sem vivência prática. Por exemplo, os livros do Robert Martin são bons porque são baseados na vivência dele. Agora, muitos livros que falam sobre a "essencia do OO" ou "como fazer OO" são escritos por doutores que nunca fizeram um software de mais de 20.000 linhas (e receio que isso não é exagero).

Sobre a idéia de função anônima, eu vou tentar explicar um pouco melhor: isso:
function imprime() {}

Cria uma função chamada imprime. Já isso:
var imprime = function() {}

Cria uma função anônima, que é atribuída à variável local "imprime". Isso evidencia-se quando você faz isso:

function criarFuncao() {
  var outra = function() {
    console.log("WOW");
  }
}

Porque, nesse caso, cada vez que for chamado o "criarFuncao" uma nova função será criada. O "outra" não é o nome da função, apenas uma variável. É meio difícil de capturar a diferença porque Javascript é baseado em variáveis globais, e quando você define uma função com nome é a mesma coisa que atribuí-la a uma variável global, mas a idéia é a seguinte: o código:

var imprime = function () {console.log('sou papai');};
fala = function() { return imprime(); }

é, na verdade, equivalente à versão em Ruby:

imprime = proc { puts "Sou papai" }
def fala
  imprime.call
end

Eu sei que é essa a forma que Javascript tem de orientação a objetos. Mas o que eu tou citando é que, na verdade, essa é uma abordagem muito mais do paradigma funcional do que do orientado a objetos. Eu sei que JS tem protótipos e tem um suporte legal pra Orientação a Objetos, e o seu código de fato é orientado a objeto. O ponto, e isso é o importante, é que JS não tem funções/métodos privados, mas é possível simular usando closures e funções anônimas atreladas a variáveis locais... como no exemplo.


Por fim, a parte sobre classes abstratas não é pensando em Java, mas pensando em C++ hahahaa. Como eu disse no post anterior, eu nunca uso Java como comparação por causa do meu histórico c/ a linguagem. Então vamos lá:

Em C++, diz-se que uma classe é "abstrata" quando todos os seus métodos são "virtuais". Isso significa que a classe pode ser instanciada, mas o propósito da classe é ser herdada. Ou seja:

class Algo {
  virtual void imprime() {}
  virtual int somar(int a, int b) {}
};

O comportamento dessa classe, quando instanciada, é idêntico ao de Ruby. Foi nesse sentido :-)

Sobre smalltalk, eu só citei pra dar um exemplo de uma linguagem que não tinha sequer métodos private, pra tentar mostrar uma outra idéia sobre o assunto. Não quis dizer apenas "qual é mais OO do que outra", apenas apresentar que eu realmente não considero um defeito da implementação OO em Ruby só pq ele tem um comportamento que difere de Java :-)

Abraços! (e realmente, essa discussão tá excelente hahaha)

2010/11/17 Emerson Macedo <emer...@gmail.com>

Emerson Macedo

unread,
Nov 17, 2010, 7:40:41 PM11/17/10
to rub...@googlegroups.com
Ta ficando bom ... :)

@mauricio
Sobre a idéia de função anônima, eu vou tentar explicar um pouco melhor: isso:
function imprime() {}

Cria uma função chamada imprime. Já isso:
var imprime = function() {}
Esse código é praticamente o mesmo, exceto pelo fato que a primeira sintaxe define a função em parse-time e a segunda em run-time. Lógico que eu usei o truque da função anônima por preferência, mas meu exemplo poderia ser facilmente modificado para:

  var Pai = function() {
   function imprime() {console.log('sou papai');};
   return {
   fala: function() { imprime(); }
   };
  };

Agora não estou usando mais o truque da função anônima, mas não mudou nada, exceto pelo fato de imprime passar a existir apenas em run-time, o que para o exemplo pouco importa.

@mauricio
O ponto, e isso é o importante, é que JS não tem funções/métodos privados, mas é possível simular usando closures e funções anônimas atreladas a variáveis locais... como no exemplo.

Então, a definição de método privados ou não não é atrelada a um modificadores de acesso como public/private/protected, mas como o environment funciona. É claro que a nossa cabeça esta chaveada para entender dessa forma, mas a partir do momento que eu consigo "definir" um método que não fica visível externamente e não é herdado e outro que faz parte da minha API e pode ser extendido/modificado estou implementando esses conceitos da Orientação a Objetos de forma correta. Eu sinceramente acredito que os termos class, public, protected, private, ficaram populares e são a opção mais usada na maioria das linguagens, mas isso não faz com que linguagens que usam outros mecanismos sejam um "O.O simulado". Simplesmente esses constructos são os mais populares.

@mauricio
Sobre smalltalk, eu só citei pra dar um exemplo de uma linguagem que não tinha sequer métodos private, pra tentar mostrar uma outra idéia sobre o assunto. Não quis dizer apenas "qual é mais OO do que outra", apenas apresentar que eu realmente não considero um defeito da implementação OO em Ruby só pq ele tem um comportamento que difere de Java :-)

Com relação ao defeito de implementação O.O eu acredito que realmente seja porque fere de maneira muito agressiva o princípio do encapsulamento. Mas isso é a minha opinião apenas. Ainda não encontrei contra-argumentos que façam sentido.

Abraços,

2010/11/17 Maurício Szabo <maurici...@gmail.com>

Maurício Szabo

unread,
Nov 17, 2010, 8:19:56 PM11/17/10
to rub...@googlegroups.com
Nossa, foi mal, fui testar uma coisa agora, e vi que eu tava errado em um detalhe: eu achei que, em JS, ao definir uma função com:

function imprime()

Ela passaria a existir em escopo global, e parece que não é o caso. Ela existe em escopo local... ou algo semelhante :-). Embora eu ainda insista que é mais funcional do que OO, e não vejo nada de errado com isso tbm :-). Só acho que, nesse caso, é melhor usar o exemplo equivalente em Ruby, usando "proc" e "define_method", pq nesse caso pode-se usar a closure para criar o "private".

Eu estava pensando que isso me gerou uma idéia ótima pro blog, um dos "coisas que vc nunca quis fazer com Ruby" hahahaha. Vou tentar implementar algo e posto aqui o resultado :-)

Abraços!

2010/11/17 Emerson Macedo <emer...@gmail.com>

Maurício Szabo

unread,
Nov 26, 2010, 8:36:37 AM11/26/10
to rub...@googlegroups.com
Bom, conforme eu tinha prometido, tentei montar uma maneira bizarra de fazer métodos privados em Ruby. Segue o link do post.

http://mauricioszabo.wordpress.com/2010/11/26/metodos-privados-semelhantes-a-javac-em-ruby/

Ficou meio confuso, mas em linhas gerais funciona... claro que não é recomendado pra nenhum uso a não ser como experimento, mas foi divertido de montar um código desses :-)

Abraços!
Maurício Szabo

2010/11/17 Maurício Szabo <maurici...@gmail.com>
It seems to me that measuring maturity is a very immature thing to do. Teens talk about their maturity, adults don't.
(Robert Martin)
Reply all
Reply to author
Forward
0 new messages