Große Klasse, Redesign
-
Hallo!
Eine Klasse 'Image' besitzt Bilddaten und Methoden zur Manipulation dieser
Bilddaten. operator+=(Image& otherPic) ist die Hauptmethode, die otherPic
und *this zuerst vorbereitet und dann addiert. Soweit ist das Design der
Klasse sauber:class Image { public: void operator+=(Image& otherPic); private: unsigned xResolution; unsigned yResolution; vector<Pixel> vPixels; void redEyes(); void enhanceContrast(); void denoise(); void scale(); void clip(); void overlay(vector<Pixels>& vPixels); }; bla.. Image::operator+=(Image& otherPic) { this->scale(); this->clip(); otherPic.scale(); otherPic.clip() otherPic.redEyes(); otherPic.enhanceContrast(); otherPic.denoise(); this->overlay(otherPic.vPixels); }Das Problem: Bei diesem Design steht beinahe der ganze Code der Software in
der (bereits riesigen) Klasse 'Image', und das wirkt so, als würde man nicht
objektorientiert programmieren, sondern hätte globale Variablen, auf die jede
beliebige Funktion zugreifen kann. -> Unübersichtlich, schwer zu debuggen. Da
noch viele weitere Funktionen zur Manipulation der Daten zu implementieren
sind, ist ein anderes Design erforderlich.Ich bin mit keiner meiner Ideen zum Redesign so recht glücklich, daher würde
ich mir gerne Eure Vorschläge ansehen, wie man die Klasse aufteilen oder die
Funktionen zur Datenmanipulation auslagern kann.(Anm: Es geht tatsächlich nicht um Bilddaten, sondern um 3D-Objekte, wo noch
mehr private Daten in der Klasse sind und manipuliert werden, aber als
Metapher für die Diskussion ist die obige Image-Klasse besser geeignet.)Danke, lg
-
Ich würde die Bildbearbeitungsfunktionen von der Image-Klasse trennen. Ganz grob skizziert vielleicht:
class Image { unsigned xResolution; unsigned yResolution; vector<Pixel> pixels; public: // Konstruktor, getter, setter, ... }; class ImageRenderer { public: void render(const Image& image) { // ... } }; class ImageFilter { public: virtual void apply(Image& img) = 0; virtual ~ImageFilter() {} }; class RedEyeFilter { public: virtual void apply(Image& img) { // Hier rote Augen entfernen } }; class ImageTransformer { public: virtual Image transform(const Image& imgA, const Image& imgB) = 0; virtual ~ImageTransformer() {} }; class Overlayer : ImageTransformer { public: virtual Image transform(const Image& imgA, const Image& imgB) { // Bilder übereinander legen und neues Bild zurückgeben } };Ja, so in der Art würd ichs jetzt spontan machen.
-
Interessant, dass das Thema in letzer Zeit des öfteren nachgefragt wird.
Z.B gleich hier:
http://www.c-plusplus.net/forum/viewtopic-var-t-is-273691.htmloder hier:
http://www.c-plusplus.net/forum/viewtopic-var-t-is-273619-and-highlight-is-.htmlDie Threads laufen mehr oder weniger auf das, was OhneName auch sagt heraus.
-
Das was du dort gemacht hat nennt sich God-class oder God-object http://de.wikipedia.org/wiki/God_object und wie schon hier erklärt hilft da nur feiner zu granulieren.
Gruß Blue-Tec
-
Die Operatorüberladung
void operator+=(Image& otherPic);ist auch äusserst fragwürdig. Am besten fährst du, wenn du einige Konventionen beachtest:
- Benutze Operatoren nur, wenn sie intuitiv sind. Ich würde hier eine benannte Funktion vorziehen, ein Plus kann in dem Zusammenhang vieles bedeuten.
- Beachte Const-Correctness. Ein
+=verändert den rechten Operand nie. Dass du es doch tust, ist ein umso grösseres Zeichen dafür, dass eine benannte Methode besser geeignet wäre. - Gebe etwas zurück. Die übliche Konvention bei
operator+=ist, eine Referenz aufthiszurückzugeben, um Verkettungen zu ermöglichen. - Wenn du
a += b;schreiben kannst, muss der Ausdrucka = a + b;ebenfalls gültig und semantisch äquivalent mit dem ersten sein.
Ich würde hier auf Operatorüberladung verzichten. Siehe auch Überladung von Operatoren in C++.
-
Schau dir an wie VTK das macht (oder nimm das gleich, denn es hat sicher 90% der Funktionalität die du brauchst).
(Bezugsdiagramm hier: http://www.vtk.org/doc/nightly/html/classvtkAlgorithm.html)Alles sauber von einer Basisklasse abgeleitet (vtkObject, bzw. hier eher vtkAlgorithm). Das Bild zeigt natürlich nur die erste Ebene der Ableitungen.
-
antialias schrieb:
Alles sauber von einer Basisklasse abgeleitet...
"Sauber" ist immer relativ. Ableitungen mögen manche Probleme beheben, bergen aber auch sehr viel Neue. Nichts gegen flache Vererbungshirachien, die hier scheint aber eine tiefe Hierarchie zu sein.
Gerade nichts aussagende Klassennamen wie TObject, CObject, vtkObject... halte ich häufig für ein Anzeichen schlechtes Designs.
-
antialias schrieb:
Schau dir an wie VTK das macht (oder nimm das gleich, denn es hat sicher 90% der Funktionalität die du brauchst).
(Bezugsdiagramm hier: http://www.vtk.org/doc/nightly/html/classvtkAlgorithm.html)Alles sauber von einer Basisklasse abgeleitet (vtkObject, bzw. hier eher vtkAlgorithm). Das Bild zeigt natürlich nur die erste Ebene der Ableitungen.
Das sieht aus wie etwas, das ich brauchen kann. So wie ich das verstehe,
werden hier Unix-Pipes nachgebildet. Die Klasse vtkAlgorithm ist ein
allgemeines Rohr. Die Algorithmen, die von vtkAlgorithm ableiten, werden
dadurch ebenfalls Rohre mit kompatiblen Anschlußstücken. Diese Anschluß-
stücke heißen inputPortInformation und outputPortInformation, womit die
Ein- und Ausgangsdaten der Algorithmen gemeint sind.Leider ist das Ding RIESIG, und ich finde mich in der Doku kaum zurecht.
So weiß ich noch nicht, wo die Abfolge festgelegt wird, d.h. wo die Rohre
zusammengeschaltet werden. Ein kleineres Projekt nach diesem Paradigma wäre
günstig. Aber ich lese mich weiter ein. Danke für den Tipp, die Idee macht
mir Fieber in den Fingern.lg