Crysis nerd
Freizeitschrauber(in)
Diamond-Vererbung schlechter Programmierstil? Lösung für Interface-Problem
Guten Tag ihr Lieben,
ich bin schon öfters auf ein Problem gestoßen, welches im Zusammenhang mit Interfaces auftritt. Mit Interface meine ich eine Klasse, die pure virtual ist (also alle Funktionen müssen überschrieben werden) und die sozusagen nur der Leitfaden für andere Klassen ist. Das wird, wenn ich das richtig verstanden habe, oft bei Spiele-Engines verwendet, um dem Programmierer vom Spiel die Funktionen bereitzustellen aber ohne den Funktionsrumpf (den Engine-Source) zu offenbaren.
Wer schon durch die Überschrift weiß worauf das hinausläuft, braucht meine Erklärung der Situation nicht lesen.
Mal ein Beispiel dazu:
Bei einem UI-System habe ich die Klasse "IButton" (I -> Interface), die rein virtuell ist. Außerdem habe ich die Klasse "CButton", welche von IButton erbt.
Soweit so klar. Nun habe ich aber mehrere UI Elemente, die auch alle ein paar gleiche Funktionen haben, wie zb. die Measure Funktion, die später die Position der Elemente anhand von Margin, Alignment und ähnlichen Werten errechnen soll.
Also erweitern wir um die Klasse IUIElement und lassen IButton davon erben:
Aber jetzt habe ich ein Problem:
Wo implementier ich den Funktionsrumpf von Measure() ?
Das könnte man jetzt in IUIElement direkt machen, aber dadurch wäre das Interface verletzt, weil im Interface keine Methoden definiert werden.
Zweite Möglichkeit ist, in jedem UI-Element die Funktion einzubauen, hier zb. in CButton. Würde sich mit dem Interface vertragen ABER man müsste auch in CDropDownBox und CSlider und und und die selbe Funktion definieren (viel doppelter Code, angenommen die Funktion ist überall gleich).
Die Lösung ist Diamond Vererbung: Ich baue eine Klasse CUIElement, die von IUIElement erbt. Und CButton erbt von CUIElement.
Dann hätten wir das:
TL;DR HIER WEITERLESEN
Und die Welt ist wieder korrekt. Fast.
Ich lese hin und wieder, dass diese Diamond Vererbung Nachteile mit sich bringt. Zb. werden vom Compiler und während der Laufzeit spezielle Zeiger-Listen oder so angefertigt, um dann auf die richtige Klasse zu zeigen. Das merkt man beim Debuggen, dass man Werte von Visual Studio (in meinem Fall) nicht angezeigt werden können, sondern nur so eine komische Zeigerliste gezeigt wird. Außerdem höre ich, dass es schlechter stil ist und so weiter.
Kennt ihr euch damit aus und könnte ihr mir sagen, ob man das wirklich Vermeiden sollte?
Oder könnte ihr mir andere Lösungvorschläge sagen, die für ein solches Problem angebracht wären?
Oder seid ihr, wie mein Kollege sebi707, der Meinung, dass man Interfaces schon mal für Funktionen nutzen darf?
Hoffe ich konnte es einigermaßen erklären
Liebe Grüße,
Crysis nerd
Guten Tag ihr Lieben,
ich bin schon öfters auf ein Problem gestoßen, welches im Zusammenhang mit Interfaces auftritt. Mit Interface meine ich eine Klasse, die pure virtual ist (also alle Funktionen müssen überschrieben werden) und die sozusagen nur der Leitfaden für andere Klassen ist. Das wird, wenn ich das richtig verstanden habe, oft bei Spiele-Engines verwendet, um dem Programmierer vom Spiel die Funktionen bereitzustellen aber ohne den Funktionsrumpf (den Engine-Source) zu offenbaren.
Wer schon durch die Überschrift weiß worauf das hinausläuft, braucht meine Erklärung der Situation nicht lesen.
Mal ein Beispiel dazu:
Bei einem UI-System habe ich die Klasse "IButton" (I -> Interface), die rein virtuell ist. Außerdem habe ich die Klasse "CButton", welche von IButton erbt.
Code:
class IButton
{
public:
virtual void SetLabel(std::string text) = 0;
}
class CButton : public IButton
{
public:
void SetLabel(std::string text) { /*do something*/ }
private:
std::string m_Label;
}
Soweit so klar. Nun habe ich aber mehrere UI Elemente, die auch alle ein paar gleiche Funktionen haben, wie zb. die Measure Funktion, die später die Position der Elemente anhand von Margin, Alignment und ähnlichen Werten errechnen soll.
Also erweitern wir um die Klasse IUIElement und lassen IButton davon erben:
Code:
class IUIElement
{
public:
virtual void Measure() = 0;
}
class IButton : public IUIElement
{
public:
virtual void SetLabel(std::string text) = 0;
}
class CButton : public IButton
{
public:
void SetLabel(std::string text) { /*do something*/ }
private:
std::string m_Label;
}
Aber jetzt habe ich ein Problem:
Wo implementier ich den Funktionsrumpf von Measure() ?
Das könnte man jetzt in IUIElement direkt machen, aber dadurch wäre das Interface verletzt, weil im Interface keine Methoden definiert werden.
Zweite Möglichkeit ist, in jedem UI-Element die Funktion einzubauen, hier zb. in CButton. Würde sich mit dem Interface vertragen ABER man müsste auch in CDropDownBox und CSlider und und und die selbe Funktion definieren (viel doppelter Code, angenommen die Funktion ist überall gleich).
Die Lösung ist Diamond Vererbung: Ich baue eine Klasse CUIElement, die von IUIElement erbt. Und CButton erbt von CUIElement.
Dann hätten wir das:
Code:
class IUIElement
{
public:
virtual void Measure() = 0;
}
class IButton : virtual public IUIElement
{
public:
virtual void SetLabel(std::string text) = 0;
}
class CUIElement : virtual public IUIElement
{
public:
void Measure() { /* do something*/ }
}
class CButton : public IButton, public CUIElement
{
public:
void SetLabel(std::string text) { /*do something*/ }
private:
std::string m_Label;
}
TL;DR HIER WEITERLESEN
Und die Welt ist wieder korrekt. Fast.
Ich lese hin und wieder, dass diese Diamond Vererbung Nachteile mit sich bringt. Zb. werden vom Compiler und während der Laufzeit spezielle Zeiger-Listen oder so angefertigt, um dann auf die richtige Klasse zu zeigen. Das merkt man beim Debuggen, dass man Werte von Visual Studio (in meinem Fall) nicht angezeigt werden können, sondern nur so eine komische Zeigerliste gezeigt wird. Außerdem höre ich, dass es schlechter stil ist und so weiter.
Kennt ihr euch damit aus und könnte ihr mir sagen, ob man das wirklich Vermeiden sollte?
Oder könnte ihr mir andere Lösungvorschläge sagen, die für ein solches Problem angebracht wären?
Oder seid ihr, wie mein Kollege sebi707, der Meinung, dass man Interfaces schon mal für Funktionen nutzen darf?
Hoffe ich konnte es einigermaßen erklären

Liebe Grüße,
Crysis nerd
Zuletzt bearbeitet:


