Frage

Ich bin kürzlich darüber gestolpert Dieser Eintrag im Google-Testblog über Richtlinien zum Schreiben von besser testbarem Code.Bis hierhin war ich mit dem Autor einer Meinung:

Bevorzugen Sie Polymorphismus gegenüber Bedingungen:Wenn Sie eine switch-Anweisung sehen, sollten Sie an Polymorphismen denken.Wenn sich die gleiche Bedingung an vielen Stellen in Ihrer Klasse wiederholt, sollten Sie erneut über Polymorphismus nachdenken.Durch Polymorphismus wird Ihre komplexe Klasse in mehrere kleinere, einfachere Klassen aufgeteilt, die klar definieren, welche Teile des Codes zusammenhängen und gemeinsam ausgeführt werden.Dies erleichtert das Testen, da einfachere/kleinere Klassen einfacher zu testen sind.

Ich kann mir das einfach nicht vorstellen.Ich kann die Verwendung von Polymorphismus anstelle von RTTI (oder gegebenenfalls DIY-RTTI) verstehen, aber das scheint eine so weit gefasste Aussage zu sein, dass ich mir nicht vorstellen kann, dass sie tatsächlich effektiv im Produktionscode verwendet wird.Mir scheint es vielmehr einfacher zu sein, zusätzliche Testfälle für Methoden hinzuzufügen, die über Switch-Anweisungen verfügen, als den Code in Dutzende separate Klassen aufzuteilen.

Außerdem hatte ich den Eindruck, dass Polymorphismus zu allen möglichen anderen subtilen Fehlern und Designproblemen führen kann, daher bin ich gespannt, ob sich der Kompromiss hier lohnt.Kann mir jemand genau erklären, was mit dieser Prüfrichtlinie gemeint ist?

War es hilfreich?

Lösung

Tatsächlich erleichtert dies das Schreiben von Tests und Code.

Wenn Sie eine Switch -Anweisung basierend auf einem internen Feld haben, haben Sie wahrscheinlich denselben Schalter an mehreren Stellen, die etwas andere Dinge tun. Dies verursacht Probleme, wenn Sie einen neuen Fall hinzufügen, da Sie alle Switch -Anweisungen aktualisieren müssen (falls Sie ihn finden können).

Durch die Verwendung von Polymorphismus können Sie virtuelle Funktionen verwenden, um die gleiche Funktionalität zu erhalten. Da ein neuer Fall eine neue Klasse ist, müssen Sie Ihren Code nicht nach Dingen suchen, die überprüft werden müssen, alles für jede Klasse isoliert ist.

class Animal
{
    public:
       Noise warningNoise();
       Noise pleasureNoise();
    private:
       AnimalType type;
};

Noise Animal::warningNoise()
{
    switch(type)
    {
        case Cat: return Hiss;
        case Dog: return Bark;
    }
}
Noise Animal::pleasureNoise()
{
    switch(type)
    {
        case Cat: return Purr;
        case Dog: return Bark;
    }
}

In diesem einfachen Fall erfordert jedes neue Tier, dass beide Switch -Anweisungen aktualisiert werden.
Vergessen Sie einen? Was ist der Standard? KNALL!!

Unter Verwendung von Polymorphismus

class Animal
{
    public:
       virtual Noise warningNoise() = 0;
       virtual Noise pleasureNoise() = 0;
};

class Cat: public Animal
{
   // Compiler forces you to define both method.
   // Otherwise you can't have a Cat object

   // All code local to the cat belongs to the cat.

};

Durch die Verwendung von Polymorphismus können Sie die Tierklasse testen.
Testen Sie dann jeden der abgeleiteten Klassen separat.

Außerdem können Sie die Tierklasse versenden (Zur Änderung geschlossen) als Teil von Ihrer binären Bibliothek. Aber die Menschen können immer noch neue Tiere hinzufügen (Für die Erweiterung geöffnet) durch Ableiten neuer Klassen aus dem Tierkopf. Wenn all diese Funktionen in der Tierklasse erfasst wurden, müssen alle Tiere vor dem Versand definiert werden (geschlossen/geschlossen).

Andere Tipps

Fürchte dich nicht...

Ich denke, Ihr Problem liegt in Vertrautheit, nicht in der Technologie. Machen Sie sich mit C ++ OOP vertraut.

C ++ ist eine OOP -Sprache

Unter den mehreren Paradigmen verfügt es über OOP -Funktionen und ist mehr als in der Lage, den Vergleich mit der meisten reinen OO -Sprache zu unterstützen.

Lassen Sie sich nicht von dem "C -Teil in C ++" glauben lassen, dass C ++ nicht mit anderen Paradigmen umgehen kann. C ++ kann mit vielen Programmierparadigmen ziemlich gnädig umgehen. Und unter ihnen ist OOP C ++ das reife C ++ -Paradigmen nach einem prozeduralen Paradigma (dh dem oben genannten "C -Teil").

Polymorphismus ist für die Produktion in Ordnung

Es gibt keine "subtilen Fehler" oder "nicht für den Produktionscode geeignet". Es gibt Entwickler, die auf ihre Weise festgelegt bleiben, und Entwickler, die lernen, wie man Tools verwendet und die besten Tools für jede Aufgabe verwendet.

Schalter und Polymorphismus sind [fast] ähnlich ...

... aber Polymorphismus entfernte die meisten Fehler.

Der Unterschied besteht darin, dass Sie die Schalter manuell bewältigen müssen, während der Polymorphismus natürlicher ist, sobald Sie mit Überschreibung der Vererbungsmethode verwendet werden.

Mit Switches müssen Sie eine Typvariable mit verschiedenen Typen vergleichen und die Unterschiede verarbeiten. Mit Polymorphismus weiß die Variable selbst, wie sie sich verhalten kann. Sie müssen die Variablen nur logisch organisieren und die richtigen Methoden überschreiben.

Wenn Sie jedoch vergessen, einen Fall im Switch zu verarbeiten, wird es Ihnen der Compiler nicht sagen, wohingegen Sie Ihnen mitgeteilt werden, wenn Sie aus einer Klasse abgeben, ohne seine reinen virtuellen Methoden zu überschreiben. Somit werden die meisten Schalterverfahren vermieden.

Alles in allem geht es bei den beiden Funktionen darum, Entscheidungen zu treffen. Polymorphismus ermöglicht es Ihnen jedoch, komplexer und gleichzeitig natürlicher und damit einfacher zu wählen.

Vermeiden Sie es, RTTI zu verwenden, um den Typ eines Objekts zu finden

RTTI ist ein interessantes Konzept und kann nützlich sein. Aber die meiste Zeit (dh zu 95% der Fälle) wird die Übergabe und Vererbung in der Methode mehr als genug sein, und der größte Teil Ihres Codes sollte nicht einmal die genaue Art des Objekts kennen, sondern darauf vertrauen, dass es das Richtige tut.

Wenn Sie RTTI als verherrlichter Schalter verwenden, fehlen Ihnen der Punkt.

(Haftungsausschluss: Ich bin ein großer Fan des RTTI -Konzepts und der Dynamic_casts. Aber man muss das richtige Werkzeug für die jeweilige Aufgabe verwenden, und die meiste Zeit wird RTTI als verherrlichter Schalter verwendet, der falsch ist)

Vergleichen Sie dynamisch mit statischer Polymorphismus

Wenn Ihr Code den genauen Typ eines Objekts zur Kompilierungszeit nicht kennt, verwenden Sie den dynamischen Polymorphismus (dh klassischer Vererbung, Überschreiben von virtuellen Methoden usw.).

Wenn Ihr Code den Typ zur Kompilierungszeit kennt, können Sie möglicherweise den statischen Polymorphismus verwenden, dh das CRTP -Muster http://en.wikipedia.org/wiki/curiously_recurring_template_pattern

Der CRTP ermöglicht es Ihnen, Code zu haben, der nach dynamischem Polymorphismus riecht, deren jeder Methode -Aufruf statisch aufgelöst wird, was für einen sehr kritischen Code ideal ist.

Beispiel für Produktionscode

Bei der Produktion wird ein ähnlicher Code (aus dem Speicher) verwendet.

Die leichtere Lösung drehte sich um ein von der Nachrichtenschleife genannter Verfahren (ein WinProc in Win32, aber ich habe eine einfachere Version für den Einfachheit halber geschrieben). Fassen Sie also zusammen, es war so etwas wie:

void MyProcedure(int p_iCommand, void *p_vParam)
{
   // A LOT OF CODE ???
   // each case has a lot of code, with both similarities
   // and differences, and of course, casting p_vParam
   // into something, depending on hoping no one
   // did a mistake, associating the wrong command with
   // the wrong data type in p_vParam

   switch(p_iCommand)
   {
      case COMMAND_AAA: { /* A LOT OF CODE (see above) */ } break ;
      case COMMAND_BBB: { /* A LOT OF CODE (see above) */ } break ;
      // etc.
      case COMMAND_XXX: { /* A LOT OF CODE (see above) */ } break ;
      case COMMAND_ZZZ: { /* A LOT OF CODE (see above) */ } break ;
      default: { /* call default procedure */} break ;
   }
}

Jede Zugabe des Befehls fügte einen Fall hinzu.

Das Problem ist, dass einige Befehle ähnlich sind und teilweise ihre Implementierung geteilt haben.

Das Mischen der Fälle war also ein Risiko für die Evolution.

Ich habe das Problem mit dem Befehlsmuster, dh erstellt, ein Basisbefehlsobjekt mit einer process () -Methode verwendet.

Deshalb habe ich das Nachrichtenverfahren neu geschrieben, den gefährlichen Code (dh das Spielen mit Leere *usw.) auf ein Minimum minimiert, und schrieb ihn, um sicherzugehen, dass ich ihn nie wieder anfassen müsste:

void MyProcedure(int p_iCommand, void *p_vParam)
{
   switch(p_iCommand)
   {
      // Only one case. Isn't it cool?
      case COMMAND:
         {
           Command * c = static_cast<Command *>(p_vParam) ;
           c->process() ;
         }
         break ;
      default: { /* call default procedure */} break ;
   }
}

Und dann für jeden möglichen Befehl, anstatt Code in die Prozedur hinzuzufügen und den Code aus ähnlichen Befehlen zu mischen (oder schlimmer noch, kopieren/einfügen), habe ich einen neuen Befehl erstellt und entweder vom Befehlsobjekt oder eines von einem abgeleitet seine abgeleiteten Objekte:

Dies führte zur Hierarchie (dargestellt als Baum):

[+] Command
 |
 +--[+] CommandServer
 |   |
 |   +--[+] CommandServerInitialize
 |   |
 |   +--[+] CommandServerInsert
 |   |
 |   +--[+] CommandServerUpdate
 |   |
 |   +--[+] CommandServerDelete
 |
 +--[+] CommandAction
 |   |
 |   +--[+] CommandActionStart
 |   |
 |   +--[+] CommandActionPause
 |   |
 |   +--[+] CommandActionEnd
 |
 +--[+] CommandMessage

Jetzt musste ich nur noch den Prozess für jedes Objekt außer Kraft setzen.

Einfach und leicht zu verlängern.

Angenommen, die Kommandaktion sollte ihren Prozess in drei Phasen durchführen: "Before", "the" und "After". Sein Code wäre so etwas wie:

class CommandAction : public Command
{
   // etc.
   virtual void process() // overriding Command::process pure virtual method
   {
      this->processBefore() ;
      this->processWhile() ;
      this->processAfter() ;
   }

   virtual void processBefore() = 0 ; // To be overriden

   virtual void processWhile()
   {
      // Do something common for all CommandAction objects
   }

   virtual void processAfter()  = 0 ; // To be overriden

} ;

Und zum Beispiel könnte der BefehlsActionStart als:

class CommandActionStart : public CommandAction
{
   // etc.
   virtual void processBefore()
   {
      // Do something common for all CommandActionStart objects
   }

   virtual void processAfter()
   {
      // Do something common for all CommandActionStart objects
   }
} ;

Wie gesagt: leicht zu verstehen (falls richtig kommentiert) und sehr leicht zu erweitern.

Der Schalter wird auf sein minimales (dh wenn auch wenn auch wir auch an die Windows-Standardprozedur delegieren) und keine RTTI (oder schlechter, intern, rtti) delegieren.

Der gleiche Code in einem Switch wäre ziemlich amüsant, denke ich (wenn auch nur die Menge an "historischen" Code, die ich in unserer App bei der Arbeit gesehen habe).

Unit -Testen eines OO -Programms bedeutet, jede Klasse als Einheit zu testen. Ein Prinzip, das Sie lernen möchten, ist "offen für Erweiterung, geschlossen für Änderungen". Ich habe das vom Kopf zuerst Designmuster bekommen. Grundsätzlich heißt es im Grunde, dass Sie Ihren Code leicht erweitern möchten, ohne vorhandenen getesteten Code zu ändern.

Der Polymorphismus ermöglicht dies, indem sie diese bedingten Aussagen beseitigen. Betrachten Sie dieses Beispiel:

Angenommen, Sie haben ein Charakterobjekt, das eine Waffe trägt. Sie können eine solche Angriffsmethode schreiben:

If (weapon is a rifle) then //Code to attack with rifle else
If (weapon is a plasma gun) //Then code to attack with plasma gun

usw.

Mit Polymorphismus muss der Charakter die Art der Waffe einfach nicht "kennen", einfach einfach

weapon.attack()

würde funktionieren. Was passiert, wenn eine neue Waffe erfunden wurde? Ohne Polymorphismus müssen Sie Ihre bedingte Aussage ändern. Mit Polymorphismus müssen Sie eine neue Klasse hinzufügen und die getestete Charakterklasse in Ruhe lassen.

Ich bin ein bisschen skeptisch: Ich glaube, die Erstellung fügt oft mehr Komplexität hinzu, als sie entfernt.

Ich denke, Sie stellen jedoch eine gute Frage, und eine Sache, die ich denke, ist Folgendes:

Trennst du dich in mehrere Klassen, weil du es mit verschiedenen zu tun hast Dinge? Oder ist es wirklich dasselbe, in einem anderen zu handeln Weg?

Wenn es wirklich neu ist Typ, Dann erstellen Sie eine neue Klasse. Aber wenn es nur eine Option ist, behalte ich sie im Allgemeinen in derselben Klasse.

Ich glaube, die Standardlösung ist die einklasse, und die Verantwortung ist, dass der Programmierer die Vererbung vorschlägt, ihren Fall zu beweisen.

Kein Experte für die Auswirkungen auf Testfälle, sondern aus Sicht der Softwareentwicklung:

  • Offener Prinzip - Klassen sollten für Veränderungen geschlossen werden, aber für Erweiterung offen. Wenn Sie bedingte Operationen über ein bedingter Konstrukt verwalten, muss sich Ihre Klasse ändern, wenn eine neue Bedingung hinzugefügt wird. Wenn Sie Polymorphismus verwenden, muss sich die Basisklasse nicht ändern.

  • Wiederhole dich nicht - Ein wichtiger Teil der Richtlinie ist das "gleich wenn Neu kommt, Sie müssen nur einen Code ändern.

Polymorphismus ist einer der Eckpfeiler von OO und sicherlich sehr nützlich.Durch die Aufteilung der Bedenken auf mehrere Klassen erstellen Sie isolierte und testbare Einheiten.Anstatt also einen Switch durchzuführen, bei dem Sie Methoden für verschiedene Typen oder Implementierungen aufrufen, erstellen Sie eine einheitliche Schnittstelle mit mehreren Implementierungen.Wenn Sie eine Implementierung hinzufügen müssen, müssen Sie die Clients nicht ändern, wie es bei switch...case der Fall ist.Sehr wichtig, da dies hilft, Rückschritte zu vermeiden.

Sie können Ihren Client-Algorithmus auch vereinfachen, indem Sie nur einen Typ verwenden:die Schnittstelle.

Für mich ist es sehr wichtig, dass Polymorphismus am besten mit einem reinen Schnittstellen-/Implementierungsmuster (wie dem ehrwürdigen Shape <- Circle usw.) verwendet wird.).Sie können Polymorphismus auch in konkreten Klassen mit Template-Methoden (auch bekannt als Hooks) haben, aber seine Wirksamkeit nimmt mit zunehmender Komplexität ab.

Polymorphismus ist die Grundlage, auf der die Codebasis unseres Unternehmens aufbaut, daher halte ich ihn für sehr praktisch.

Schalter und Polymorphismus tun dasselbe.

Im Polymorphismus (und in klassenbasierter Programmierung im Allgemeinen) gruppieren Sie die Funktionen nach ihrem Typ. Bei Verwendung von Switches gruppieren Sie die Typen nach Funktion. Entscheiden Sie, welche Ansicht gut für Sie ist.

Wenn also Ihre Schnittstelle behoben ist und Sie nur neue Typen hinzufügen, ist Polymorphismus Ihr Freund. Wenn Sie jedoch Ihre Benutzeroberfläche neue Funktionen hinzufügen, müssen Sie alle Implementierungen aktualisieren.

In bestimmten Fällen haben Sie möglicherweise eine feste Menge an Typen, und es können neue Funktionen kommen, dann sind Schalter besser. Wenn Sie jedoch neue Typen hinzufügen, aktualisieren Sie jeden Switch.

Mit Switches duplizieren Sie Sub-Typ-Listen. Mit Polymorphismus duplizierst du Betriebslisten. Sie haben ein Problem gehandelt, um ein anderes zu bekommen. Dies ist der sogenannte Ausdrucksproblem, was nicht durch ein Programmierparadigma gelöst wird, das ich kenne. Die Wurzel des Problems ist die eindimensionale Natur des Textes, mit dem der Code dargestellt wird.

Da hier pro-Polymorphismus-Punkte gut diskutiert werden, lassen Sie mich einen Pro-Switch-Punkt anbieten.

OOP hat Designmuster, um häufige Fallstricke zu vermeiden. Die prozedurale Programmierung hat auch Designmuster (aber noch niemand hat es noch abgeschrieben. Ein Entwurfsmuster könnte sein Fügen Sie immer einen Standardfall bei.

Schalter können richtig gemacht werden:

switch (type)
{
    case T_FOO: doFoo(); break;
    case T_BAR: doBar(); break;
    default:
        fprintf(stderr, "You, who are reading this, add a new case for %d to the FooBar function ASAP!\n", type);
        assert(0);
}

Dieser Code verweist Ihren bevorzugten Debugger auf den Ort, an dem Sie vergessen haben, einen Fall zu behandeln. Ein Compiler kann Sie dazu zwingen, Ihre Schnittstelle zu implementieren, aber diese Kräfte Sie können Ihren Code gründlich testen (zumindest wird festgestellt, dass der neue Fall festgestellt wird).

Wenn ein bestimmter Schalter mehr als ein Stellen verwendet würde, wird er natürlich in eine Funktion ausgeschnitten (Wiederhole dich nicht).

Wenn Sie diese Switches erweitern möchten, machen Sie einfach a grep 'case[ ]*T_BAR' rn . (unter Linux) und es wird die Sehenswürdigkeiten ausspucken. Da Sie sich den Code ansehen müssen, sehen Sie einen Kontext, mit dem Sie den neuen Fall korrekt hinzufügen können. Wenn Sie Polymorphismus verwenden, sind die Anrufstellen im System versteckt, und Sie sind auf die Richtigkeit der Dokumentation angewiesen, wenn sie überhaupt existiert.

Das Erweiterungsschalter brechen nicht auch die OCP, da Sie die vorhandenen Fälle nicht ändern, sondern nur einen neuen Fall hinzufügen.

Switches helfen auch dem nächsten Mann, der versucht, den Code zu gewöhnen und zu verstehen:

  • Die möglichen Fälle liegen vor Ihren Augen. Das ist eine gute Sache beim Lesen von Code (weniger herumspringen).
  • Virtuelle Methodenaufrufe sind jedoch genau wie normale Methodenaufrufe. Man kann nie wissen, ob ein Anruf virtuell oder normal ist (ohne die Klasse nach oben zu suchen). Das ist schlecht.
  • Wenn der Anruf jedoch virtuell ist, sind mögliche Fälle nicht offensichtlich (ohne alle abgeleiteten Klassen zu finden). Das ist auch schlecht.

Wenn Sie eine Schnittstelle zu einer Drittanlage bereitstellen, können sie einem System Verhaltens- und Benutzerdaten hinzufügen, dann ist dies eine andere Sache. (Sie können Rückrufe und Zeiger auf Benutzerdaten setzen, und Sie geben ihnen Griffe)

Weitere Debatten finden Sie hier: http://c2.com/cgi/wiki?switchStatementsSmell

Ich fürchte, mein "C-Hacker-Syndrom" und mein Anti-Oopismus werden hier irgendwann meinen ganzen Ruf verbrennen. Aber wann immer ich etwas brauchte oder in ein prozedurales C -System einschlagen musste, fand ich es ziemlich einfach, das Fehlen von Einschränkungen, die Zwangsverkapselung und weniger Abstraktionsschichten machen mich "einfach". In einem C ++/C#/Java -System, in dem zehn Abstraktionsschichten im Leben der Software aufeinander gestapelt sind In ihr System eingebaut, um andere zu vermeiden, "mit ihrer Klasse durcheinander zu bringen".

Dies ist hauptsächlich mit der Kapselung von Wissen zu tun. Beginnen wir mit einem wirklich offensichtlichen Beispiel - toString (). Dies ist Java, überträgt aber leicht auf C ++. Angenommen, Sie möchten eine menschliche freundliche Version eines Objekts für Debugging -Zwecke drucken. Du könntest es tun:

switch(obj.type): {
case 1: cout << "Type 1" << obj.foo <<...; break;   
case 2: cout << "Type 2" << ...

Dies wäre jedoch eindeutig albern. Warum sollte eine Methode irgendwo wissen, wie man alles druckt? Es wird oft besser für das Objekt selbst sein, sich selbst zu drucken, z. B.:

cout << object.toString();

Auf diese Weise kann das ToString () auf Mitgliedsfelder zugreifen, ohne Abgüsse zu benötigen. Sie können unabhängig getestet werden. Sie können leicht geändert werden.

Sie könnten jedoch argumentieren, dass ein Objekt, wie ein Objekt nicht mit einem Objekt zugeordnet werden sollte, mit der Druckmethode zugeordnet werden sollte. In diesem Fall ist ein weiteres Entwurfsmuster hilfreich, nämlich das Besuchermuster, das zum Fälschen von Doppelversand verwendet wird. Es ist zu lang zu lang für diese Antwort, aber Sie können es können Lesen Sie hier eine gute Beschreibung.

Wenn Sie Switch -Anweisungen überall verwenden, wo Sie die Möglichkeit begegnen, dass Sie beim Upgrade einen Ort verpassen, benötigt das ein Update.

Es funktioniert sehr gut Wenn Sie es verstehen.

Es gibt auch 2 Aromen des Polymorphismus. Das erste ist sehr leicht zu verstehen in Java-ähnlich:

interface A{

   int foo();

}

final class B implements A{

   int foo(){ print("B"); }

}

final class C implements A{

   int foo(){ print("C"); }

}

B und C teilen eine gemeinsame Schnittstelle. B und C in diesem Fall können nicht erweitert werden, daher sind Sie immer sicher, welche Foo () Sie anrufen. Gleiches gilt für C ++, machen Sie einfach ein :: foo pure virtual.

Zweitens und schwieriger ist der Laufzeitpolymorphismus. In Pseudo-Code sieht es nicht schlecht aus.

class A{

   int foo(){print("A");}

}

class B extends A{

   int foo(){print("B");}

}

class C extends B{

  int foo(){print("C");}

}

...

class Z extends Y{

   int foo(){print("Z");

}

main(){

   F* f = new Z();
   A* a = f;
   a->foo();
   f->foo();

}

Aber es ist viel schwieriger. Vor allem, wenn Sie in C ++ arbeiten, wo einige der FOO -Deklarationen virtuell sein können, und einige der Vererbung möglicherweise virtuell sein. Auch die Antwort darauf:

A* a  = new Z;
A  a2 = *a;
a->foo();
a2.foo();

Könnte nicht das sein, was Sie erwarten.

Machen Sie sich einfach sehr bewusst, was Sie tun, und wissen Sie nicht, ob Sie Run-Time-Polymorphismus verwenden. Seien Sie nicht übermütig, und wenn Sie nicht sicher sind, was zur Laufzeit etwas tun wird, dann testen Sie es.

Ich muss wiederholen, dass das Finden aller Switch-Statements nicht triviale Prozesse in einer ausgereiften Codebasis sein können. Wenn Sie einen verpassen, wird die Anwendung aufgrund einer unvergleichlichen Anweisung wahrscheinlich zum Absturz gebracht, es sei denn, Sie haben einen Standardsatz.

Schauen Sie sich auch "Martin Fowlers" -Buch über "Refactoring" an
Die Verwendung eines Schalters anstelle von Polymorphismus ist ein Codegeruch.

Es hängt wirklich von Ihrem Programmstil ab. Dies kann zwar in Java oder C#korrekt sein, aber ich stimme nicht zu, dass die automatische Entscheidung, Polymorphismus zu verwenden, korrekt ist. Sie können Ihren Code in viele kleine Funktionen aufteilen und beispielsweise eine Array -Lookup mit Funktionszeigern (zum Kompilierzeit initialisiert) ausführen. In C ++ werden Polymorphismus und Klassen oft überbeansprucht - wahrscheinlich der größte Designfehler von Menschen, die aus starken OOP -Sprachen in C ++ stammen, ist, dass alles in eine Klasse geht - dies ist nicht wahr. Eine Klasse sollte nur die minimalen Dinge enthalten, die sie als Ganzes funktionieren lassen. Wenn eine Unterklasse oder ein Freund notwendig ist, sei es so, aber sie sollten nicht die Norm sein. Alle anderen Operationen in der Klasse sollten kostenlose Funktionen im gleichen Namespace sein. ADL ermöglicht es, diese Funktionen ohne Suche zu verwenden.

C ++ ist keine OOP -Sprache, mach es nicht. Es ist so schlimm wie das Programmieren von C in C ++.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top