2017-02-13 1 views
3

Lassen Sie mich Ihnen das Problem sagen, das ich habe. Ich entwerfe eine Reihe von Klassen, um ein digitales Gerät zu steuern. Dieses Gerät kann in zwei Betriebsmodi arbeiten. In dem ersten Modus kann er einen spezifischen Satz von Operationen ausführen, und in dem zweiten Modus kann er einen weiteren Satz von Operationen durchführen (mit möglicherweise einigen gemeinsamen Operationen zwischen den beiden). Ich kann auch den Modus des Geräts im Lauf ändern, damit ich bei Bedarf zwischen den beiden Modi wechseln kann. Unabhängig vom Modus verwendet das Gerät den gleichen Satz von Registern.C++ mehrfache Vererbung von Basisklassen mit Mitgliedern mit demselben Namen

Ich dachte in Lösung dieses Problem mit einer Basisklasse für jeden Modus, so kann ich Objekte von Modus 1 haben, wenn ich die erste Reihe von Operationen und Objekte von Modus 2 brauche, wenn ich die zweite Reihe von Operationen benötigen. Dann könnte ich eine Klasse aus diesen beiden Basisklassen ableiten, so dass ich Objekte haben kann, die alle Operationen ausführen.

Das Problem mit meinem Design ist, dass die beiden Basisklassen einige gemeinsame Funktionen und Verweise auf die gleichen Register haben. Da ich die Vererbung von Mitgliedern nicht verhindern kann, würde ich in der abgeleiteten Klasse Duplikate haben. Ich weiß, ich kann wählen, welches Duplikat mit dem Scope-Operator zugreifen soll, aber ich denke immer noch, dass dies ein schlechtes Design ist.

Meine Frage ist also: Gibt es eine idiomatische Lösung dieses Problems?

Wenn es keine richtige oder einfache Möglichkeit gibt, dies zu lösen, denke ich über Design 3 hierarchisch unabhängig Klassen. Ich hätte einen doppelten Code, aber das ist kein großes Problem, oder?

Code (vereinfacht) zur Erläuterung:

class mode1 
{ 
protected: 
    volatile uint8_t& reg1; 
    volatile uint8_t& reg2; 
    uint8_t data; 
public: 
    virtual void operation1() final { // do something } 
    virtual void operation2() final { // do something } 
    virtual void operation3() final { // do something } 
}; 


class mode2 
{ 
protected: 
    volatile uint8_t& reg1; 
    volatile uint8_t& reg2; 
    uint8_t data; 
public: 
    virtual void operation4() final { // do something } 
    virtual void operation2() final { // do something } 
    virtual void operation5() final { // do something } 
}; 


class mode1and2 : public mode1, public mode2 
{ 
public: 
    void operation6() { // do something } 
    void operation7() { // do something } 
}; 

Note-Modi 1 und 2 haben operation2 und alle Datenelemente gemeinsam haben.

+0

Warum möchten Sie ein Objekt, das alle Operationen ausführt? – immibis

+0

Was hast du probiert? – Raindrop7

+0

Werfen Sie einen Blick auf virtuelle Vererbung. –

Antwort

1

würde ich die gemeinsamen Teile von mode1 und mode2 in einer gemeinsamen Basisklasse setzen, lassen Sie uns Common sagen, umfassend dann Ihre Daten und Elementfunktion operation2. Zusammen mit der virtuellen Vererbung können Sie dann zwei Sichten auf dieselben Daten haben, sogar zur gleichen Zeit, wenn dies erforderlich ist.

class common { 
    friend class mode1; 
    friend class mode2; 
protected: 
    volatile uint8_t& reg1; 
    volatile uint8_t& reg2; 
    uint8_t data; 

public: 
    virtual void operation2() final { // do something 
    }; 

}; 

class mode1 : public virtual common 
{ 
public: 
    virtual void operation1() final { // do something 
    }; 
    virtual void operation3() final { // do something } 
    }; 
}; 

class mode2 : public virtual common 
{ 
public: 
    virtual void operation4() final { // do something 
    } 
    virtual void operation5() final { // do something 
    } 
}; 


class mode1and2 : public mode1, public mode2 
{ 
public: 
    void operation6() { // do something } 
    }; 
    void operation7() { // do something } 
    }; 
}; 
+0

Ich lese jetzt über virtuelle Vererbung. Sieht so aus, als ob ich genau danach suche. Danke, Stephan. – rrd

1

Das staatliche Designmuster sieht wie ein guter Kandidat für Ihren Fall aus.
Als minimal, Arbeitsbeispiel:

#include<memory> 
#include<iostream> 

struct Behavior { 
    virtual void f() = 0; 
    virtual void g() = 0; 
}; 

struct NullBehavior: Behavior { 
    void f() override {} 
    void g() override {} 
}; 

struct Mode1: Behavior { 
    void f() override { std::cout << "mode 1 - f" << std::endl; } 
    void g() override { std::cout << "mode 1 - g" << std::endl; } 
}; 

struct Mode2: Behavior { 
    void f() override { std::cout << "mode 2 - f" << std::endl; } 
    void g() override { std::cout << "mode 2 - g" << std::endl; } 
}; 

struct Device { 
    template<typename B> 
    void set() { behavior = std::unique_ptr<Behavior>{new B}; } 

    void f() { behavior->f(); } 
    void g() { behavior->g(); } 

private: 
    std::unique_ptr<Behavior> behavior{new NullBehavior}; 
}; 

int main() { 
    Device device; 
    device.f(); 
    device.g(); 

    device.set<Mode1>(); 
    device.f(); 
    device.g(); 

    device.set<Mode2>(); 
    device.f(); 
    device.g(); 
} 

Aus der Sicht des Benutzers des Gerätes, ist es egal, was ist der Modus, den Sie verwenden. Wie auch immer Sie es wünschen, Sie können es jederzeit dynamisch ändern und Ihr Gerät wird ab diesem Zeitpunkt mit dem neuen Modus arbeiten.
Das Vorziehen der Komposition über die Vererbung löst das Problem aufgrund der widersprüchlichen Namen. Das Delegieren von allem von der äußeren Klasse in den inneren Zustand erledigt den Rest.

Beachten Sie, dass Sie, wenn Sie Methoden zwischen Zuständen freigeben möchten, nichts daran hindert, sie in die Basisklasse zu setzen.

Eine etwas andere Version können Sie Daten auch zwischen den Zweien teilen

struct Data { 
    volatile uint8_t& reg1; 
    volatile uint8_t& reg2; 
    uint8_t data; 
}; 

struct Behavior { 
    virtual void f(Data &) = 0; 
    virtual void g(Data &) = 0; 
}; 

struct NullBehavior: Behavior { 
    void f(Data &) override {} 
    void g(Data &) override {} 
}; 

struct Mode1: Behavior { 
    void f(Data &) override { /* ... */ } 
    void g(Data &) override { /* ... */ } 
}; 

struct Mode2: Behavior { 
    void f(Data &) override { /* ... */ } 
    void g(Data &) override { /* ... */ } 
}; 

struct Device { 
    template<typename B> 
    void set() { behavior = std::unique_ptr<Behavior>{new B}; } 

    void f() { behavior->f(data); } 
    void g() { behavior->g(data); } 

private: 
    Data data{}; 
    std::unique_ptr<Behavior> behavior{new NullBehavior}; 
}; 

Alle diese Parameter, die für einen bestimmten Modus einzigartig sind, können Teil der Klassendefinition oder setzen innerhalb Data und ignoriert werden, wenn Sie in einem anderen Modus arbeiten.

Verwandte Themen