Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Bruno Augusto

[Resolvido] E quando assinaturas de interface...

Recommended Posts

Boas,

 

A grande maioria de minhas classes derivam de uma classe base chamada Object, assim tenho um mínimo de tipagem inter-objetos.

 

Então eu decidi/precisei implementar o Padrão Observer. Inicialmente eu o tive implementado em classes separadas e funcionava perfeitamente, mas chegava a ser ridículo ter uma classe só para Subject e outra só para ser Observer.

 

Então eu decidi fazer com que todos os meus objetos que derivassem de Object serem, também, Subjects.

 

Até aí, tudo bem. Como a partir de agora um Object é um Subject, estendi a classe.

 

Porém eu queria mais. Quis fazer com cada Object pudesse atualizar a si mesmo e, então, decidi implementar nele a interface Observer de método único, a saber, Observer::update().

 

Foi então que começaram os conflitos de assinaturas.

 

Por exemplo: No meu Table Manager, tenho como parte do CRUD básico, o método Manager::update() que, dada a relativa simplicidade não possui argumentos.

 

Mas como essa classe também estende Object (e isso é vital nesse exemplo) devo implementar Observer::update() que contém como argumento o Subject atualizado e, assim, as assinaturas conflitam.

 

Claro, eu poderia muito bem alterar a interface para que o assunto pudesse ser opcional e simplesmente equalizar as assinaturas, mas isso é gambiarra das grossas.

 

Alguma opinião?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Poderia postar o código? Acho que se eu puder ajudar, para para mim ficaria mais fácil entender.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não havia postado antes para não justificar esse tópico estar em PHP.

 

Postarei apenas os fragmentos que afetam:

 

Subject.php

class Subject extends \stdClass implements \SplSubject {}

Object.php

class Object extends Observer\Subject {}

Table\Manager.php

 

class Manager extends Object {

   // ...

   public function select() {}

   public function insert() {}

   public function update() {}

   public function delete() {}
}

Isso por si só funciona, as implementações são irrelevantes para a discussão.

 

O problema como eu disse começa quando quero permitir que os objetos que estendam de Object possam se auto-atualizar.

 

Para isso a classe Object recebe uma interface que e deve implementar seu método:

 

class Object extends Observer\Subject implements \SplObserver {

   // ...

   public function update( SplSubject $subject ) {

   }
}

E isso estraga tudo pois o Table\Manager ali em cima tem um método de mesmo nome por pura comodidade (e harmonia).

 

Mas a assinatura do método conflita. Se eu quisesse contornar eu poderia fazer:

 

[code]class Manager extends Object {

   // ...

   public function select() {}

   public function insert() {}

   public function update( SplSubject $subject ) {}

   public function delete() {}
}

Esse problema em particular sumiria, mas ganho um novo pois não faz sentido nenhum, pelo menos não por enquanto, exigir um Subject quando eu quiser atualizar uma tabela.

 

Daí entra a consideração final do post anterior. Eu poderia tornar esse método opcional na interface e, consequentemente na Object e varrer o problema pra baixo do tapete.

 

Mas não é bem assim que a banda toca.

Compartilhar este post


Link para o post
Compartilhar em outros sites

A primeira pergunta que deves te fazer é a seguinte: faz sentido para um Manager implementar um SplSubject?

Sim porque ele também pode ser um Assunto Observável. Hoje não tenho uma aplicação prática que justifique ele ser um um, mas existem casos que tal Padrão de aplica.

 

Posso por exemplo ter um Observer que dispare um alert() JavaScript toda vez que um registro for atualizado. Ao invés de condicionar o retorno de Driver::getAffectedRows() posso simplesmente notificar, em Manager:update(), que tal ação ocorreu e o Observador se encarregaria de mostrar a mensagem.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Ninguém tem nenhuma idéia de como se procede corretamente nesses casos?

 

Estive pensando em "clonar" a interface nativa SplObserver para poder fazer o tal método update() com outro nome.

 

Não se é uma idéia válida ou não e tô meio sem idéias de um nome sutil? updateSubject() é feio.:P

Compartilhar este post


Link para o post
Compartilhar em outros sites

É um bom nome. Sem dúvidas melhor que o meu.

 

Mas do ponto de vista da Orientação a Objetos, há algo melhor?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Do ponto de vista de OOP (de acordo com meus conhecimentos) tem algumas coisas estranhas, mas beleza =P

Na teoria, a persistência de dados é um item opcional no ciclo de vida de um objeto, e não é necessariamente realizada em um banco de dados.

 

Para evitar este problema de conflito com o método update() (e pensando no que falei acima), sugiro que você substitua os métodos Manager::insert() e Manager::update() por Manager::persist(). Dessa forma quem for utilizar sua classe não precisa controlar qual método chamar - insert ou update - o ciclo de uso será simples. Algo como:

 

<?php
// Inserção
$joao = new Person('João');
$manager->persist($joao);

// Atualização
$maria = $manager->select(/* ... */);
$maria->setAge(14);
$manager->persist($maria);

Compartilhar este post


Link para o post
Compartilhar em outros sites

Muitíssimo grato, Icobucci.

 

Você não imagina quantas coisas eu pude assimilar, além desse problema em particular, com esse simples exemplo.

 

Mas já que você levantou a lebre e dada a dificuldade que eu tive em implementar esse recurso, quais outros pontos você consideraria errado?

 

Porque se você conseguiu ver problemas com esse fragmento sem corpo que postei, pode me ser de muita valia sua opinião.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Que bom que ajudei =D

Na minha visão você criou uma complexidade extra desnecessária nos teus objetos com a herança da class Object. Na OOP cada classe representa um tipo e como tipo deve possuir uma única responsabilidade. Na minha avaliação, essa classe Object pode representar tudo e nada ao mesmo tempo, e isso é um tanto problemático.

 

Conceitualmente a utilização da herança não é apenas para "reutilizar métodos", mas principalmente dizer que o tipo FILHO além de compartilhar os métodos do tipo PAI é um tipo derivado. Por exemplo, um Professor pode estender o tipo Pessoa, pois partilham do mesmo contexto (um professor É uma pessoa). Agora um Avião não pode estender um Pássaro, porque mesmo que os dois saibam voar, eles tem contextos completamente diferentes. Neste caso uma interface resolveria a questão da padronização das assinaturas e talz.

 

Outro problema causado por estender o tipo Object é que você acaba "queimando" a possibilidade da classes estender algo que compartilhe o contexto (e que não necessariamente precise estender o Object). No futuro provavelmente a classe Object será um objeto inchado com métodos que "deturpam a essência" do que você pensou inicialmente e/ou você terá que fazer "pequenos xunxos" nas suas classes, por exemplo a troca de uma herança por agregação em objetos que representam coisas afins.

 

Fui um pouco repetitivo, mas espero que tenha sido didático. Em resumo digo: o objetivo da OOP é simplificar a vida, e na minha visão você tá complicando um pouquin ;-)

 

Dentre as palestras que dou existe uma que acho que pode te ajudar:

Compartilhar este post


Link para o post
Compartilhar em outros sites

Então cara, a idéia é que toda classe é um Object. Essa classe faz pouca coisa, mas esse pouco elimina muita duplicação de código em várias classes. E eu ainda consigo um mínimo de tipagem.

 

E uma das coisas mais bacanas que ela permite, mesmo que ainda não esteja de todo elaborada ou mesmo madura, é simular o mais básico dos recursos das Traits no PHP 5.3.

 

Isso me proporciona um código além de funcional, elegante, coisa que eu eu sou irritantemente exigente.

 

Só agora com a implementação do Observer (e do Subject) nessa classe é que ela vai crescer um pouquinho.

 

Como todo código eu preciso refatorar algumas coisas, já tenho idéias de outras, mas vou fazê-las depois de começar os testes, coisa que eu vergonhosamente admito nunca ter feito.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Ok, toda classe é um Object, mas o que o Object representa?

Se você responder "tudo" você tem um problema conceitual. Como disse anteriormente, a classe é a representação sintática um objeto e um objeto representa algo concreto. Mesmo não estando nos seus códigos, o Object é uma classe abstrata, e acaba atingindo um nível bem grande de abstração. Esse nível alto de abstração faz com que você perca a coesão do seu objeto.

 

Eu entendo o seu objetivo com ela, mas a minha opinião é que este não é o melhor caminho =D

Como disse antes, com o passar do tempo você terá o seguinte cenário: vai acabar colocando métodos no Object para atender a uma necessidade de um objeto ou criará uma hierarquia complexa.

 

Se você executar o PDepend ou o PHPMD eles provavelmente acusarão alguns problemas nesta questão de complexidade.

 

Vamos combinar o seguinte, se você optar a continuar neste caminho e continuar desenvolvendo o software você entra em contato daqui a um ano pra eu analisar seu código. Te dou um prêmio se não encontrarmos problemas que vão contra os princípios SOLID =P

Compartilhar este post


Link para o post
Compartilhar em outros sites

Que existem violações SOLID não tenho dúvidas afinal eu mexo com OOP há pouquíssimo tempo (relativamente falando) e estou aprendendo ainda.

 

Quanto ao número de métodos, hoje são poucos e pretendo que continue assim para que a classe Object represente apenas isso, um Objeto. Toda e qualquer necessidade do objeto estão sendo definidas nas classes, concretas ou abstratas, sempre de acordo a interface (quando aplicado).

 

Sobre o PHPMD, de fato ele reportou duas coisas que me chamaram a atenção: NPath compexity elevado em alguns métodos, de algumas classes (coisa que estou reduzindo aos poucos) e o número de classes filhas de Object, como você acabou de dizer, assim como um Syntatic Sugar que o @Evandro Oliveira demonstrou pra mim.

 

De fato, Object não representa uma coisa. Ele apenas define um tipo, o tipo Object. Eu sei que o melhor seria que uma interface definisse um tipo, mas acabaria em redundância.

 

PHPDepend não entendi como funciona. <_<

 

Tão logo eu efetue os testes nas classes eu enviarei ao GITHub e, quem sabe, você não veja.

 

Nesse meio tempo, que abordagem você assumiria?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Cara, você tá entendendo errado o conceito de Observer. Dê uma pesquisada melhor sobre... Se não conseguir entender onde está errando, posta aqui que eu tento reescrever o post imenso que fiz, antes do meu PC dar pau e reiniciar --'... Agora estou meio sem tempo pra fazê-lo...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Oras, tem algo mais além do fato de um ou mais objetos que esteja(m) observando outro executar alguma rotina quando este que esteja sendo observado notificar que fez alguma coisa?

 

Fiquei curioso agora.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Escrevi errado, tem mais a ver com a implementação, não com o conceito... Preciso pegar um voo em 2h, por isso, estou meio sem tempo hoje... Se conseguir, amanhã eu explico o que eu quis dizer...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom, é o seguinte, eu acho o padrão Observer um dos mais fantásticos justamente pelo fato de ser só uma ideia, que pode ser implementada de N maneiras diferentes sem deixar de ser um observer...

 

Tá, mas essa não é a ideia de um padrão???

 

Em teoria sim, mas padrões como Singleton, por exemplo, são muito mais inflexíveis.

 

Essa liberdade que nos é oferecida, em certos casos, pode ser prejudicial, nos levando a ideias equivocadas.

 

Voltando ao seu exemplo, o problema de implementação é só a nomenclatura, até aí, beleza...

Mas acho que você falhou um pouco na hora de interpretar que todo objeto é um "Subject" e também um "Observer":

class Object extends Observer\Subject implements \SplObserver

 

Ah, mas todos objetos PODEM SER subjects ou observers...

 

"Pode ser um", não significa "é um", logo, por definição, não cabe uma herança aí...

 

Esse é o problema conceitual de que falei, mas não é exatamente sobre o padrão, é sobre OO mesmo...

 

O problema de implementação do padrão eu explico agora, voltando em algo PARECIDO com o que você mesmo citou no post #3.

 

Imagine que um certo observer precisa obter alguns dados do subject em questão. Com classes genéricas, como está fazendo, seria algo assim:

class AbstractSubject {
public function notify() {
	// aquela implementação padrão...
}
}

class SubjectWithData extends AbstractSubject {
public function getUpdateData() {
	// retorna algum dado...
}
}

 

Agora, seguindo a implementação "padrão", temos a seguinte interface para Observer:

interface iObserver {
public function update(AbstractSubject $subject);
}

 

Supunhemos então que um certo observer precisa dos dados fornecidos somente pelos objetos do tipo SubjectWithData, teríamos algo assim:

class ObserverWithData implements iObserver {
public function update(AbstractSubject $subject) {
	if(! $subject instanceof SubjectWithData) {
		throw new Exception('ObserverWithData can only observe SubjectWithData objects!');
	}

	$data = $subject->getUpdateData();
	// Faz o que tem que fazer...
}
}

 

Sentiu o cheirinho no ar??? Estamos quebrando o contrato da interface Observer. Se fosse Java, não poderíamos fazer isso, a não ser que adicionássemos uma cláusula throws no método definido na interface...

 

Qual a solução??? Separar os dois modelos!

class SubjectWithData extends AbstractSubject {
//...
}

interface iObserverWithData {
public function update(SubjectWithData $subject);
}

interface iObserverWithoutData {
public function update(AbstractSubject $subject);
}

class ObserverWithData implements iObserverWithData {
public function update(SubjectWithData $subject) {
	$data = $subject->getUpdateData();
	// Faz o que tem que fazer...
}
}

 

Tá melhor, mas tem um problema ainda, qual???

Pensa na resposta, responde, que depois eu te falo...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não sei se peguei o ponto que você queria chegar mas olhando o código eu vejo como sendo errado fazer o type-hinting para classes concretas pois estaríamos engessando o modelo a trabalhar com uma implementação de um Subject ao invés de um tipo Subject.

 

Fora, na falta de expressão mais bonita, o crescimento vertical das heranças aumentando o acoplamento entre as classes.

 

Enfim... Eu faria diferente, apenas, num primeiro momento, claro, mudando a classe por uma interface:

 

interface SubjectWithData {

   public function getUpdatedData();
}

class SomeSubject implements SubjectWithData {

   public function getUpdatedData() {}
}

"Pode ser um", não significa "é um", logo, por definição, não cabe uma herança aí...

Eu pensei muito nisso e até cheguei esquematizar um modelo conde tanto Observer como Subject fossem interfaces permitindo que um método pudesse ser um assunto, um observador ou ambos.

 

Funcionou muito bem e o ponto de vista da OOP realmente é o mais adequado tanto que já até estou considerando tal direção para meu código.

 

Mas ainda assim, pelo exemplo original do tópico, haveria o conflito de nomes.

 

O real motivo de pensar primeiro no jeito errado e depois no jeito certo foi puro comodismo (leia-se preguiça) de implementar tais interfaces (e suas implementações) em cada uma das classes que precisasse do recurso.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.