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!
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.
@Whandall: Das ist eigentlich meine Frage, auf den Punkt gebracht! 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.
Was meint ihr? ich hab mir gerade erst die OOP Syntax von C++ angeeignet, ihr habt da sicher viel mehr Erfahrung.
@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.
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
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.