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/
--
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
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*.
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?
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>:
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!
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>:
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>:
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.
obj.send(symbol [, args...]) => obj
Invokes the method identified by symbol