Wzajemna zależność klas - rozwiązanie problemu

0

Być może to bardziej nadaje się do działu inżyniera oprogramowania, jako że na etapie projektowania takie rzeczy powinno się rozwiązać, ale że jest już troche kodu i jakaś implementacja to wrzuciłem to tutaj.

Chciałbym spytać się o rozwiązanie następującego problemu.

Piszę sobie obsługę takiego systemu gdzie mam jakieś urządzenia i komendy/polecania które mogą przychodzić z zewnątrz i danym urządzeniem sterować.
Mam zaimplementowany observer (jako szablon) który słucha komend przychodzących z sieci i wywołuje odpowiednia metodę (update()) w której mogę wykonać sobie odpowiednie instrukcje.

Klasa obserwer wyglada tak (pomijam implementację metod attach, notify i update, to działa poprawnie):

template <typename Msg>
class Observer {
    public:
        virtual ~Observer () {}
        virtual void update (Msg message) = 0;
};

Specyfikacja szablonu dla jakiegoś konkretnego typu wiadomości wygląda np. tak:

template<>
class Observer<Mode> {
    void update(Mode msgMode){
        ...
    }
}

Do obsługi przychodzących wiadomości po sieci mam też klasę ReceiverX1 (XX - identyfikator urządzenia), która używa obserwer aby dostawać tylko konkretny typ wiadomości (dodatkowo klasa ReceiverXX implementuje metody, które sprawdzają poprawność danych przychodzących). Tych receiverów jest kilka, w zależności od tego jakiego urządzenia jest to odbiornik, każdy z tych Receiverów dziedziczy po klasie abstrakcyjnej Receiver i implementuje swoją metodę receive().

//klasa abstrakcyjna & interfejs
class Receiver {
public:
    virtual void receive() = 0;
};

No i teraz taki przykładowy Receiver odbierający jakąś wiadomość (lub więcej wiadomości). Klasy CommunicationChannel nie będę wklejał, zajmuje się ona obsługa danych na poziomie soketów.

class ReceiverX1 {
public:
    void receive(){
        Observer<Mode> observerMode {};

        cc.attach(observerMode);            //cc - obiekt klasy CommunicationChannel

        while(true){
            cc.recv();
        }
    }
};

Mam też klasę która określa mi jakieś urządzenie (np. DeviceX1) i wykonuje na nim odpowiednie operacje, np., zaczyna nasłuchiwać przychodzących wiadomości, wykonuje jakieś akcje po otrzymaniu danej komendy.

class DeviceX1 {
public:

    void start(){
        thread recX1(&ReceiverX1::receive, receiverX1);
        recX1.detach();
        
        while(true){
            ...
        }
    }

    void changeMode(DeviceMode devMode){
        switch(devMode){
            case DeviceMode::ON:
                ...
                break;
            case DeviceMode::OFF:
                ...
                break;
        }
    };

    ...
private:
    DeviceMode mode;
    unique_ptr<Receiver> receiverX1;
}

No i teraz co ja chciałbym osiągnąć?

Chciałbym w metodzie update() w klasie Observer<...> mieć dostęp do instancji obiektu danego urządzenia, by np. w momencie otrzymania jakiejś wiadomości móc zareagować i wywołać metodę obsługującą dane zdarzenie. Jednocześnie chciałbym móc w klasie DeviceX1 uruchomic metodę receive() obiektu ReceiverX1. Jak to zrobić? Jest tutaj zależność klas DeviceX1 i ReceiverX1, których obiekty wzajemnie się wykorzystują.
ReceiverX1 potrzebuje referencję do obiektu DeviceX1 aby móc przekazać ją do konstruktora obiektu Observera danej wiadomości.
DeviceX1 potrzebuje obiektu ReceiverX1 aby dostać się do metody receive() i uruchomić odbieranie wiadomości gdy urządzenie będzie gotowe.

1

Cześć,

Nie jestem pewien czy zrozumiałem Twój problem w pełni (jeśli nie, napisz plz przykład kodu, który chcesz żeby działał, a nie działa).

W każdym razie, zależności z cyklami zazwyczaj rozwiązuje się korzystając z forward declarations, czyli napisania kompilatorowi, że kiedyś będzie taka i taka klasa, ale bez definiowania jej struktury. To działa świetnie z pointerami i referencjami, ale wymaganiem jest by przed przejściem do samej kompilacji metod, kompilator znał już strukturę wszystkich istotnych klas - to oznacza, że musisz mieć jasno wydzieloną definicję struktury klasy od jej implementacji.
Przykład:

#include <stdio.h>

// Fwd. declaration.
class MyClass2;

// Definicje struktury klas.
class MyClass1 {
 public:
  void asdf(MyClass2& x);  // Definicja metody asdf nie moze byc tutaj,
                           // poniewaz w tym momencie kompilator nie zna
                           // jeszcze struktury MyClass2 (tylko wie o jej
                           // istnieniu).

  // Metoda ret nie korzysta z klasy MyClass2, wiec moze byc zdefiniowana
  // tutaj.
  int ret() const { return 4; }
};

class MyClass2 {
 public:
  int ret(MyClass1& x) const;  // Tu juz moznaby zdefiniowac metode ret, ale
                               // dla spojnosci dalem ja nizej.
};

// Implementacje metod - obie klasy sa juz w pelni znane kompilatorowi.
void MyClass1::asdf(MyClass2& x) {
  printf("Result: %i\n", x.ret(*this));
}

int MyClass2::ret(MyClass1& x) const {
  return 6 + x.ret();
}

// Test.
int main() {
  MyClass1 x;
  MyClass2 y;
  x.asdf(y);

  return 0;
}
gynvael:haven-windows> g++ -Wall -Wextra test.cc
gynvael:haven-windows> a
Result: 10

Z tego co zrozumiałem Twój przykład, to nie korzystasz nigdzie w tych klasach wzajemnie zależnych z szablonów. Jeśli byś korzystał, to sprawa się trochę komplikuje i, w zależności od przypadku, trzeba by interfejs wspólny dla szablonów zdefiniować i z niego korzystać w powyższy sposób.

Jeśli coś źle zrozumiałem, daj znać plz (a coś mi mówi że tak jest - fwd. declaration jest zbyt proste w stosunku do tego co piszesz, że robisz).

1

@Mały Lew zależności nie muszą być przekazywane tylko przez konstruktor. W praktyce w Observerze przekazuje się je inaczej -> przez metodę "rejestracyjną". Bo Obserwatorów może być wielu! Więc obiekt który może być Obserwowany powinien mieć w sobie listę wskaźników do Obserwatorów. Obserwator musi zrobić na takim obiekcie .register(this) a potem ten obiekt za kazdym razem iteruje sobie po Obserwatorach i notyfikuje ich o zmianach.

1 użytkowników online, w tym zalogowanych: 0, gości: 1