Forum: PC-Programmierung Funktionsaufteilung in Objektorientierung


von F. (Gast)


Lesenswert?

Moin,

wo kommt in der objektorientierten Programmierung zum Beispiel die 
Zeichenfunktion hin. In die Graphikausgabe oder in die Objekte?

Nehmen wir als Beispiel eine 2D-Verkehrssimulation an.
Da gibt es eine GUI, mit Steuerelementen und einer Zeichenfläche für die 
Simulation, und die Simulationsobjekte, zum Beispiel Fußgänger als 
Kreise dargestellt, Fahrräder als Balken dargestellt und Autos als 
Rechtecke dargestellt.

Macht man dann eher in die GUI eine Zeichenfunktion für alles
(Pseudocode)
1
class GUI_canvas
2
{
3
  void paint()
4
  {
5
    for(i:Objekt)
6
    {
7
      case(Objekt.class)
8
      {
9
        Fußgänger:
10
          drawCircle();
11
        Fahrrad:
12
          drawLine();
13
        Auto:
14
          drawRectangle();
15
      }
16
    }
17
  }
18
}
oder bekommt jedes Objekt seine eigene Zeichenfunktion, die beim 
Neuzeichnen für jedes Objekt aufgerufen wird?
1
class paintable
2
{
3
  void paint();
4
}
5
6
class Fußgänger : paintable
7
{
8
  void paint()
9
  {
10
    drawCircle();
11
  }
12
}
13
14
class Fahrrad : paintable
15
{
16
  void paint()
17
  {
18
    drawLine();
19
  }
20
}
21
22
class Auto : paintable
23
{
24
  void paint()
25
  {
26
    drawRectangle();
27
  }
28
}
Das sind natürlich nur vereinfachte Beispiele. Die Zeichnungen könnten 
natürlich deutlich komplizierter sein mit Fallunterscheidungen.

Einerseits würde ich Graphik und Logik trennen, also alles, was mit 
Zeichnen zu tun hat, in die Graphik/GUI packen, andererseits wird die 
dann sehr groß und vollgepackt mit Zeug, das eigentlich zum Objekt 
gehört. Das spräche dann eher für die zweite Variante, damit alles 
Objektspezifische beim Objekt bleibt.

von J. S. (jojos)


Lesenswert?

ich halte die zweite Variante für besser weil damit generisch gearbeitet 
werden kann. Wenn es ein neues Objekt gibt, muss der Canvas das kennen 
und damit muss an zwei Stellen geändert werden. Ein generisches for 
(I:Objekt) I.paint(); kann alles zeichnen ohne es zu kennen (zweite 
Variante).

Aka Polymorphie. Wikipedia bringt genau das auch als Beispiel.
https://de.wikipedia.org/wiki/Polymorphie_(Programmierung)

: Bearbeitet durch User
von micha (Gast)


Lesenswert?

Ich würde es auch der Logik/Funktion nach aufteilen

//----------------------
// canvas low-level
class CCanvas
  void Line
  void Circle
  void Rectangle

//----------------------
// high level graphic
class CShape
  virtual Paint(CCanvas & Canvas)

class ShapeAuto : CShape
  virtual Paint(CCanvas & Canvas)
     Canvas.Rectangle ...

class ShapeFahrrad : CShape
class ShapeFussgaenger : CShape

//----------------------
// simulation logic
class CSimulationObject
  CPaintable Shape;

class Auto : CSimulationObject
class Fahrrad : CSimulationObject
class Fussgaenger :: CSimulationObject

// main/draw
forall (SimOb : AllSimulationObjects)
  SimOb.Shape.Paint(Canvas)

von F. (Gast)


Lesenswert?

Erstmal ist mir ein kleiner Fehler im ersten Beispiel aufgefallen. Es 
müsste natürlich heißen:
1
for(i:Objekt)
2
{
3
  case(i.class)
4
  {
Ich nehme aber an, dass das auch vorher jeder verstanden hätte.

J. S. schrieb:
> Ein generisches for
> (I:Objekt) I.paint(); kann alles zeichnen ohne es zu kennen (zweite
> Variante).
Das würde ich auch so sehen.
J. S. schrieb:
> Wenn es ein neues Objekt gibt, muss der Canvas das kennen
> und damit muss an zwei Stellen geändert werden.
Meine Überlegung ist, dass man das Argument auch umdrehen könnte. Wenn 
man die Graphik ändern will, zum Beispiel von 2D auf 3D, muss man das in 
jedem Objekt einzeln machen. Wenn alles in der GUI ist, muss man das nur 
einmal machen.

Danke für die Einschätzung.

von F. (Gast)


Lesenswert?

micha schrieb:
> Ich würde es auch der Logik/Funktion nach aufteilen
Also in dem Beispiel drei Klassen? Die High-Level-Klasse übernimmt dann 
den 'graphischen Aufbau'/das Zusammensetzen aller Objekte aus den 
einfachen Zeichenobjekten?

von J. S. (jojos)


Lesenswert?

Das drawXY kann ja auch wieder die Methode eines Canvas sein als 
canvas.drawXY(). Den canvas gibt man den Objekt mit damit ganz die 
Ausgabe variieren. Also um zB auf verschiedenen Displays zu zeichnen.

von Noch ein Kommentar (Gast)


Lesenswert?

Die zentrale Grundidee an der Objektorientierung: Du kannst später eine 
Klasse EBike hinzufügen, die ihre eigene paint() Methode hat. Musst die 
bestehenden Klassen nie wieder ändern.

Die Frage ist, bleibt es bei Fußgänger, Fahrrad und Auto? Oder trudeln 
da immer wieder Erweiterungswünsche ein?

Eigentlich brauchst du Erfahrung, was für Anforderungen später 
hinzukommen werden. Damit du von vorn herein die erforderlicher 
abstrakten Methoden in den Basisklassen anlegen kannst. Ohne 
Überflüssige Methoden und Basisklassen, die nur Verwirrung stiften.

In diesem Fall solltest du einfach mal verschiedene GUI Framewoks 
ausprobieren. Da stecken 40 Jahre Erfahrung drin. Das kann man nicht 
mehr selbst erfinden.

von Experte (Gast)


Lesenswert?

F. schrieb:
> wo kommt in der objektorientierten Programmierung zum Beispiel die
> Zeichenfunktion hin. In die Graphikausgabe oder in die Objekte?

Das ist ganz einfach: Beide Varianten sind richtig.

Ja, das ist kein Witz, sondern Deine Frage/Beispiel ist ein Klassiker in 
der Frage des OO-Entwurfs.

Man kann wirklich beides machen. Wichtig ist so wissen, dass die beiden 
Varianten unterschiedliche Konsequenzen haben:

a.) Ist die Zeichen-Funktionalität separat (Dein erstes Beispiel), dann
    sind die Simulations-Objekte unabhängig von der GUI. Du kannst dann
    die Simulation mit unterschiedlichen GUIs oder auch ohne verwenden.
    Trotzdem kannst Du in der Zeichen-Funktion ein "catch-all" für
    neue, unbekannte Klassen basteln, die z.B. einfach einen Punkt
    oder etwas ähnliches malen.

b.) Ist die Zeichen-Funktion teil der Simulationobjekte, kannst Du
    mitunter beim Zeichnen auf interne Daten der Objekte zugreifen.
    Allerdings sind dann die Objekte fest mit der GUI verbunden.
    Das ist eine relativ steife Angelegenheit hat aber den Vorteil,
    das Änderungen an an der Simulation einfacher sind, weil alles
    was ein Objekt betrifft in einer Klasse erledigt wird.

Aus Erfahrung würde ich Variante A wählen, auch wenn es gegen die vor 
noch 25 Jahren gelehrte "Prinzipien" der OO "verstößt".

Es ist wichtiger, Abhängigkeiten zu vermeiden bzw. Abhängigkeiten an 
einer Stelle zu konzentrieren. Und das geht mit Variante A besser: Du 
hast eine GUI und Du hast eine Simulation. Beide wissen nichts 
voneinander. Erst durch eine 3. Komponente wird GUI und Simulation 
miteinander verbunden.
Dafür ist es etwas mehr Arbeit und die Objekt-Struktur (Vererbung etc.) 
wird explizit. Aber auch hier zeigt die Erfahrung, dass große 
Klassen-Hierarchien eher nachteilig sind. Sie sind steif und schwer zu 
ändern.

Zu diesem Thema werden ganze Bücher gefüllt, das kann man hier nicht 
alles kurz wiedergeben. Auch im Klassiker "Gang of Four", wird dieser 
Fall diskutiert.

von Björg Strupp oder so (Gast)


Lesenswert?

Experte schrieb:
> Aus Erfahrung würde ich Variante A wählen, auch wenn es gegen die vor
> noch 25 Jahren gelehrte "Prinzipien" der OO "verstößt".

I beg to differ! Beispiel aus der Praxis: Die Simulation selbst soll 
unabhängig von plattformspezifischen APIs (Direct X, OpenGL, X-Window 
oder was auch immer) sein - nur die GUI darf das. Trotzdem möchte ich 
die Polymorphie nutzen, um nicht für jedes neue Objekt im GUI neue 
Zeichenmethoden zu implementieren.

Lösung A: Ich abstrahiere, z.B. mit

extern DrawCircle();
extern DrawLine();
...

Die eigentlichen Zeichenroutinen müssen dann erst beim Linken von GUI 
und Simulation irgendwie dazukommen.


Lösung B: Ich arbeite mit dem Visitor-Pattern, d.h. jede Klasse hat eine 
acceptVisitor-Methode, und da holt man sich dann die für das Zeichnen 
nötigen Informationen.

von Elias K. (elik)


Lesenswert?

Meiner Meinung nach gehört die Funktion, die die Modelldaten auf die GUI 
übersetzt, weder ins Model noch in die GUI, sondern in den Controller. 
Such Mal nach "Model View Controller" für mehr Details.

von Karl (Gast)


Lesenswert?

Wie wäre es mit einer Eigenen Klasse für die Aufgabe? Schließlich will 
man im realen Leben vermutlich nicht nur einen Kreis mit einem Pixel, 
sondern einen Kreis mir n-Pixeln im Abstand r-Pixel com Mittelpunkt m an 
der Position x;y-Pixel. (Pixel lässt sich durch eine beliebige Einheit 
ersetzen).

von Experte (Gast)


Lesenswert?

Ja mei, wir können noch geschätzt einhundert verschiedene Entwürfe für 
das Problem in den Ring werfen...

Björg Strupp oder so schrieb:
> Ich abstrahiere, z.B. mit
>
> extern DrawCircle();
> extern DrawLine();

Tja, Klassiker: Ist nämlich keine (bzw. sehr schwache) Abstraktion, 
sondern einfach nur eine Verschleierung.

Was machst Du wenn Du z.B. eine Monochrom-GUI hast? Was machst Du, wenn 
die GUI gar keine Grafik-Primitive kennt, weil sie z.B. in einer Konsole 
läuft? Was machst Du, wenn die GUI ausschließlich mit Sprites arbeitet? 
Ach, es gibt so viele Varianten auf dieser Welt...

von F. (Gast)


Lesenswert?

Vielen Dank für die Antworten. Da habe ich Anhaltspunkte. Ich dachte, 
das Problem wäre vielleicht schon gelöst worden.

Dann werde ich mir mal ansehen, wie ich es am Besten umsetze.

Nur aus Interesse, weiß jemand, wie das bei modernen 3D-Computerspielen 
gemacht wird?

von Experte (Gast)


Lesenswert?

F. schrieb:
> Nur aus Interesse, weiß jemand, wie das bei modernen 3D-Computerspielen
> gemacht wird?

Unterschiedlich.

Beliebt ist: ECS - Entity Component System

  https://github.com/SanderMertens/ecs-faq

von c-hater (Gast)


Lesenswert?

F. schrieb:

> wo kommt in der objektorientierten Programmierung zum Beispiel die
> Zeichenfunktion hin. In die Graphikausgabe oder in die Objekte?

Das klassische OO-Problem: Es kollidieren Architekturen.

Die übliche Lösung sind: Interfaces. Die Alternative wäre 
Mehrfach-Vererbung, aber die letztlich daraus resultierende Komplexität 
ist kaum beherrschbar.

Also: mache deine Klassenhierachie, so wie du sie brauchst und füge den 
Klassen, die die Fähigkeit haben müssen, sich zeichnen zu können, ein 
entsprechendes Interface (und natürlich dessen Implementierung) hinzu.

von Björg Strupp oder so (Gast)


Lesenswert?

Experte schrieb:
> Was machst Du, wenn
> die GUI gar keine Grafik-Primitive kennt, weil sie z.B. in einer Konsole
> läuft?

Ich habe genau den Fall bei mir. Die Simulation kann auch ohne GUI auf 
einem Cluster laufen, in einer Konsolenanwendung. Da habe ich dann

void DrawCircle()
{
  // Dude, I'm a console app!
}

von Noch ein Kommentar (Gast)


Lesenswert?

> Aus Erfahrung würde ich Variante A wählen...

Ja, genau deswegen mache ich diesen Job! In allen anderen Bereichen 
schreiben irgendwelche Bürokraten und sonstigen Sesselpuper vor, wie wir 
es machen müssen.

Programmierung ist das einzig übrig gebliebene mittelalterliche 
Handwerk, in dem die alten Meister aus Erfahrung heraus selbst 
entscheiden, wie es gemacht werden muss.

Hay, F.(Gast) die Antwort auf deine Frage: Es gibt keinen Konsens. Es 
gibt keine Vorschriften. Es gibt auch keine DIN Normen. Du musst deine 
eigenen Erfahrungen sammeln.

von Thomas W. (goaty)


Lesenswert?

Verwendet man nicht gleich Templates oder Concepts heutzutage ?

von Björg Strupp oder so (Gast)


Lesenswert?

Thomas W. schrieb:
> Verwendet man nicht gleich Templates oder Concepts heutzutage ?

Ein Template löst doch nicht das Problem das TO.

von udok (Gast)


Lesenswert?

F. schrieb:
> wo kommt in der objektorientierten Programmierung zum Beispiel die
> Zeichenfunktion hin. In die Graphikausgabe oder in die Objekte?

Stroustrup (der C++ Erfinder) hat sich damit intensiv im Buch 
"Programming Principles and Practice" beschäftigt.

Seine Lösung:  Alle Objekte leiten sich von einer Interfaceklasse 
"Shape" ab.
Die virtuelle Interfacefunktion "draw_lines()" wird von den Objekten
überschrieben. Das "Fenster" hat eine Liste von Zeigern auf "Shape" 
Objekte, und ruft die virtuelle "draw_lines()" Funktion auf, um sich neu 
zu zeichnen.
Das "Fenster" ist wieder eine eigene Klasse, die eine Liste
von Objekten verwaltet. Die Anwendung kann dann Objekte in die Liste 
dazufügen oder rausnehmen.

Gruss,
Udo

von F. (Gast)


Lesenswert?

udok schrieb:
> Seine Lösung:  Alle Objekte leiten sich von einer Interfaceklasse
> "Shape" ab.
> Die virtuelle Interfacefunktion "draw_lines()" wird von den Objekten
> überschrieben. ...
Das wäre ja meine zweite Variante, wenn ich das richtig verstehe (außer, 
dass ich nicht Shape und drawLines habe, sondern paintable und paint() 
).

Auch noch danke für die weiteren Antworten.

von udok (Gast)


Lesenswert?

F. schrieb:
> Das wäre ja meine zweite Variante, wenn ich das richtig verstehe (außer,
> dass ich nicht Shape und drawLines habe, sondern paintable und paint()

Im Prinzip ja, nur das paintable::paint() eine virtuelle Funktion ist.
Das GUI sieht nur eine Liste/Vektor von paintable Objekten, und 
verwendet die paintable::paint() zum Zeichnen.  Das GUI weiss aber nicht 
ob es sich um
einen Fussgänger oder ein Auto handelt.
Das GUI ist damit von der konkreten Implementierung der 
Simulations-Objekte
entkoppelt.  Es können einfach neue Objekte dazugefügt werden, ohne dass
der GUI Code geändert werden muss.

von F. (Gast)


Lesenswert?

udok schrieb:
> F. schrieb:
>> Das wäre ja meine zweite Variante, wenn ich das richtig verstehe (außer,
>> dass ich nicht Shape und drawLines habe, sondern paintable und paint()
>
> Im Prinzip ja, nur das paintable::paint() eine virtuelle Funktion ist.
So war das von mir auch gemeint, das ist nur dem Pseudocode zum Opfer 
gefallen. In C++ hätte ich paintable::paint() virtuell gemacht, in Java 
wäre paintable ein Interface geworden.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.