OOP Klassendesign, ESP8266

Hallo,

Ich schreibe gerade an einem Tool für den ESP8266. Das Ding besteht in erst Linie aus mehreren Klassen wie zB:
FileHandler kümmert sich um die Dateien (SPIFFS),
WifiManager kümmert sich um das Wifi Gedöse,
Webserver kümmert sich um das Webinterface, usw.

Jede Klasse hat ein eigene Config (als "struct").
Und dann gibt es eine "Settings" Klasse die sich um diese Konfiguration kümmern soll, es gibt zwar default Werte für die jeweilige Config die zb.: beim einlesen einer JSON Datei überschrieben wird. Wird durch das Webinterface die Konfiguration geändert, wird diese auch automatisch als JSON Datei, gespeichert.
Soweit so gut!

Jetzt brauch ich quasi eine Pipeline von der Settings Klasse auf viele der anderen Klassen. Ich möchte das aber nicht über Getter und Setter im Hauptcode machen, das soll quasi im "Hintergrund" bzw. durch das Klassendesign funktionieren.
Sollte ich die Settings Klasse als abstrakte Basisklasse verwenden, gibt es einen besseren Weg? (Oder ist das bereits ein guter Weg?) <- Das würde auch Doppelvererbungen mit sich bringen, also 2 Basisklassen.

Was ich genau will?
Wie bekomm ich einen Pointer des Config Struct in die Settings Klasse ohne Getter/Setter, im Hauptcode werden nur die Klassen initalisiert. (Viele Klassen dürfen auch nur einmal initalisiert werden) was wäre hier ein gutes Design? Etwa eine "Oberklasse" die alle Klassen in sich vereint?

Ich hoffe ihr versteht was ich meine, würde mich sehr auf ein paar Denkanstöße (wie implementiert man eine Configuartion, OOP Design) freuen!

Grüße,
Donny

angenommen du hast für jede deine Klasse eine eigene "Einstellungen"-Struktur,
dann kannst du diese Struktur ja der eigentlichen Nutzer Klasse (FileHandler, WifiManaer, Webserver) übergeben
sowie alle 3 verschiedenen Strukturen deiner "Settings" Klasse.

Tipp: wenn du dir das erst mal in einen Diagramm aufzeichnest erkennst du imho besser die Abhängigkeiten / Interfaces zwischen den Klassen.

Du könntest die Configs im Konstruktor in eine (verkettete) Liste hängen.

Danke für die raschen Antwort!
Ich habe ein paar Diagrame bereits gezeichnet, ich lade mal ein abgespecktes Diagramm hoch.

@Whandall: Das ist eigentlich meine Frage, auf den Punkt gebracht! :wink: Mir fällt nur Vererbung ein, mit friendship funktioniert das leider auch nicht so elegant (wie ich es gerne hätte).
Also ich brauche die Config in 2 Klassen, deshalb muss es auch ein Pointer sein, zumindest in einer Klasse. Die Config soll auch nicht global sein (Gültigkeit).

Hier mal ein Design Anfang (mit Getter und Setter)

typedef struct Station
{
	String ssid;
	String password;
	
	byte mac[6];
	IPAddress ip;
	IPAddress subnet;
	IPAddress gateway;

} Station_t;

typedef struct WifiConfig
{
	Station_t ap;
	Station_t sta;

	String hostname;
	
	IPAddress dns1;
	IPAddress dns2;

	IPAddress ntp1;
	IPAddress ntp2;

	bool capativePortal;
	bool reconnect;

	byte maxConnectionsAttempts;
	unsigned int connectionTimeout;

	WiFiMode mode;
	
} WifiConfig_t;

class WifiManager
{
private:
	
	WifiConfig_t conf; // Wifi Config
	WiFiMode mode; // vom ESP Core

	// IST Wert
	bool run;	
	bool runDNS;
	bool runMDNS;

	bool autoConnect();
	bool connected();

public:
	WifiManager();
	//WifiManager(WifiConfig_t& conf); // hier hatte ich die Idee, Call by Referenz im Konstruktor aber eigentlich will ich genau das vermeiden
	WifiManager(WiFiMode_t mode_);
	~WifiManager();

	WiFiMode_t getMode() {
		return mode;
	}
	void setMode(WiFiMode_t mode_) {
		mode = mode_;
	}

	void begin(WifiConfig_t mode_);
	void begin(WifiConfig_t mode_, const String * ssid_, const String * hostname_, bool captivePortal_ = true);
	void end();

	void startCP(const String *ssid_, const String * hostname);
	void stopCP();

	//.....

};

Hier muss dann aber Sichergestellt werden, dass die Settings Klasse initziert wird und der Pointer richtig übergeben wird.
Wenn ich die Config in der Settings Klasse erstelle und nicht öffentlich halte, brauch ich natürlich wieder einen Setter oder über den Konstruktor. :thinking:

Was meint ihr? ich hab mir gerade erst die OOP Syntax von C++ angeeignet, ihr habt da sicher viel mehr Erfahrung.

struct braucht kein typedef, es ist identisch zu class,
lediglich die Standardsichtbarkeit ist bei struct public: statt private:.

@Whandall: Das struct und class im Grunde gleich sind war mir eigentlich schon bewusst, ich hab diese Schreibweise mit typedef sehr oft in Fremdcodes gelesen. Ist das C spezifisch oder woher kommt diese Schreibweiße eigentlich?

Ich glaube,
in C war struct WasAuchImmer kein richtiger Datentyp mit Namen WasAuchImmer,
man musste also immer struct WasAuchImmer schreiben.

Bei C++ ist das weggefallen.

C++ ist ja gerade die Vereinfachung des üblichen Kodes, der oft aus
structs und Funktionen, die als ersten Parameter einen Pointer auf eine struct hatten, bestand.

1 Like

Die ist historisch!
Es ist C, wird aber auch von C++ verstanden.

Soweit mir bekannt, ist typedef fast vollständig aus dem Rennen.
Findet man noch ab und an in der Std Libc++ in ein paar Ecken.
Aber auch da aus eher historische Gründen.

Zum eigentlichen:
Was du da vorhast, hat unter dem Begriff "Dependency injection Pattern" einiges an Bekanntheitsgrad gewonnen.
Vielleicht ist auch das "Erbauer-Entwurfsmuster" mal einen Seitenblick wert.

Grundsätzlich ist es gut, wenn Klassen nur das notwendigste von einander wissen.
Über Pointer, ist so ziemlich die risikoreichste Übergabemethode für Abhängigkeiten.

@Whandall @combie Danke! ich hätte wohl noch die nächsten Jahre mit typedef definiert.
Wird typedef eigentlich noch bei Aufzählungen (enum) verwendet oder nur noch enum Klassen?

Ich werde mit die Design Patterns einmal anschauen, vielen Dank!

Im Grunde will ich nur eine Klasse die, die jeweiligen Variablen aus den anderen Klassen in ein Config File schreibt und wieder ließt. So lässt sich meine Frage wohl am einfachsten formulieren.

Wie wäre es damit?

template <typename T>
class Settings
{
private:
	T * conf;
	
public:
	Settings(T &obj) { // setze Pointer
		conf = obj;
	}
	~Settings() {

	}
    // oder über Getter/Setter
	T getConf() {
		return conf;

	} 
	void setConfig(T &obj) {
		conf = obj;
	}

	void writeConfigFile(); // muss natürlich noch geschrieben werden
	void readConfigFile();


};

Dazu hab ich eigentlich auch eine Frage. Wann verwendet man "template < class T>" und wann "template < typename T>"?
edit: Der Abstand zwischen "<" und "typename" ist absichtlich drinnen da das sonst ausgeblendet wird.

"template < typename T>" ist universell, kann man also immer verwenden.
"template < class T>" ist identisch mit dem vorgenannten.

Aber dank des Schlüsselwortes class, das ist meine Meinung, sollte man es auch nur für Klassen Typen verwenden. (um Verwirrungen zu vermeiden)
Ich verwende class nie. Aber das ist eine Vorliebe. Keine Muss Bedingung.

Vielleicht suchst du ja auch:
C++ marshaling and unmarshalling

1 Like

Alles klar, Danke!

Die Wiki Beschreibung trifft es wohl sehr gut, vielen Dank! Werde erstmal mehr lesen. :slight_smile:

So, da ich mich bisher mit Design Patterns noch relativ wenig beschäftigt habe musste ich mich hier erstmal ein bissi einlesen. Diese Thema ist echt faszinierend!

Stimm ich Dir komplett zu!

Wem es interessiert, ich hab es jetzt etwas vereinfacht indem die Config in der "Settings" Klasse liegt und sich die jeweilige Klasse (WifiManager, FileHandler, usw.) sich über den Konstruktor den Pointer auf die jeweilige Config holt. Außerdem ist die Settings Klasse, "static" und der Konstruktor "private", Instanzen braucht diese Klasse eh nicht.

struct Station
{
	String ssid;
	String password;
	
	IPAddress ip;
};
struct WifiConfig
{
	Station ap;
	Station sta;

	String hostname;
    //....
};
class Settings
{
private:
	static WifiConfig wifiConf; 
	static WebConfig webConf; 
	Settings() { }
	~Settings() { }
	
public:
	static WifiConfig * getWifiConf() {
		return &wifiConf;
	}
	static WebConfig * getWebConf() {
		return &webConf;
	} 
	
	static void writeConfigFile(); // auf SPIFFS
	static void readConfigFile();
};

class WifiManager
{
private:
	WifiConfig * conf = nullptr; // Wifi Config (Pointer)

public:
	WifiManager()
	{
		conf = Settings::getWifiConf();
	}
    //...
};

Grüße!

1 Like

Ist es!

Danke für die Rückmeldung, und schön dass ich dir einen Ansatz liefern konnte.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.