Padrão de observador; sabendo * o que * mudou?

10

Eu criei duas classes abstratas Subject e Observer que definem uma interface padrão clássica do Observer. Derivei deles para implementar o padrão Observer. Um observador pode ficar assim:

void MyClass::Update(Subject *subject)
{
    if(subject == myService_)
    {
        DoSomething();
    }
    else if(subject == myOtherService_)
    {
        DoSomethingElse();
    }
}

Isso é bom e me diz quem mudou alguma coisa. No entanto, isso não me diz o que mudou. Às vezes, está tudo bem, porque vou consultar o assunto para obter os dados mais recentes, mas outras vezes preciso saber o que exatamente mudou no assunto. Percebo em Java que eles têm um método notifyObservers () e um método notifyObservers (Object arg) para presumivelmente especificar detalhes sobre o que foi alterado.

No meu caso, preciso saber se uma das duas ações diferentes ocorreu sobre o assunto e, se for uma ação específica, saber um número inteiro relacionado a essa ação.

Então, minhas perguntas são:

  1. qual é a maneira C ++ de passar um argumento genérico (como Java faz)?
  2. O Observer é o melhor padrão? Talvez algum tipo de sistema de eventos?

ATUALIZAR

Eu encontrei este artigo que fala sobre a modelagem do padrão Observer: Implementando um padrão Subject / Observer com modelos . Isso me fez pensar se você poderia modelar um argumento.

Eu encontrei essa pergunta de estouro de pilha que fala sobre a modelagem do argumento: Padrão de Observer de assunto baseado em modelo - devo usar static_cast ou dynamic_cast . No entanto, o OP parece ter um problema que ninguém respondeu.

A outra coisa que eu poderia fazer é alterar o método Update para obter um objeto EventArg como em:

void MyClass::Update(Subject *subject, EventArg arg)
{
  ...

E, em seguida, crie subclasses do EventArg para dados de argumentos específicos e, em seguida, suponho que seja convertido de volta para a subclasse específica dentro do método de atualização.

ATUALIZAÇÃO 2

Também foi encontrado um artigo, Ao criar uma estrutura c ++ assíncrona baseada em mensagens; parte 2, que discute como o Assunto comunica detalhes sobre o que mudou.

Agora estou pensando seriamente em usar o Boost.Signals . Usar meu próprio padrão de observador fazia sentido quando era simples, mas modelar o tipo e o argumento está começando a ficar complicado. E talvez eu precise da segurança do thread do Boost.Signals2.

ATUALIZAÇÃO 3

Também encontrei alguns artigos interessantes sobre o padrão de observadores:

Generalizing Observer por Herb Sutter

Implementando o Padrão Observador em C ++ - Parte 1

Experiências de implementação do padrão de design do observador (parte 2)

Experiências de implementação do padrão de design do observador (parte 3)

No entanto, mudei minha implementação para usar o Boost.Signals que, embora possivelmente esteja um pouco inchado para os meus propósitos, está funcionando com êxito. E provavelmente quaisquer preocupações de inchaço ou velocidade são irrelevantes.

Do utilizador
fonte
As perguntas no corpo não parecem realmente corresponder ao seu título. Qual é realmente o cerne da questão?
22411 Nicole
@ Renesis: Atualmente, estou usando o padrão Observer como no exemplo de código no início do meu post. Para o código em que estou trabalhando, é necessário saber especificamente o que mudou para que eu possa reagir de acordo. Minha implementação atual do padrão observador (que é o padrão) não fornece essas informações. Minha pergunta é como obter melhor informações sobre o que mudou.
Usuário
@Renesis: Aqui está uma discussão no fórum de fazer uma pergunta semelhante à minha: gamedev.net/topic/497105-observer-pattern
Utilizador
Anúncio update2: definitivamente aceito o uso do Boost.Signals. É muito mais conveniente do que fazer o seu próprio. Também existe o libsigc ++ se você quiser algo mais leve apenas para esta tarefa (o Boost.Signals usa muito código do restante do Boost); embora não seja seguro para threads.
Jan Hudec
Em relação a problemas de velocidade: eu não sei especificamente como speedy Boost.Signals é, mas isso é apenas uma preocupação quando você tem uma enorme quantidade de eventos voando ao redor ...
Max

Respostas:

4

Seja C ++ ou JAVA, uma Notificação para o observador pode vir junto com as informações do que foi alterado. Os mesmos métodos notifyObservers (Object arg) também podem ser usados ​​em C ++.

Geralmente, a questão permanece é que pode haver vários assuntos enviados para um ou vários observadores e, portanto, class argnão podem ser codificados.

Normalmente, a melhor maneira de fazer é criar arg na forma de mensagens / tokens genéricos que formam o mesmo tipo de dados para várias classes, mas os valores diferem para diferentes classes observadas. Como alternativa, se todos esses valores de notificação forem derivados da classe em alguma classe baseada que é comum a todos.

Para o padrão do observador, é importante que o tipo de dados Arg não seja codificado entre o observador e o observador - caso contrário, é um acoplamento que dificulta a evolução das coisas.

EDITAR
Se você deseja que esse observador não apenas observe, mas também precise executar muitas outras tarefas com base no que foi alterado , verifique também o padrão Visitor . No padrão de visitante, o observador / visitante chama o objeto modificado e, portanto, pode não apenas saber qual é a modificação, mas pode realmente trabalhar nele

Dipan Mehta
fonte
5
Se o observador interpreta o argumento, não é um acoplamento, não importa como você esconder o tipo. Na verdade, eu diria que é mais difícil de evoluir se você passar Object( void *, boost::anyou algo semelhante genérico) do que se você passar tipo específico, porque com o tipo específico que você verá em tempo de compilação que algo mudou, enquanto que com o tipo genérico que irá compilar e parar de funcionar, porque o observador não poderá trabalhar com os dados reais passados.
Jan Hudec
@JanHudec: Eu concordo com isso, mas isso significa que você cria uma subclasse específica Observer / Subject definida para cada argumento (ou seja, para cada caso de uso)?
Utilizador
@ JanHudec: também o acoplamento é apenas uma maneira. O assunto não tem idéia sobre os observadores. Sim, o observador sabe sobre o assunto, mas não é assim que o padrão de observador funciona?
Utilizador
11
@ Usuário: Sim, eu faço uma interface específica para cada assunto e cada observador implementa as interfaces dos assuntos que ele precisa observar. Bem, todas as linguagens que utilizo possuem ponteiros de método vinculado na linguagem ou estrutura (delegados em C #, C ++ 11 std::functionBoost boost::function, Gtk + GClosure, métodos vinculados python etc.), então apenas defino métodos com assinaturas apropriadas e solicito ao sistema que crie o real observador. O acoplamento é de fato apenas uma maneira, o assunto define a interface para os observadores, mas não tem idéia sobre sua implementação.
Jan Hudec
0

Existem algumas maneiras de enviar um argumento de evento genérico "Java Like" em C ++.

1) Declare o argumento do evento como nulo * e faça a conversão para a classe correta no manipulador de eventos.

2) Declare o argumento do evento como um ponteiro / ref para uma nova classe / interface como (em resposta ao seu exemplo)

class GenericEventArgument
{
  virtual bool didAction1Happen() = 0;
  virtual int getActionInteger() = 0;
};

E as classes de argumentos de eventos reais derivam dessa classe.

Quanto à sua pergunta sobre um observador baseado em modelo, confira

http://www.codeproject.com/Articles/3267/Implementing-a-Subject-Observer-pattern-with-templ

Vou eu
fonte
-1

Não sei se esse é o nome canônico, mas, desde os meus velhos tempos em Smalltalk, lembro-me do termo "aspecto" para identificar o que mudou no observável.

Não é tão complexo quanto a sua idéia de EventArg (e subclassificá-lo); apenas passa uma string (pode até ser uma constante inteira) do observável para o observador.

Além disso: existem apenas dois métodos simples ( update(observable)eupdateAspect(observable, aspect)

Menos: O observador pode ter que pedir mais informações ao observável (ou seja, seu "número inteiro")

virtualnobi
fonte