Conflito de personalidade
Publicado em Developer
O Giuliani Sanches estava falando comigo agora há pouco e me contou de um framework em PHP chamado Akelos, que clama para si ser um port do Ruby on Rails em PHP, como diz em sua página:
Being port of Ruby on Rails to PHP Akelos is also optimized for programmer happiness and sustainable productivity. It lets you write beautiful PHP Code by favoring convention over configuration.
Não cheguei a testar o Akelos, até por que estou bem confortável com o Rails (apesar de ainda manter bastante coisa em PHP), mas, frameworks à parte, estava pensando nas diferenças do que os movem: as linguagens.
Antes de tentar criar qualquer flamewar por aqui, quero pedir que por favor não levem qualquer comparação no post como uma crítica nervosa ou destrutiva para as linguagens, e sim mais como constatação de algumas coisas esquisitas que se fossem diferentes poderiam ajudar bastante em um caso como a construção de um framework, e particularmente, como a minha referência para a questão em particular que vou registrar aqui, pois sempre esqueço onde ficam os meus arquivos de teste para essa situação. ;-)
Algum tempo atrás eu estava fazendo algumas coisinhas em PHP e precisava que uma classe retornasse, através de um método estático, o seu próprio nome. Em um primeiro momento funcionou, porém as classes filhas da classe com o método que retornava o nome da classe insistiam que eram os pais! Conflito de personalidade? Aqui vai um pouco de código para demonstrar isso:
1 <?php 2 class Pai { 3 function name(){ 4 return get_class(); 5 } 6 } 7 8 class Filha extends Pai { 9 } 10 11 print Pai::name(); 12 print Filha::name(); 13 ?>
Rodando o programa:
[taq@~]php static.php PaiPai
Ops, parece que há alguma coisa errada ali. Lógico que isso não é o fim do mundo nem impede alguém de fazer alguns ótimos aplicativos usando PHP. Mas com certeza pode ser uma pedra no sapato se você precisar justamente do comportamento correto, ainda mais quando a coisa entra na discussão sobre ser um bug ou o jeito que a linguagem funciona.
Vamos dar uma olhada como isso funciona em Ruby:
1 class Pai 2 def self.name 3 self 4 end 5 end 6 7 class Filha < Pai 8 end 9 10 puts Pai.name 11 puts Filha.name
Rodando o programa:
[taq@~]ruby static.rb Pai Filha
Na minha opinião, esse é o comportamento correto, o que vocês acham?
Para finalizar, resolvi fazer o mesmo teste em Java. Rapaz, foi difícil e ainda não cheguei em um resultado conclusivo (não que tenha tentando taaaaanto assim também) pois existe muita discussão sobre esse assunto e me parece que o jeito correto de fazer isso, mas com um comportamento similar ao PHP, é:
1 class Pai { 2 public static String name(){ 3 return new Throwable().getStackTrace()[0].getClassName(); 4 } 5 } 6 7 class Filha extends Pai { 8 } 9 10 public class StaticClass { 11 public static void main(String args[]){ 12 System.out.println(Pai.name()); 13 System.out.println(Filha.name()); 14 } 15 }
Rodando o programa:
[taq@~]java StaticClass Pai Pai
Particularmente eu achei a solução meio "remendão", mas nada contra usar Java, afinal, eu uso também, mas putz, podiam ter feito isso de um jeito melhor.
Comentários
Comentários fechados.
Artigos anteriores
- Pull requests em modo raiz - sex, 22 de dezembro de 2023, 09:57:09 -0300
- Qual a idade do seu repositório? - ter, 27 de dezembro de 2022, 12:50:35 -0300
- Utilizando ctags em projetos Rails mais recentes - qui, 24 de junho de 2021, 08:23:43 -0300
- Fazendo o seu projeto brotar - seg, 15 de julho de 2019, 08:57:05 -0300
- Learn Functional Programming with Elixir - sex, 02 de março de 2018, 18:47:13 -0300
- Ambiente mínimo - Driver Driven Development - qua, 23 de agosto de 2017, 15:15:03 -0300
- Ambiente mínimo - repositórios de código - dom, 16 de abril de 2017, 13:02:14 -0300
- Ambiente mínimo - terminal e navegador - dom, 02 de abril de 2017, 21:43:29 -0300
- Utilizando muitas gems no seu projeto? - sáb, 29 de outubro de 2016, 11:57:55 -0200
- Desenvolvedores e inteligência artificial - seg, 11 de julho de 2016, 09:09:38 -0300
Michael, beleza?
Assim funciona pois nesse caso o método name não foi herdado da classe Pai, sendo reescrito na classe Filha. O lance seria não precisar fazer isso, talvez até através de alguma referência explicíta, tipo um "self", ali no get_class, para indicar que estaríamos interessados na classe corrente e não na classe que, como você disse, contém o bloco original de código. Isso permitiria não reescrever os mesmos métodos em todos os descendentes.
Abração!
Olá TAQ,
primeiro gostaria de dizer... Java é o bicho, oh linguagem rebuscada...
Quanto o PHP, isto ocorre porque você está chamando o método pela classe pai, de fato, a classe pai irá pegar o nome da classe referente ao bloco. Como não conheço muitas outrtas linguagens, me parece normal esta atitude. Talvez se você tentasse algo como:
Istó irá retornar a classe normalmente.
Sobre os active records: são, eles são coisa lindideus :)
Em Java, o JPA usa anotações ou reflection pra pegar o nome da tabela (se vc não especificar por anotação). O problema do static não existe, pq JPA não usa herança, é desacoplado (vantagens e desvantagens aqui).
Em PHP, pior: eu tive que fazer minha própria classe "meia boca" de anotações.
Ah, agora eu entendi seu exemplo.
Bem, este seu caso mostrou o quão mais livres são os métodos estáticos no Ruby, afinal o próprio new é um método estático. :¬)
Não sei no PHP, mas as coisas no Java são bem mais restritas desde o começo. Já não seria possível implementar o Active Record por vários outros motivos. Por exemplo, o find_by_property poderia, no máximo, ser simulado usando um parâmetro String.
É nessas horas que temos que mudar, além da linguagem, o "dialeto" utilizado.
Uia, choveram comentários de ontem para hoje, e todos que estão aqui são ótimos! Obrigado gente!
Ontem eu estava tão cansado que cheguei em casa e fui dormir as 20:30, nem liguei computador nem nada quando cheguei ... mas vamos lá.
Wilerson, os captchas tem que ser chatinhos né. Eu estava recebendo muita porcariada por aqui e tendo que limpar "na unha", mas pode deixar que vou colocar um aviso sobre estarem em minúsculas! Como exemplo de código, posso pensar em um primeiro momento em algo tipo o método find do ActiveRecord. Vamos supor que temos uma classe base e várias classes filhas que tem propriedades diferentes que precisam ser preenchidas após uma consulta no banco, retornando *uma classe nova do mesmo tipo onde o método find foi utilizado*.
Exemplificando com um código apenas para o exemplo, http://pastebin.com/f5a40704e . Repare que mesmo se eu não quisesse o retorno do mesmo tipo de objeto onde foi executado o método find() (ou seja, Cliente ou Nota), eu não saberia qual a tabela certa para procurar o ID. Ok, poderíamos ter criado uma instância do objeto e utilizado o find diretamente nele, o que ia identificar a tabela correta e preencher a própria instância com os valores retornados. Mas fica aí o exemplo do nome da classe em um método estático da própria classe sem ter uma instância do objeto, mas retornando a dita cuja. :-)
Thiago, sim, você tem razão, mas fiz a redundância justamente para explicitar um método estático ali. No caso é só acessar sim Pai.name ou Filha.name que você tem uma String indicando o nome da classe, eu quis só destacar o método estático com o self.name para mostrar que o bicho ali é estático *mesmo*. Sobre retornar o self ali no método, nenhum problema para esse exemplo, pois eu usei puts que pegou a representação da Class, mas sinta-se livre para retornar self.to_s se preciso, ou o próprio name anterior, ou nem usar, já que não precisa né :-). Em relação ao PHP, ali você já dá uma dica fora da classe, o X da questão é o próprio objeto de auto-avaliar e retornar a que classe pertence. Sobre a sua observação sobre os métodos, sim, estão corretas, os "operadores" no caso ali são métodos (o que nos dá uma grande flexibilidade em "brincar" com eles) que tiveram a sua precedência alterada em favor da boa e velha matemática. Senão viraria um rolo também! Convém prestar atenção também na precedência dos operadores lógicos como !, &&, || e not, and e or, ainda mais em operações de "curto-circuito".
Maximiliano, é, realmente em Java o negócio funciona legal mesmo somente com uma instância do objeto, de resto é isso que você falou mesmo, gambiarra para tudo quanto é lado, como a turma estava discutindo nas URLs que eu coloquei no post ...
Tirando um pouco da parte teórica a favor da prática (e sendo MUITO suspeito ao falar isso), acho que Ruby deu um melhor resultado ali quando o que se espera é o termo que o Thiago usou, o "Princípio do Óbvio", que nos retorna o que esperamos naquele ponto.
Thiago, eu falei do Princípio da Mínima Surpresa considerando que estado estático normalmente não é polimórfico. Embora isso não seja exatamente uma "regra", vale para Java, PHP, C# e C++ (entre outras, talvez). Ruby "quebra esta regra", para que quem use um método estático numa subclasse tenha um comportamento similar ao da superclasse.
Ola,
Quando a php e ruby eu nao sou nenhum grande especialista ... mas conheço bem java.
Na especificação da linguagem java, diz que métodos ESTATICOS nao sao herdados, logo qualquer tentative de implementar a sua necessidade é uma gambiarra.
Em java voce so pode ter referencias a instancias de objetos ou no maximo uma referencia a "Class" de um objeto (que nada mais é que uma classe com meta informacoes de outra classe)
Para pegar o nome da classe de um objeto instanciado: objeto.class.getName();
abraços
No codigo do ruby, você não precisa definir o método 'name'. Além do mais, vc fez ele retornar self (?!).
> Pai.name.class #exibe 'Class', mas espera-se 'String'.
Ao declarar classes, o método 'name' ja é criado para elas:
> class Pai
> end
>
> class Filha < Pai
> end
>
> puts Pai.name ":" + Filha.name #Pai:Filha
Já em PHP, a classe e seu nome...er, bem, observe o seguinte codigo:
> class Pai { }
>
> $klass = Pai; //com ou sem aspas, whatever. Isso eh uma string
> echo "class name: " . $klass;
> $inst = new $klass;
Então, se vc tem algo (uma variável ou valor) que representa sua classe, provavelmente ela é uma string. Aí está o nome dela. Mas, por ela ser uma string, ao invés de ser uma instância Class, por exemplo, não se pode fazer:
> $klass->staticFunc();
pois classes não são valores em PHP (a não ser que se assuma que uma string seja uma representação aceitável da classe).
[[Newton: Afinal de contas, um método estático é um método da classe, poderíamos comparar isso a um atributo privado.]]
Um método estático comparado à um atributo privado? Em que sentido?
[[Newton: mesmo que isso possa comprometer teorias de comportamento.]]
O que quer dizer?
[[Wilerson: mas acho que o Ruby implementou desta maneira para obedecer o Princípio da Mínima Surpresa.]]
Veja bem, nos exemplos de PHP e Java, a linguagem parece...faltando faltando algo, ou dificultando uma tarefa simples. Em java, especialmente, chegou-se a pensar em fazer uns voodoos, e mesmo assim, a coisa não funcionou.
Um exemplo de "Princípio da Mínima Surpresa" é o caso das operações aritméticas em Ruby: não existem operadores +,-,*, etc. Eles são métodos. E a única precedência de avaliação de métodos é da esquerda para a direita. Portanto, 1 + 2 * 3 seria 6. Uma vez que isso "surpreende", o pessoal do Ruby implementou precedência *apenas* para estes métodos. Sendo assim, 1 + 2 * 3, em ruby, é 7. Isso seria "Princípio da Mínima Surpresa": quebrar a coerência e uniformidade gramatical quando elas causam surpresas, por exemplo. O que se espera da linguagem é mais importante e tem maior prioridade do que ter ao final uma gramática limpa.
No exemplo de código do Ruby, nenhuma regra que antes era uniforme foi quebrada e o comportamento executado não é a exceção da regra (como no caso da avaliação de métodos aritméticos). Por isso, neste caso, acho que não se trata de "Princípio da Mínima Surpresa", mas do "Princípio do Óbvio" ;)
Outra coisa, que chatinho esse seu "captcha" ser case-sensitive. :P
Só por curiosidade, qual foi a necessidade que você teve de saber o nome da classe em um método estático da própria classe? Não consigo imaginar uma situação em que isso seja necessário.
Eu concordo com o que o Newton disse sobre métodos estáticos serem métodos da classe, mas acho que o Ruby implementou desta maneira para obedecer o Princípio da Mínima Surpresa. :)
Já havia ouvido falar desse problema, mas assim como o Rael, também tenho minhas dúvidas em relação a qual o método certo na teoria. Afinal de contas, um método estático é um método da classe, poderíamos comparar isso a um atributo privado.
Mas tenho que concordar que o Ruby é uma linguagem que preza pela facilidade, mesmo que isso possa comprometer teorias de comportamento.
Ainda sobre qual achar o comportamento correto: eu não sei na teoria o que está certo ou não, MAS eu já apanhei várias vezes por esperar o comportamento exibido por Ruby e não ter ele em Java e PHP.
TaQ, para imprimir Pai, Filha em Java, vc poderia fazer diretamente: http://pastebin.com/f65f8da73
Ainda sobre statics em Java: tem umas coisas estranhas mesmo, tipo: http://pastebin.com/m3cb7dacb
Isso imprime Pai, Pai. Lembro de ter lido umas explicações doidas, sobre static não ser overriding pela classe filha e tals, pq static só pertence a própria classe, etc...
Bom, pra ter o mesmo resultado em Java que você já tem (Pai, Pai), você pode usar: Pai.class.getName();