Observer Pattern - Calling methods of objects stored in an array

I'm trying to implement a basic Observer pattern in for an Arduino project (UNO WiFi Rev2). For my project, I'd like to be able to provide several methods of sending commands (e.g. MQTT, REST API, timers, pushbuttons) to manipulate outputs (e.g. leds, lights, pumps, MQTT subscribers, etc.). I thought it would be slick to implement these command sources as Subjects, while the outputs would be the Observers. I'm still very early in developing and implementing this model. To be transparent, I'm kind of a Google, Copy/Paste, tweak to fit type of developer. So my knowledge of c++ pointers and all could be classified as weak.

This tester project implements a PushButton class as the Subject and a Device as the Observer. I'm not to the point of implementing hardware, so the "push()" of the PushButton is soft. The issue I'm having is with managing adding/removing Observers, then calling their methods. I'm having trouble calling the update() method on the Device (Observer) from the PushButton (Subject) notifyObservers() method.

When I try to compile, I get this error:

C:\Users\xxxxx\AppData\Local\Temp\ccxkQzRt.ltrans0.ltrans.o: In function `notifyObservers':

sketch/PushButton.cpp:20: undefined reference to `Observer::update(arduino::String)'

collect2.exe: error: ld returned 1 exit status

exit status 1
Error compiling for board Arduino Uno WiFi Rev2.

ObserverPattern.ino

#ifndef MY_OBSERVER_H
#define MY_OBSERVER_H

#include <Arduino.h>

class Observer {

  public:
    void update(String message);

};

#endif

#ifndef MY_SUBJECT_H
#define MY_SUBJECT_H

#include <Arduino.h>

class Subject {

  public:
    void registerObserver(Observer *observer);
    void removeObserver(Observer *observer);
    void notifyObservers();
};

#endif

#ifndef MY_PUSH_BUTTON_H
#define MY_PUSH_BUTTON_H

#include <Arduino.h>

class PushButton : public Subject {

  private:
    Observer observers[5];
    int observerIndex;
    byte id;

  public:
    PushButton(byte id);
    void registerObserver(Observer *observer);
    void removeObserver(Observer *observer);
    void notifyObservers();
    void push();
};

#endif

PushButton::PushButton(byte id) {
  this->id = id;
  observerIndex = 0;
}

void PushButton::registerObserver(Observer *observer) {
  observers[observerIndex] = *observer;
  observerIndex = observerIndex + 1;
  Serial.print("observerIndex = ");
  Serial.println(observerIndex);
}

void PushButton::removeObserver(Observer *observer) {

}

void PushButton::notifyObservers() {
  for (int i = 0; i < observerIndex; i++) {
        observers[i].update(String("hello")); //this is where it breaks
  }
}

void PushButton::push() {
  notifyObservers();
}

#ifndef MY_DEVICE_H
#define MY_DEVICE_H

#include <Arduino.h>

class Device : public Observer {

  private:
    byte id;

  public:
    Device(byte id);
    void update(String message);

};

#endif

Device::Device(byte id) {
  this->id = id;
}

void Device::update(String message) {
  Serial.print("Client ");
  Serial.print(id);
  Serial.print(" : ");
  Serial.print(message);
  Serial.println();
}

//ObserverPattern.ino
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Serial started");

  PushButton pushButton(2);
  Device one(1), two(2), three(3);

  // register Device one to listen for pushButton pushes
  pushButton.registerObserver(&one);

  pushButton.push();

  // register Device two to listen for pushButton pushes
  pushButton.registerObserver(&two);

  pushButton.push();

  // register Device three to listen for pushButton pushes
  pushButton.registerObserver(&three);

  pushButton.push();

  // just to prove update method works
  one.update("on");
  two.update("off");
}

void loop() {
  // put your main code here, to run repeatedly:

}

Device.cpp (228 Bytes)

Device.h (252 Bytes)

Observer.h (167 Bytes)

ObserverPattern.ino (784 Bytes)

PushButton.cpp (558 Bytes)

PushButton.h (442 Bytes)

Subject.h (274 Bytes)

First problem I noticed (there may be others) is that you need to declare update() as virtual in Observer.h. That will make it part of classes that inherit from it. Perhaps even purely virtual.

Also, it helps in early development to put all your class declarations and implementation in the same .ino file as setup() and loop(). That way you don't have to keep jumping between tabs while you're working out the basic mistakes like this. Much More Importantly, it makes it easier for anyone interested in helping you. All they have to do is copy one code window and paste it in the Arduino IDE rather than making a bunch of tabs (I won't expend that effort). You can break it in to separate .h and .cpp files later.

I revised the original post per your recommendations with the original code. (e.g. did not add virtual declaration). I did however, add a few more Serial prints and a call to the update() method from setup().

Here's your code with inheritance/ polymorphism properly implemented -- made the update() function virtual and changed observers[] to an array of pointers. Now it compiles (with warnings for unused function parameters). You'll have to determine if it does what you want.

#include <Arduino.h>

class Observer {

  public:
    virtual void update(String message);

};

class Subject {

  public:
    void registerObserver(Observer *observer);
    void removeObserver(Observer *observer);
    void notifyObservers();
};

class PushButton : public Subject {

  private:
    Observer *observers[5];
    int observerIndex;
    byte id;

  public:
    PushButton(byte id);
    void registerObserver(Observer *observer);
    void removeObserver(Observer *observer);
    void notifyObservers();
    void push();
};

PushButton::PushButton(byte id) {
  this->id = id;
  observerIndex = 0;
}

void PushButton::registerObserver(Observer *observer) {
  observers[observerIndex] = observer;
  observerIndex = observerIndex + 1;
  Serial.print("observerIndex = ");
  Serial.println(observerIndex);
}

void PushButton::removeObserver(Observer *observer) {

}

void PushButton::notifyObservers() {
  for (int i = 0; i < observerIndex; i++) {
    observers[i]->update("hello");
  }
}

void PushButton::push() {
  notifyObservers();
}

class Device : public Observer {

  private:
    byte id;

  public:
    Device(byte id);
    void update(String message);

};

Device::Device(byte id) {
  this->id = id;
}

void Device::update(String message) {
  Serial.print("Client ");
  Serial.print(id);
  Serial.print(" : ");
  Serial.print(message);
  Serial.println();
}

//ObserverPattern.ino
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Serial started");

  PushButton pushButton(2);
  Device one(1), two(2), three(3);

  // register Device one to listen for pushButton pushes
  pushButton.registerObserver(&one);

  pushButton.push();

  // register Device two to listen for pushButton pushes
  pushButton.registerObserver(&two);

  pushButton.push();

  // register Device three to listen for pushButton pushes
  pushButton.registerObserver(&three);

  pushButton.push();

  // just to prove update method works
  one.update("on");
  two.update("off");
}

void loop() {
  // put your main code here, to run repeatedly:

}

This works as intended. Although, I'm still unclear as to why the functions in the Subject class don't require this.

Thank you for pointing me in the right direction (pun intended).

tunneling:
This works as intended. Although, I'm still unclear as to why the functions in the Subject class don't require this.

Only functions that will be overridden in inheriting classes need to be declared virtual in the base class. No classes inherit from Subject.