6 Stimmen

Objektorientiert oder sequentiell?

Ich überarbeite einen 500 Zeilen langen C++-Code in main(), um eine Differentialgleichung zu lösen. Ich möchte die großen Ideen unseres Solvers in kleineren Funktionen kapseln (z.B. "SolvePotential(...)" anstelle von 50 Zeilen numerischem Code).

Sollte ich dies sequentiell mit einer Reihe von Funktionen codieren, die sehr lang Parameterlisten, wie zum Beispiel:

int main(int *argc, void **argv){
   interpolate(x,y,z, x_interp, y_interp, z_interp, potential, &newPotential);
   compute_flux(x,y,z, &flux)
   compute_energy(x,y,z, &eng)
   ...
   // 10 other high-level function calls with long parameter lists
   ...
   return 0;
}    

Oder soll ich eine "SolvePotential"-Klasse erstellen, die wie folgt aufgerufen wird?

int main(int *argc, void **argv){
   potential = SolvePotential(nx, ny, nz, nOrder);
   potential.solve();
   return 0;
}

Ich würde in SolvePotential Funktionen definieren, die Mitgliedsvariablen statt langer Parameterlisten verwenden, wie z.B.:

SolverPotential::solve(){
  SolvePotential::interpolate()
  SolverPotential::compute_flux()
  SolverPotential::compute_energy()
  // ... 
  //  10 other high-level function calls with NO parameter lists (just use private member variables)
}

In jedem Fall bezweifle ich, dass ich den Code wiederverwenden sehr viel ... wirklich, ich bin nur Refactoring, um mit Code Klarheit auf der Straße zu helfen.

Vielleicht ist das so, als würde man darüber streiten, ob es '12' oder 'ein Dutzend' heißt, aber was meinen Sie?

1 Stimmen

Es wurde allgemeineres C++ verwendet, so dass diese Frage von der gesamten C++-Gemeinschaft gesehen werden würde.

5voto

Joe Phillips Punkte 46741

Schreiben Sie es der Reihe nach und überarbeiten Sie es dann, wenn Sie etwas wiederverwenden können oder es klarer machen würden.

Außerdem macht eine Klasse SolvePotential nicht viel Sinn, da eine Klasse ein Objekt mit der Methode SolvePotential sein sollte.

3voto

bradheintz Punkte 3082

"SolvePotential" ist ein Verb, und Klassen sind in der Regel Substantive mit angehängten Verben. Ich weiß nicht viel über die Details Ihres Problems, aber dies könnte ein Zeichen dafür sein, dass ein prozeduraler Ansatz hier klarer wäre als OO. Auf jeden Fall sieht es so aus, als ob diese Klasse, wenn Sie sie erstellen würden, kaum mehr als eine Verpackung für die Funktionen wäre.

Es sei denn, ich hatte einen zweiten Ort, um die Klasse zu verwenden, würde ich nur die Funktionen mit expliziten Argumenten deklarieren - dies wird klarer sein (vor allem für eine neue Person, die diesen Code zum ersten Mal sieht) als mit Methoden auf eine Klasse, die versteckten Zustand erfordern.

3voto

jalf Punkte 235501

Weder noch. "Den gesamten Code aus einer einzigen Funktion in eine einzige Klasse zu verschieben, ist kein OOP. Eine der grundlegenden Regeln von OOP ist, dass eine Klasse Folgendes haben sollte ein einziger Zuständigkeitsbereich . Es handelt sich nicht um eine einzige Verantwortung, sondern um etwa 15:

SolverPotential::solve(){
SolvePotential::interpolate()
SolverPotential::compute_flux()
SolverPotential::compute_energy()
// ... 
//  10 other high-level function calls with NO parameter lists (just use private member variables)
}

Das macht es auch so gut wie unmöglich, eine Klasseninvariante beizubehalten, nicht wahr? Wann ist es gültig, compute_flux aufzurufen? Lösen? Interpolieren? Was soll mich davon abhalten, es in der falschen Reihenfolge zu tun? Wird sich die Klasse in einem gültigen Zustand befinden, wenn ich das tue? Bekomme ich gültige Daten aus ihr heraus?

Aber warum ist es ein Entweder-oder? Warum kann man nicht mehrere Klassen et Funktionen?

// This struct could be replaced with something like typedef boost::tuple<double,double,double> coord3d
struct coord3d {
double x, y, z;
};

coord3d interpolate(const coord3d& coord, const coord3d& interpolated, double potential); // Just return the potential, rather than using messy output parameters
double compute_flux(const coord3d coord&flux); // Return the flux instead of output params
double compute_energy(const coord3d& coord); // And return the energy directly as well

Natürlich müssen diese Funktionen nicht zwangsläufig Funktionen sein. Wenn nötig/zweckmäßig, könnte jede zu einer Klasse oder besser noch zu einem Funktor gemacht werden, um den notwendigen Zustand zu erhalten und vielleicht auch, um sie effizient als Argumente an andere Funktionen übergeben zu können.

Wenn optimale Leistung wichtig ist, müssen Sie möglicherweise mit der direkten Rückgabe größerer Strukturen vorsichtig sein, anstatt Ausgabeparameter zu verwenden, aber ich würde auf jeden Fall zuerst ein Profil erstellen, um zu sehen, ob es ein Problem ist, und selbst wenn es so ist, können Sie wahrscheinlich Ausgabeparameter mit Ausdrucksvorlagen vermeiden.

Wenn Sie ein konzeptionelles Objekt haben, auf dem eine Reihe unabhängiger Operationen ausgeführt werden können, ist das wahrscheinlich ein Hinweis darauf, dass Sie etwas OOP brauchen, dass es als Klasse mit einer Reihe von Mitgliedsfunktionen modelliert werden sollte, von denen jede natürlich die Invarianz der Klasse beibehält, egal wie, wann und warum sie aufgerufen werden.

Wenn Sie eine Reihe von Funktionen zusammenstellen und sie zu neuen, größeren Funktionen zusammenfügen müssen, sind funktionale Programmierung und Funktoren wahrscheinlich genau das Richtige für Sie. Ein häufiger Grund (aber sicher nicht der einzige) für den Wunsch nach zusammensetzbaren Funktionen ist, dass Sie dieselbe Operation mit vielen verschiedenen Datensätzen durchführen müssen (vielleicht sogar mit mehreren verschiedenen Typen, die alle dasselbe Konzept implementieren). Wenn ein Funktor die schwere Arbeit übernimmt, kann er mit std::transform oder std::for_each verwendet werden. Sie können auch Currying verwenden, um Ihre Funktionen schrittweise zusammenzustellen (vielleicht können einige der Funktionen mit einer Reihe von festen Parametern parametrisiert werden, die sich zwischen Aufrufen nicht ändern). Auch hier erstellen Sie einen Funktor, der mit diesen festen Parametern initialisiert wird, und liefern dann die variierenden Daten in operator().

Und schließlich, wenn Sie einfach eine Reihenfolge von Operationen auf einige veränderliche Daten, kann einfache alte prozedurale Programmierung das sein, was Ihren Bedürfnissen am besten entspricht.

Und schließlich sollten Sie generisch programmieren, indem Sie die erforderlichen Klassen und Funktionen in Schablonen einbauen, damit sie zusammenarbeiten können, ohne dass Sie sich mit Zeigerumleitungen oder Vererbung herumschlagen müssen.

Machen Sie sich nicht zu viele Gedanken über OOP. Nutzen Sie die Werkzeuge, die Ihnen zur Verfügung stehen.

Ich kenne den Kontext Ihrer Frage nicht gut genug, um das mit Sicherheit sagen zu können, aber mir scheint, dass Sie eigentlich keine Klasse brauchen, sondern nur eine Hierarchie von Funktionen. Ihr Benutzercode ruft solve() auf. solve() ruft intern, sagen wir mal (frei erfunden, um des Beispiels willen), interpolate() und compute_energy() auf. compute_energy() ruft intern compute_flux() auf, und so weiter. Jede Funktion macht nur ein paar Aufrufe, um die logischen Schritte auszuführen, die die Verantwortung der Funktion ausmachen. Sie haben also nirgendwo eine riesige Klasse mit einem Dutzend verschiedener Zuständigkeiten oder eine große monolithische Funktion, die alles sequentiell ausführt.

Auf jeden Fall ist gegen "sehr lange Parameterlisten" nichts einzuwenden (man kann sie in der Regel kürzen, indem man einige von ihnen zusammenfasst, aber selbst wenn das nicht möglich ist, ist es nicht "un-OOP", viele Parameter zu übergeben. Im Gegenteil, es bedeutet, dass die Funktion gut von allem anderen gekapselt ist. Alles, was sie braucht, wird in den Parametern übergeben, so dass sie nicht wirklich mit dem Rest der Anwendung verbunden ist.

2voto

Luis Punkte 859

Eigentlich ist C++ nicht nur eine OO-Sprache, sondern mischt auch andere Paradigmen, darunter das prozedurale. Die Möglichkeit, Klassen zu verwenden, macht sie nicht geeigneter für irgendein Problem.

Meiner Meinung nach machen Funktionen hier viel mehr Sinn, da Sie mathematische Verfahren die nicht auf einem Zustand beruhen und keine Daten wiederverwenden müssen. Die Verwendung von OO bedeutet hier, dass man Objekte konstruiert, nur um eine Methode aufzurufen und sie dann zu zerstören. Das klingt für mich fehleranfälliger und weniger intuitiv als eine prozedurale API. Außerdem, wie bradheintz sagt, beseitigt eine explizite Liste von Parametern auch das Problem, dass man sich daran erinnern muss, die Klasse zu initialisieren, bevor man sie tatsächlich benutzt (ein typischer Fehler beim Refactoring).

Übrigens, wenn man bei Funktionen Rückgabewerte anstelle von E/A-Parametern verwendet, sieht eine API in der Regel viel klarer aus.

Ich würde sogar wagen zu sagen, dass Sie OO und Prozeduren mischen sollten, indem Sie Klassen für Konzepte wie Vektoren (ich sehe hier einige x,y,z). Das würde auch einige Parameter zu entfernen, wenn das ist, was Sie so sehr betrifft.

float SolvePotential(const Vector3& vn, float nOrder)
{
    // ...
    const float newPotential = interpolate(vn, v_interp, potential);
    const float flux         = compute_flux(vn);
    const float energy       = compute_energy(vn);
    // ...
    return result;
}

Schließlich erwähnen Sie nicht die Leistung, also nehme ich an, dass es Sie nicht stört. Aber falls doch, scheint es in diesem Fall einfacher zu sein, es schneller zu machen und sauber mit einem prozeduralen Ansatz als mit OO.

Hoffentlich hilft das!

1voto

caseyboardman Punkte 779

Ich stimme für die Klasse, da sie die Daten in einem viel ordentlicheren Paket verpackt und Ihre main()-Funktion ziemlich klar macht.

In gewissem Sinne haben Sie die Funktion main() bereinigt und haben nun eine unordentliche Klasse, die Sie nach Belieben weiter bereinigen können. Eine Art "Teile und herrsche"-Methode. Oder vielleicht eine "Stopf den ganzen Müll auf den Dachboden"-Methode, bei der zumindest der am meisten genutzte Teil des Hauses sauber ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X