OO ist gescheitert

Ist OO (definiert nach Alan Key) gescheitert?

  • JA

  • NEIN


Die Ergebnisse sind erst nach der Abstimmung einsehbar.

knotenpunkt

Mitglied
in dem Thread möchte ich nur zwei Punkte ansprechen, die darlegen warum oo vllt. nicht so sinnvoll ist.

Vorab möchte ich definieren, was OO ist und was es nicht ist:

OO ist nicht: Polymorphy, Abstraktion, Vererbung, SOLID-Prinzipien,......
OO ist ein Paradigma wie man ganz generell eine Software aufbauen soll. Sprich in Klassen/Objekten, die sich dann später gegenseitig via Messages unterhalten.
Dabei ist wichtig, dass Daten und Verhalten gekapselt wird.

Klassen, die im Namen Service/Manager/Sachbearbeiter etc pp haben sind Prozedurensammlungen, die zwar in sich selbst oobjektorientiert konfiguriert werden können, dann aber auf den DomänenObjekten, also den eigentlichen Daten/Entitäten, prozedural arbeiten. Diese werden in die Services hineingereicht, herausgereicht, herumgereicht, so wie man es eben aus der prozeuduralen Programmierung kennt.

Viele Programmierprojekte verwenden diese Service-Klassen.
Ein Indiz dafür ist der Gebrauch der DI-Injektion/Service-Lokatoren.

Ein objektorientiertes Softwareprojekt würde keine einzige DI-Injektion benötigen.

Warum aber verwenden viele DI-Injektionen/Service-Klassen.
Vllt. einfach aus dem Grund, weil OO nichts taugt?!
Wie seht ihr das?


Jetzt hätte ich mal noch eine Frage zu einem OO-Aufbau, wie ihr das selbst/anders machen würdet.
Ich habe eine normale oder aber auch Service Klasse, die wie folgt aussehen könnte.

Code:
class Blub
{
public T member1;
public T member2;
public T member3;

public T member4

public Blub(......)
{
....
}

public RESULT doSth(T arg1, T arg2)
{
// und das ganze macht irgendwas //und verwendet member1-3 sowie arg1-2
}


}

die Probleme, die ich mit dem ganzen hier habe sind die Folgende.

1:
nach welchem sinnvollen Algorithmus soll ich entscheiden, dass member1-3 fest in das Objekt gebunden sind und arg1-2 schön dynamisch hineingereicht werden können/dürfen?

2:
möchte ich die Methode/Message-Empfänger doSth mit unterschiedlichen member1-3 variablen aufrufen, so habe ich zwei möglichkeiten:
Ich habe ein Objekt vom Typ Blub und vor jedem doSth Aufruf passe ich member1-3 an.
Oder ich erstelle N Objekte vom Typ Blub und rufe hier jeweils doSth auf.
Fühlt sich beides sehr falsch an^^
Ein BSP:

Ich habe eine EmailServiceKlasse, die zur Aufgabe hat, DomänenObjekte mit dem Typ EmailNachricht (enthält Absender, Betreff, Empfänger) überreicht zu bekommen und schließlich mit den in dem Service hinterlegten Config-Daten, die Email abzusenden.
Die DomänenObjekte vom Typ EmailNachricht sind anemic (siehe Fowler), da sie einer c++ Struct ohne nennenswertes Verhalten gleichen. Also ich finde die Structs gut, Fowler findet sie im Kontext der objektorientierung nicht gut.
Die Service-Klasse EmailServiceKlasse dagen ist in sich selbst schon OO aufgebaut.
Man kann Sie bspw. mit dem Emailanbieter konfigurieren. Also Domain, Port, Sonstiges.
Ausserdem erhält die EmailServiceKlasse eine Abhängigkeit LoggerServiceKlasse1 eingespritzt.

Was ist aber, wenn ich jetzt bspw. zur Laufzeit in Abhängigkeit von Uhrzeit und von der EmailNachricht (Typ EmailNaricht) bestimmen möchte, dass ich LoggerServiceKlasse2 oder LoggerServiceKlasse7 verwenden möchte? Außerdem habe ich 50Emailanbieter und auch das soll genau wie die LoggerServiceKlassen zur Laufzeit und in Abhängigkeit........ entschieden werden.

Ich glaube Ihr seht das Problem, oder?

Eine einfache prozedur mit
Code:
sendMail(Emailnachricht)
{
//hole in Abhängigkeit von time() und Emailnachricht -> LoggerServiceKlasseX
//hole in Abhängikeit von time() und Emailnachricht ->EmailanbieterX

sendMail(LoggerServiceKlasseX, EmailanbieterX, Emailnachricht)//überladen

}
wäre doch viel besser.

pseudo-OO mit einer weiteren ServiceKlasse könnte dann wie folgt aussehen:

Code:
class EmailanbieterANDLoggerServiceDeciderService
{

public sendMail(Emailnachricht)
{
extrahiere aus Emailnachricht und time() -> LoggerServiceKlasseX und EmailanbieterDataX

new EmailService(LoggerServiceX, EmailanbieterDataX).sendMail(Emailnachricht);

}

}
Nein wir haben nicht den 1. April, aber so wird das teilweise gemacht.
Den Sinn dahinter verstehe ich nicht^^

Und jetzt möchte ich noch ein Newsletter an 1Mio User senden.
Warum soll ich mir 1Mio*(was ich sonst noch an unnötiigen over-head-Klassen habe) sinnlose Objekte auf meinen wertvollen HEAP-Speicher klatschen, nur um eine prozedurale-Funktionalität auszuführen?


3:
member4 wird in doSth gar nicht benötigt, in doSthOthers() vllt. dann schon.
Das ganze Kollidiert auch mit (2:)
Wenn ich dann ständig
Code:
new EmailService(LoggerServiceX, EmailanbieterDataX, null).sendMail(Emailnachricht);
//wobei null member4 abdeckt
aufurufen muss, dann ist der Mond bereits mit der Erde zu einem Saturn2 fusioniert^^

Das Single-Responsibilty-Prinzip decke ich am besten in einer Prozedur ab.
Eine Prozedur benötigt genau exakt die Daten, die es für sich eben benötigt.
Eine MemberFunktion die den This-Pointer/This-Referenz bekommt, davon aber nur 20% der enthaltenen Daten benötigt, hat nur unnötige Abhängigkeiten.


Soweit erstmal, ich habe noch einiges mehr, was OO den Nährstoff entziehen würde, aber ich belasse es erstmal dabei.

Und wie oben bereits geschrieben. Eine Archtitektur mit an Haufen Service-Klassen, die mit anemic-DomänObjekten/Structs arbeiten ist nicht objektorientiert, sondern wie ich es an der Stelle gerne nenne: Pseudo-objektorientiert. Die ServiceKlassen-Landschaft selbst ist objektorientiert. Das Zusammenspiel mit den Daten, mit denen die Landschaft arbeitet dagegen nicht. Zumindest ein Schritt in die richtige Richtung. Wenn jetzt auch die Landschaft selbst wieder proezduraler werden würde -> dann hätten wir endlich wieder eine sehr gute Codequalität, die einerseits performant ist, andererseits aufgrund Ihrer Einfachheit und nicht Overheadheit gut wartbar ist und dritterseits wesentlich schneller zum Erfolg führt.



lg knotenpunkt
 

mrBrown

Super-Moderator
Mitarbeiter
Code:
class Blub
{
public T member1;
public T member2;
public T member3;

public T member4

public Blub(......)
{
....
}

public RESULT doSth(T arg1, T arg2)
{
// und das ganze macht irgendwas //und verwendet member1-3 sowie arg1-2
}


}
nach welchem sinnvollen Algorithmus soll ich entscheiden, dass member1-3 fest in das Objekt gebunden sind und arg1-2 schön dynamisch hineingereicht werden können/dürfen?
Kommt drauf an, ob man mehr als nur "Alles ist ein Objekt und die kommunizieren über Messages" für die Beantwortung zulassen will ;)

Sind halt Design/Entwurfs-Fragen, die kann man eben auch nur auf Design-Ebene klären - und dann landet man bei sowas wie SOLID, High Cohesion, der Domäne...

Nur mit den oben stehenden Informationen: völlig egal was man macht.


2:
möchte ich die Methode/Message-Empfänger doSth mit unterschiedlichen member1-3 variablen aufrufen, so habe ich zwei möglichkeiten:
Ich habe ein Objekt vom Typ Blub und vor jedem doSth Aufruf passe ich member1-3 an.
Oder ich erstelle N Objekte vom Typ Blub und rufe hier jeweils doSth auf.
Fühlt sich beides sehr falsch an^^

Die erste Variante ist Unsinn - wäre das nötig, sollten die Dinge Argumente und keinen Felder sein.
Variante zwei ist völlig Plausibel: wenn member1-3 Teil des Objekts sind, sind's mit unterschiedlichen member1-3 unterschiedliche Objekte, warum sollte ich sie dann nicht neu erzeugen?

Mit nur so spärlichen Informationen kann man da aber keine fundierte Aussage treffen...

Was ist aber, wenn ich jetzt bspw. zur Laufzeit in Abhängigkeit von Uhrzeit und von der EmailNachricht (Typ EmailNaricht) bestimmen möchte, dass ich LoggerServiceKlasse2 oder LoggerServiceKlasse7 verwenden möchte? Außerdem habe ich 50Emailanbieter und auch das soll genau wie die LoggerServiceKlassen zur Laufzeit und in Abhängigkeit........ entschieden werden.

Ich glaube Ihr seht das Problem, oder?
Hm, einfach für beides einen Proxy.
Der EmailService braucht davon keine Ahnung zu haben, der ruft einfach die entsprechenden Methoden auf (auf dem Proxy, der das entsprechend weiter delegiert).


Und jetzt möchte ich noch ein Newsletter an 1Mio User senden.
Warum soll ich mir 1Mio*(was ich sonst noch an unnötiigen over-head-Klassen habe) sinnlose Objekte auf meinen wertvollen HEAP-Speicher klatschen, nur um eine prozedurale-Funktionalität auszuführen?
Du kannst dir die nötigen Informationen natürlich auf den noch wertvolleren Stack-Speicher klatschen ;)
Irgendwo müssen die nötigen Informationen liegen, wie soll man sie sonst versenden?

member4 wird in doSth gar nicht benötigt, in doSthOthers() vllt. dann schon.
Das ganze Kollidiert auch mit (2:)
Wenn ich dann ständig
Code:
new EmailService(LoggerServiceX, EmailanbieterDataX, null).sendMail(Emailnachricht);
//wobei null member4 abdeckt
aufurufen muss, dann ist der Mond bereits mit der Erde zu einem Saturn2 fusioniert^^

Deshalb würde man es auch nicht so machen, eine mögliche Lösung steht weiter oben...

Das Single-Responsibilty-Prinzip decke ich am besten in einer Prozedur ab.
Eine Prozedur benötigt genau exakt die Daten, die es für sich eben benötigt.
Eine MemberFunktion die den This-Pointer/This-Referenz bekommt, davon aber nur 20% der enthaltenen Daten benötigt, hat nur unnötige Abhängigkeiten.

Das Single-Responsibilty-Prinzip decke ich am besten mit mehreren Klassen/Objekten ab.
Ein Objekt besteht genau exakt den Daten, die es für sich eben benötigt.
Einer Prozedur alle nötigen Daten einzeln übergeben, ist doch völliger Unsinn ;)


Und wie oben bereits geschrieben. Eine Archtitektur mit an Haufen Service-Klassen, die mit anemic-DomänObjekten/Structs arbeiten ist nicht objektorientiert, sondern wie ich es an der Stelle gerne nenne: Pseudo-objektorientiert. Die ServiceKlassen-Landschaft selbst ist objektorientiert. Das Zusammenspiel mit den Daten, mit denen die Landschaft arbeitet dagegen nicht.
Deswegen nutzt man ja auch kein anemic-model, sondern ein vernünftiges Domain-Model. Fowler dazu: "The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design".
Das ist etwas, was ich gerne Pseudo-Argument nenne :p Ein Argument, was PRO gutem-OO benutzt wird, so verdrehen, dass man das als KONTRA OO nutzen kann.

Wenn jetzt auch die Landschaft selbst wieder proezduraler werden würde -> dann hätten wir endlich wieder eine sehr gute Codequalität, die einerseits performant ist, andererseits aufgrund Ihrer Einfachheit und nicht Overheadheit gut wartbar ist und dritterseits wesentlich schneller zum Erfolg führt.

Also wieder alles zurück auf Anfang, damit man dann von dort aus von neuem den Vorteil von Objekt-Orientiertem Design sehen kann? ;)
 

knotenpunkt

Mitglied
Hey,

Nur mit den oben stehenden Informationen: völlig egal was man macht.
Ich suche eben eine generische Lösung. Ein statisch aufgebautes starres objekorientiertes Programmgerüst, ist nicht wirklich flexibel und generisch einsetzbar. Das hat zur Folge das es gut testbar ist, aber auch dass man viel Code für wenig Funktionalität schreibt.

Die erste Variante ist Unsinn - wäre das nötig, sollten die Dinge Argumente und keinen Felder sein.
Variante zwei ist völlig Plausibel: wenn member1-3 Teil des Objekts sind, sind's mit unterschiedlichen member1-3 unterschiedliche Objekte, warum sollte ich sie dann nicht neu erzeugen?
Wenn ich jetzt im Batch-Betrieb doSth aufrufen möchte?
Die Objektinstanzen sind in dem Fall nur kurzlebig, also eine Art ProzedurenObjekt. new Object() -> parameter als Membervariablen via set-Methoden setzen -> executeMethod aurufen -> Destructor/Garbage Collection.

Eine direkte Prozedurenaufruf wäre erstens ressourcenschonender und würde nicht soviel Overhead-Code benötigen.

Das Single-Responsibilty-Prinzip decke ich am besten mit mehreren Klassen/Objekten ab.
Ein Objekt besteht genau exakt den Daten, die es für sich eben benötigt.
Einer Prozedur alle nötigen Daten einzeln übergeben, ist doch völliger Unsinn ;)
Warum ist das Unsinn?
So hast du die Möglichkeit, wirklich alles aus der Prozedur rauszuholen. Bspw. indem du den übergebenen State leicht abändern kannst, nur für die Prozedur.
Zudem kannst du der Prozedur Structs (c++), associative Arrays (PHP), Java-Objekte ohne Verhalten == structs wie in C/C++ übergeben. Somit übergibst die Variablen auch gesammelt^^. Zudem hast du die Möglichkeit mehrere gesammelte Daten zu übergeben und nicht nur ein Datensammelsatz (this).
Desweiteren können die Structs bei Bedarf vor der Übergabe angepasst werden.
Arbeite ich dann nur mit dem Rückgabewert weiter bin ich schon fast bei der funktionalen programmierung angelagant. Mit einem Sideffekt bin ich dann bei der prozeduralen Programmierung.

Aber warum ist das nicht sinnvoll?

Hm, einfach für beides einen Proxy.
Der EmailService braucht davon keine Ahnung zu haben, der ruft einfach die entsprechenden Methoden auf (auf dem Proxy, der das entsprechend weiter delegiert).
Könntest du eventuell ein Beispiel posten. Kann mir nicht ganz vorstellen was du meinst.
Aber ich denke es fällt auch hier ein prozedural aufgebautes Programm heraus.

Du kannst dir die nötigen Informationen natürlich auf den noch wertvolleren Stack-Speicher klatschen ;)
Irgendwo müssen die nötigen Informationen liegen, wie soll man sie sonst versenden?
Ist denke so bin ich ressourcenschonender und zweitens auch flexibler.
Du kannst die Programmstruktur auch für Third-Party APIs gut einsetzen, wo du die Daten nicht selbst im Speicher hälst. Auch passt das ganze perfekt zu relationelen Datenbanken, wo du die Datenänderungen direkt auf/in der Datenbank durchführst.

Deshalb würde man es auch nicht so machen, eine mögliche Lösung steht weiter oben...
Was würde man so nicht machen?

Deswegen nutzt man ja auch kein anemic-model, sondern ein vernünftiges Domain-Model. Fowler dazu: "The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design".
Das ist etwas, was ich gerne Pseudo-Argument nenne :p Ein Argument, was PRO gutem-OO benutzt wird, so verdrehen, dass man das als KONTRA OO nutzen kann.
Wie sieht ein vernünftiges und vor allem flexibles und dynamisch einsetzbares domain-model aus?
Du hast noch nichts zu meinen oben genannten Services und DI-Injection/Service-Locatoren gesagt.
In einem vernünftigen Domain-Model kommen diese nicht vor, würdest du das bestätigen?
Wenn aber keine proezdurale Service-Klassen existieren, wie erreichst du dann ein dynamisches Programm? auf der Grundlage von einer starren/unflexiblen Objektwelt?
Ich denke die Paradoxie erkennt man^^

Ein Beispiel, was ich dafür immer gerne hernehme:

Du möchtest eine UserBan-Funktionalität implementieren.
Es macht null Sinn, die Banfunktionalität in die Klasse eines Users/Admin hineinzuimplementieren.
Also benötige ich hier sozusagen einen BanService, etwas aussenstehendes

diesem BanService wird jetzt folgendes übergeben. der User der bannen möchte, der User der gebannt werden soll, die Uhrzeit und vieles weitere (eventuell holt sich die Funktion die Daten auch selbst, da noch zu Beginn nicht ganz klar ist, welche Daten die Funktion letzten Endes benötigt)

Gehen wir davon aus der bannende User hat die abstrakte Adminberechtigung 5
Alle Admins mit dem Status 5 dürfen nur zwischen 17 und 18Uhr bannen.

Außerdem gibt der Admin eine Ban-Zeit zwischen 1-10 an.

Wählt der Admin die 4 und hat davor schon 3User gebannt, die älter als 70Jahre sind und eine weitere Person die sich erst vor 3Tagen registriert hat, dann beträgt bei der Nummer vier die Ban-Zeit der aktuellen Person 9 Tage. Außerdem, sollte die zu bannende Person bereits über 6Monate registriert sein, wird diese via Email über den Ban benachrichtigt. Je nach Flags, die der Admin setzt, kann die Emailbenachrichtigung unterdrückt und falls nicht, im Detail angepasst werden.

usw. usf.

Warum das ganze nicht in die UserAdmin-Klasse?
Vllt. möchte das System selbst ohne zutun eines Admins selbst auch User bannen können, aufgrund irgendwelcher Kriterien.


Was ich damit verdeutlichen möchte: Das ganze geht doch nur sinnvoll in einer Prozedur bzw. divide-and-conquer Prozedurlandschaft.
Wie möchtest du das sinnvoll objektorientiert machen?


Also wieder alles zurück auf Anfang, damit man dann von dort aus von neuem den Vorteil von Objekt-Orientiertem Design sehen kann?
Zurück auf ein Level, das gut funktioniert.
Man sollte nicht OO Programmieren des OO zu Willen.

Und warum verwenden soviele Programmierer Services/etc pp?
Services sind prozedural!


lg knotenpunkt
 

mrBrown

Super-Moderator
Mitarbeiter
Ich suche eben eine generische Lösung. Ein statisch aufgebautes starres objekorientiertes Programmgerüst, ist nicht wirklich flexibel und generisch einsetzbar. Das hat zur Folge das es gut testbar ist, aber auch dass man viel Code für wenig Funktionalität schreibt.
Häh? ich habe keine Ahnung, was das mit Objektorientiert vs Prozedual zu tun hat...

Wenn ich jetzt im Batch-Betrieb doSth aufrufen möchte?
Die Objektinstanzen sind in dem Fall nur kurzlebig, also eine Art ProzedurenObjekt. new Object() -> parameter als Membervariablen via set-Methoden setzen -> executeMethod aurufen -> Destructor/Garbage Collection.

Eine direkte Prozedurenaufruf wäre erstens ressourcenschonender und würde nicht soviel Overhead-Code benötigen.

Objektorientiert != Garbagecollector != Resourcen-Verschwendend != Overhead-Code.

Nach dem Kompilieren kann aus "new Object() -> parameter als Membervariablen via set-Methoden setzen -> executeMethod aurufen -> Destructor/Garbage Collection." durchaus ein "executeMethod aurufen" geworden sein, genauso wie das bei deiner Variante der Fall wäre.

Ressourcenschonender ist dann nichts mehr, es bleibt der "Overhead-Code" (solange du das "mehr" an nötigem Source-Code meinst), der von den wenigstens als "Overhead" angesehen wird, sondern einfach als sinnvolle Struktur.


Warum ist das Unsinn?
So hast du die Möglichkeit, wirklich alles aus der Prozedur rauszuholen. Bspw. indem du den übergebenen State leicht abändern kannst, nur für die Prozedur.
Zudem kannst du der Prozedur Structs (c++), associative Arrays (PHP), Java-Objekte ohne Verhalten == structs wie in C/C++ übergeben. Somit übergibst die Variablen auch gesammelt^^. Zudem hast du die Möglichkeit mehrere gesammelte Daten zu übergeben und nicht nur ein Datensammelsatz (this).
Desweiteren können die Structs bei Bedarf vor der Übergabe angepasst werden.
Arbeite ich dann nur mit dem Rückgabewert weiter bin ich schon fast bei der funktionalen programmierung angelagant. Mit einem Sideffekt bin ich dann bei der prozeduralen Programmierung.

Aber warum ist das nicht sinnvoll?

Und bringe ich dann die Funktion noch mit in dem Objekt unter, sodass ich die Funktion und die dazu gehörenden Daten zusammen habe, bin ich bei Objektorientiert und wir drehen uns im Kreis.
Und Vermeide ich dann Seiteneffekte, bin ich bei Objektorientiert und Funktional (siehe Scala).

Wenn du Objekte bei Objektorientierter Programmierung als "Datensammelsatz" ansiehst, hast du Objektorientierte Programmierung nicht verstanden - dann ist es natürlich besser, keine Objektorientierte Programmierung zu nutzen.


Könntest du eventuell ein Beispiel posten. Kann mir nicht ganz vorstellen was du meinst.
Aber ich denke es fällt auch hier ein prozedural aufgebautes Programm heraus.

Wenn du über Objektorientierte Programmierung urteilen willst, sollte dir doch zumindest das Proxy-Pattern geläufig sein?

Code:
class MailService{
  sendMail(Mail mail) {
    mailLogger.log(mail);
    mailServer.send(mail);
  }
}
interface  MailLogger {
  log(Mail mail);
}
class MailLoggerProxy implements MailLogger {
 log(Mail mail) {
   MailLogger logger  = getLoggerForMail(mail);
    logger.log(mail);
  }

  getLoggerForMail(Mail mail) {
    //die von dir nicht gezeigte Magie...
  }
}
class SimpleMailLogger implements MailLogger {
 log(Mail mail) {
   sout(mail.sender);
  }
}

Aber wie erreichst du denn bei deinem rein prozeduralem Programm zB die Auswahl zwischen unterschiedlichen Mail-Servern, die auf unterschiedliche Arten und Weisen benutzt werden müssen? Und wie kann man da dynamisch zur Laufzeit neue hinzufügen?


Ist denke so bin ich ressourcenschonender und zweitens auch flexibler.
Du kannst die Programmstruktur auch für Third-Party APIs gut einsetzen, wo du die Daten nicht selbst im Speicher hälst. Auch passt das ganze perfekt zu relationelen Datenbanken, wo du die Datenänderungen direkt auf/in der Datenbank durchführst.
Inwiefern sind Daten auf dem Stack resourcenschonender und flexibler als auf dem Heap? (wobei Objektorientiert natürlich nicht voraussetzt, das Daten überhaupt auf dem Heap liegen, da vermischt du wieder eine Mögliche Implementierung mit einem Konzept)
Und wie bringst du überhaupt 1 Mio. Mails auf dem Stack unter?

Wo die Daten herkommen ist völlig egal, ich kann durchaus auch Objektorientiert mit denen arbeiten, wenn die in einer Relationen Datenbank liegen.
Wie passt deine Lösung eigentlich zu Objektorientierten Datenbanken? ;)


Was würde man so nicht machen?
Das, was ich zitiert hab?

Wie sieht ein vernünftiges und vor allem flexibles und dynamisch einsetzbares domain-model aus?

Genauso, wie die eine "vernünftige und vor allem flexible und dynamisch einsetzbare" Prozedur aussieht: nicht existent.

Ein Domänen-Modell ist nur ein Modell für *eine* Domäne, wie soll sowas dynamisch und flexibel aussehen?
Du hast noch nichts zu meinen oben genannten Services und DI-Injection/Service-Locatoren gesagt.
In einem vernünftigen Domain-Model kommen diese nicht vor, würdest du das bestätigen?
Wenn aber keine proezdurale Service-Klassen existieren, wie erreichst du dann ein dynamisches Programm? auf der Grundlage von einer starren/unflexiblen Objektwelt?
Ich denke die Paradoxie erkennt man^^

Weder kommen Service-Klassen in einem Domänen-Model niemals vor, noch sind diese zwingend nötig. (Der Begriff "Service" ist btw. hauptsächlich durch Domain-Driven-Design geprägt.)

Ich habe keine Ahnung, was du unter einem "dynamischen Programm" verstehst, aber generell und nach deiner Definition von OO erreicht man das, indem Objekte sich Nachrichten schicken.

DI ist aber ein generelles Konzept, und hat nicht mit Services zu tun.
Es ist nichts weiter, als dass Objekte ihre Abhängigkeiten übergeben bekommen, anstatt sie sich selbst zu besorgen. Über die Abhängigkeiten sagt das nichts aus.


Ein Beispiel, was ich dafür immer gerne hernehme:
[...]
Was ich damit verdeutlichen möchte: Das ganze geht doch nur sinnvoll in einer Prozedur bzw. divide-and-conquer Prozedurlandschaft.
Wie möchtest du das sinnvoll objektorientiert machen?

Dein Argument ist: Sobald irgendwo eine Funktion existiert, ist das ganze Prozedural und absolut nicht mehr Objekt-Orientiert?

Wie würdest du das ganze denn, ohne jegliche Objekt-Orientierung, umsetzen? Ellenlange Methoden mit zig verschachtelten if's?


Zurück auf ein Level, das gut funktioniert.
Man sollte nicht OO Programmieren des OO zu Willen.

Und warum verwenden soviele Programmierer Services/etc pp?
Services sind prozedural!

Man programmiert nicht OO, um OO zu programmieren, sondern weil es für die meisten Dinge gut Funktioniert. OO gibt es nicht ohne Grund.

Und das Methoden in Services Prozedural sind (ich gehe mal davon aus, dass das nach deiner Definition von Prozedural so ist), bedeutet für dich, dass sie nicht mehr Objektorientiert sind?
 
Zuletzt bearbeitet:

AndiE

Top Contributor
Als "Halb-Laie" finde ich den Titel sehr provokativ, und auch der Entscheidung, was OOP ist und was nicht, kann ich nicht zustimmen.
Aus meiner "C"-Zeit kenne ich das so, dass Maschinencode (Mnemotics) in Prozeduren zusammengefaßt wurden, und es dann Header-Dateien mit den Signaturen gab und Compilierte Bibliotheken. Das Programm selbst besteht dann wieder das Prozeduren, die Porzeduren aufrufen, wobei mit der "main()"-Prozedur begonnen wird. In diesem Umfeld, dass ich als "prozedural" bezeichnen würde, gibt es dann als komplexisten Typen die "struct", die mehrere Daten verschiedener Typen zusammenfasst.
Der objektorientierte Ansatz, wie wir ihn aus dem MVC- oder dem 3-Schichten-Modell kennen, erzeugt Datenobjekte, die miteinander kommunizieren. Dabei kann man eben zwischen der statischen und der dynamischen Sicht unterscheiden. Bei einem Taschenrechner gibt es eine Klasse, die die Darstellung übernimmt und Nutzereingaben entgegennimmt. Bei einer Nutzereingabe ruft sie eine Methode des Controllers auf, der wiederum eine Methode des Datenobjektes aufruft. So würde ich das organisieren, auch wenn es hier im Forum gerne anders gemacht wird. Der Vorteil ist eben, dass ich als Bearbeiter mich nicht erst umständlich in das reindenken muss, was der andere programmiert hat.Wer schon mal den Hüpfweg in einem prozeduralen Programm nachvollzogen hat, weiß was ich meine. Bei einer CD-Verwaltung etc. kann ich die vorhandene Struktur im Datenmodell realitätsnah abbilden. Ich habe ein Regal->ich bilde das mit einer List-Klasse ab. In dem Regal sind CD->Datenobjekte.

Ich finde, dass Scriptsprachen dazu verleiten, dieses Paradigma zu umgehen, was dann zu "IOP-indirekt objektorientierter Programmierung" führt, ein Begriff, der mir im Zusammenhang mit "Perl" aufgefallen ist. Dabei benutzt man die Methoden der "vorgefertigten" Klassen, und das Programm selbst kommt ohne Klassendefinition und Methodendeklaration aus.
 

knotenpunkt

Mitglied
Häh? ich habe keine Ahnung, was das mit Objektorientiert vs Prozedual zu tun hat...
Es soll einen negativen Gesichtspunkt von OO hervorheben und gleichzeitig darauf hinweisen, dass Prozedurale Programmierung diese Schwäche nicht hat.


Objektorientiert != Garbagecollector != Resourcen-Verschwendend != Overhead-Code.

Nach dem Kompilieren kann aus "new Object() -> parameter als Membervariablen via set-Methoden setzen -> executeMethod aurufen -> Destructor/Garbage Collection." durchaus ein "executeMethod aurufen" geworden sein, genauso wie das bei deiner Variante der Fall wäre.

Ressourcenschonender ist dann nichts mehr, es bleibt der "Overhead-Code" (solange du das "mehr" an nötigem Source-Code meinst), der von den wenigstens als "Overhead" angesehen wird, sondern einfach als sinnvolle Struktur.
Es kann?!...... Ja die Compiler optimieren schon ganz gut, wobei ob das hier wegoptimiert wird, das ist so die Frage^^
Aber es fühlt sich halt einfach schlecht an, "schlechten" Code zu schreiben, auch wenn der Compiler das nacher wegoptimieren würde. Warum nicht gleich straightforward programmieren?
Ja ich meine mit Overhead auch den Source-Code.
Warum soll diese Struktur, die du meinst, sinnvoller sein?
Es sieht vllt. ästhetisch aus, mehr aber auch nicht. Und wenn man dann noch weiß, dass dies wegoptimiert werden muss, dass es performt, dann sehe ich darin auch keine Ästhetik mehr^^


Und bringe ich dann die Funktion noch mit in dem Objekt unter, sodass ich die Funktion und die dazu gehörenden Daten zusammen habe, bin ich bei Objektorientiert und wir drehen uns im Kreis.
Und Vermeide ich dann Seiteneffekte, bin ich bei Objektorientiert und Funktional (siehe Scala).

Wenn du Objekte bei Objektorientierter Programmierung als "Datensammelsatz" ansiehst, hast du Objektorientierte Programmierung nicht verstanden - dann ist es natürlich besser, keine Objektorientierte Programmierung zu nutzen.
Diese Seiteneffekte sind unter Umständen ja gewollt. Ok vermutlich nicht für denjenigen, der den Code dann testen soll. Aber sinnvoll strukturiert, kann man auch beherrschbaren prozeduralen Code schreiben.

Möchte ich Funktionen und Daten zusammen in ein Objekt bringen, dann bedingt das ganze auch, dass die Daten vor Ort sind. So sind direkte Datenmanipulationen direkt auf der Datenbank bspw. ausgeschlossen.
Zudem, wann arbeitet eine Objektmethode auch nur auf den Daten, die sie selbst bereithält?
Es gibt einen Fall, wo das so ist: ADTs und einfachste Datentypen......... but that was it.

Folgende Probleme tauchen bei komplexeren Datenstrukturen auf.
Erstens möchte ich nicht lange Graphoperationen durchführen um an irgend ein Objekt/Datensatz im letzten Zweig des grossen Urwalds zu kommen und zweitens möchte ich vllt. auch gar keinen Graphen haben, sondern schön losgekoppelte Objekte/Daten.

Zum Thema "OOP als \"Datensammelsatz\"":
Nein ich sehe das nicht so. Aber genau weil ich das nicht so sehe, sehe ich OO als gescheitert an^^
Wann macht es deiner Meinung nach Sinn Funktionen an die Daten zu knüpfen.
Meiner Meinung nach - wie oben geschrieben - bei ADTS (Listen,.....) und einfachsten Datentypen (boolean, int) (also Datentypen, wo die Funktionen auch wirklich nur auf dem Datentyp arbeitet).

Wenn du über Objektorientierte Programmierung urteilen willst, sollte dir doch zumindest das Proxy-Pattern geläufig sein?
Ich kenne sogar alle GoF-Patterns und weitere + Architekturpatterns

Aber wie erreichst du denn bei deinem rein prozeduralem Programm zB die Auswahl zwischen unterschiedlichen Mail-Servern, die auf unterschiedliche Arten und Weisen benutzt werden müssen? Und wie kann man da dynamisch zur Laufzeit neue hinzufügen?
Die liegen alle vor: Als Datensatz direkt im Programmcode oder auch in der Datenbank.
Das witzige an deinem Codebeispiel ist eben, dass es meiner Meinung nach prozedural arbeitet.

Dein Mailserver (1Funktion -> 1Prozedur) ruft weitere Subprozeduren (-> loggger und sendMail).
Diese sind eben in weiteren Prozedurenklassen hinterlegt.
Ich sehe hier keinen Vorteil.
Welchen Vorteil siehst du darin?...... Das sind weitestgehend stateless Prozedurenklassen, die keiner Klasse bedürften.

Aber auch ein anderers Problem sehe ich hier. Ein Problem, das ich auch ganz generell in der prozeduralen Programmierung habe, wenn ich den Code auf verschiedene Funktionen aufteile, wenn auch nicht ganz so drastisch.

Gehen wir mal davon aus, zur Bestimmung des Log-Services, aber auch zur Bestimmung des Emailanbieters wird ein ähnlicher Code ausgeführt. Was ich meine ist das. Ein berechnetes Zwischenergebnis beim Finden des LogServices kann dazu hergenommen werden, den Emailanbieter zu bestimmen. Trenne ich das so strikt, wie du es gemacht hast, so kann ich ein temporär berechnetes Zwischenergebnis dafür nicht hernehmen. Und das kann ganz sicher kein Compiler wegoptimieren.

prozedural würde ich das dann so ähnlich machen. sendMail(mailData) -> calculate LogService(mailData)
aus dem LogService kommen jetzt zwei Datensäze zurück, einerseits der LogService an sich und zweitens irgend ein beschleunigendes Zwischenergebnis tmpData

Also rufe ich jetzt ne prozedur auf, die wie folgt heisst: ->calculate Emailanbieter (mailData, tmpData)
Im Falle dessen, dass mir tmpData nicht zur Verfügung steht, weil ich bspw. auf den LogService verzichtet habe, schreibe ich natürlich auch noch einfach ein calculateEmailanbieter(struct mailData) Prozedur

Wie würdest du das objektorientierisieren?
Und warum würdest du das überhaupt mit 5 veschiedenen Klassen machen, wenn es doch auch straightforward geht, in java also mit static functionen.

Und wie gehst du an die Sache heran, wenn du es Top-Bottom oder Bottom-Up aufbaust?
Wie sind deine Gedankengänge, das direkt von Anfang an richtig zu machen?



Inwiefern sind Daten auf dem Stack resourcenschonender und flexibler als auf dem Heap? (wobei Objektorientiert natürlich nicht voraussetzt, das Daten überhaupt auf dem Heap liegen, da vermischt du wieder eine Mögliche Implementierung mit einem Konzept)
Und wie bringst du überhaupt 1 Mio. Mails auf dem Stack unter?

Wo die Daten herkommen ist völlig egal, ich kann durchaus auch Objektorientiert mit denen arbeiten, wenn die in einer Relationen Datenbank liegen.
Wie passt deine Lösung eigentlich zu Objektorientierten Datenbanken?
Ja in C++ kann ich sie mir auch auf den Stack oder sonst wohin packen. War wirklich etwas falsch von mir formuliert.

Wenn die Daten in der Datenbank liegen, dann kann ich sie erstmal durch umständliche OR-Mapper oder händisch in meinen Speicher holen und dabei schön nen Graphen aufbauen und dann damit arbeiten -> das finde ich nicht ressourcenschonend.

Andererseits kann ich wieder meine prozeduren/Service-Klassen verwenden, die dann direkt mit der DatenbankAPI kommunizieren....... Das ist meiner Meinung nach sinnvoll, da ich mir hier vor allem schnell arbeitende Datenbanken zu Nutze machen kann.

Das, was ich zitiert hab?
Die Problemstellung war die, dass unter Umständen unnötige Abhängikeiten in der Doing/prozeduren/Service-Klasse habe, die für bestimmte Methodenaurufe irrelevant sind und somit störend!

Genauso, wie die eine "vernünftige und vor allem flexible und dynamisch einsetzbare" Prozedur aussieht: nicht existent.

Ein Domänen-Modell ist nur ein Modell für *eine* Domäne, wie soll sowas dynamisch und flexibel aussehen?
Das musste mir etwas näher erklären^^


Weder kommen Service-Klassen in einem Domänen-Model niemals vor, noch sind diese zwingend nötig. (Der Begriff "Service" ist btw. hauptsächlich durch Domain-Driven-Design geprägt.)

Ich habe keine Ahnung, was du unter einem "dynamischen Programm" verstehst, aber generell und nach deiner Definition von OO erreicht man das, indem Objekte sich Nachrichten schicken.

DI ist aber ein generelles Konzept, und hat nicht mit Services zu tun.
Es ist nichts weiter, als dass Objekte ihre Abhängigkeiten übergeben bekommen, anstatt sie sich selbst zu besorgen. Über die Abhängigkeiten sagt das nichts aus.
Nach Fowler sollten sie auch nicht vorkommen. Und ich sehe das genau wie Fowler, objektorientierung besteht nicht aus service-Klasssen. Der Einsatz dieser Klassen macht das ganze wieder prozedural mit oo-ästhetik. Nur dass es dann icht vielleicht sogar besser wäre auf diese Ästhetik zu verzichten und direkt prozedural (das schließt eine Modualisierung nicht aus!) zu programmieren.

Ja Objekte schicken sich Nachrichten, aber was steht deiner Meinung nach in diesen Narchrichten drinnen.
In deiner MailService Klasse wird ein MailObjekt/Struct geschickt........... prozeduren schicken sich auch derartige Nachrichten. Wie sollen sich die Nachrichten bei ordentlicher oo-programmierung unterscheiden?

Was ich vor allem als undynamisch ansehe ist das folgende:
Die festverdrahteten Objekte. Die Membervariablen, die fest verdrahtet auf andere Objekte zeigen.
Somit habe ich eine Möglichkeit um an diese Informationen zu kommen, aus der Sichtweise einer Prozedur oder Service-Klasse:

Ich muss mich mich im Graph entlang hangeln bis ich an entsprechender Information bin, die aber in sich dann ein anemic Objekt darstellt.

ein Datenbanksystem dagegen kann das viel effizienter und ich komme direkt an die Information.

Wenn ich jetzt aus den Anemic-Objekten richtige Objekte mache, sprich funktionalität dazuimplementiere, dann arbeitet die funktionalität auf THIS, also auf den Daten des Objekts selbst. Und das macht das ganze unflexibel, da ich ja nur auf den THIS Daten arbeiten darf/kann.
Also werden unabhängige Serviceklassen/prozeduren eingeführt um in die ganze Bude wieder etwas Schwung/dynamic zu bringen^^


Dein Argument ist: Sobald irgendwo eine Funktion existiert, ist das ganze Prozedural und absolut nicht mehr Objekt-Orientiert?

Wie würdest du das ganze denn, ohne jegliche Objekt-Orientierung, umsetzen? Ellenlange Methoden mit zig verschachtelten if's?
Divide-And-Conquer...... also viele Methoden/Funktionen.
Switch-Cases finde ich gar nicht so problematisch. Aber wenn es sein muss, kann man auch schön polymorphe Funktionspointer einsetzen^^
Zum Thema "wenn irgendwo eine Funktion existiert": Ja das ist ein Schritt weg von OO. Nicht gänzlich, aber kein reines OO mehr. Je mehr -> desto mehr pseudo-oo wird es^^

Man programmiert nicht OO, um OO zu programmieren, sondern weil es für die meisten Dinge gut Funktioniert. OO gibt es nicht ohne Grund.

Und das Methoden in Services Prozedural sind (ich gehe mal davon aus, dass das nach deiner Definition von Prozedural so ist), bedeutet für dich, dass sie nicht mehr Objektorientiert sind?
Warum und für was funktioniert es gut?

Ja es ist nicht mehr objektorientiert.
Und die Frage, die ich mir stelle, warum dann überhaupt Service-Klassen und nicht direkt Service-prozeduren.
Auch diese kann man konfigurieren.
Aber das ist nicht das Hauptthema.
Das Hauptthema ist die Frage, warum verwenden soviele Programmierer, wenn Sie meinen OO zu programmieren, ServiceKlassen.
Meiner Meinung nach, weil Sie das reine OO nicht akzeptieren und das somit ein klares Indiz, dass es gescheitert ist.


@AndiE

Gegen Datenmodellierung habe ich nichts.
Mich stört es nur Funktionalität und Daten zusammenzupacken.
Und anscheinend stört das nicht nur mich, sondern viele andere auch -> Lösung, wenn auch nur bedingt sinnvoll: Serviceklassen.
Das andere was du ansprichst, sind Architekturentscheidungen, die erstmal nichts mit OO zu tun haben.
ich kann auch MVC ohne Klassen und Objekte programmieren^^
Das ist nur ne Frage der Modularisierung/Strukturierens.

OO ist eine Frage des ProgrammierDenkens.

MVC lässt sich sowohl im oo-denken als auch im pp-denken umsetzen.

Und wenn du die einzelnen patterns aus mvc herausarbeiten möchtest, bspw. strategy und observer, dann geht das sehr gut mit prozeduralem Denken. Wie ich oben geschrieben habe, kannst du ja für die polymorphie (auf polymorphie bauen die ganzen DesignPatterns ja auf) Funktionspointer verwenden, oder auch switch-cases, die super schnell sind.

in C++ sollte man wo es nur geht auf vtables verzichten.


Ja gut soweit


lg knotenpunkt
 

mrBrown

Super-Moderator
Mitarbeiter
Es soll einen negativen Gesichtspunkt von OO hervorheben und gleichzeitig darauf hinweisen, dass Prozedurale Programmierung diese Schwäche nicht hat.
Du kannst also rein prozedural ein "flexibel und generisch einsetzbares Programmgerüst" schreiben?


Aber es fühlt sich halt einfach schlecht an, "schlechten" Code zu schreiben, auch wenn der Compiler das nacher wegoptimieren würde. Warum nicht gleich straightforward programmieren?
Für mich fühlt es sich besser an, klaren und verständlichen Code zu schreiben, anstatt hochoptimierten...

Es sieht vllt. ästhetisch aus
"ästhetisch aussehen" im Sinne von "klar, verständlich, strukturiert, etc" (was Lesbarkeit, Wartbarkeit und Testbarkeit impliziert) ist zumindest mir deutlich wichtiger als performant und Maschinennah...


Zudem, wann arbeitet eine Objektmethode auch nur auf den Daten, die sie selbst bereithält?
Es gibt einen Fall, wo das so ist: ADTs und einfachste Datentypen......... but that was it.
Nö, eigentlich ist das in den meisten vernünftigen, Domänenorientierten Entwürfen der Fall.


Erstens möchte ich nicht lange Graphoperationen durchführen um an irgend ein Objekt/Datensatz im letzten Zweig des grossen Urwalds zu kommen und zweitens möchte ich vllt. auch gar keinen Graphen haben, sondern schön losgekoppelte Objekte/Daten.
Beides kannst du mit vernünftigem Design verhindern.

Zum Thema "OOP als \"Datensammelsatz\"":
Nein ich sehe das nicht so. Aber genau weil ich das nicht so sehe, sehe ich OO als gescheitert an^^
Wann macht es deiner Meinung nach Sinn Funktionen an die Daten zu knüpfen.
Meiner Meinung nach - wie oben geschrieben - bei ADTS (Listen,.....) und einfachsten Datentypen (boolean, int) (also Datentypen, wo die Funktionen auch wirklich nur auf dem Datentyp arbeitet).
Viellicht solltest du dich mal tiefergehend mit Objekt-Orientiertem Design beschäftigen? Grundlektüre wäre da Domain Driven Design.

Die liegen alle vor: Als Datensatz direkt im Programmcode oder auch in der Datenbank.
Das witzige an deinem Codebeispiel ist eben, dass es meiner Meinung nach prozedural arbeitet.

Dein Mailserver (1Funktion -> 1Prozedur) ruft weitere Subprozeduren (-> loggger und sendMail).
Diese sind eben in weiteren Prozedurenklassen hinterlegt.
Ich sehe hier keinen Vorteil.
Welchen Vorteil siehst du darin?...... Das sind weitestgehend stateless Prozedurenklassen, die keiner Klasse bedürften.

Na gut, ich seh's ein: wenn man geschachtelte Objekte, die über Messages kommunizieren, als prozedural bezeichnet, programmieren alle prozedural.

Nur so als Frage: was ist für dich Objektorientiert, wenn es über Messages kommunizierende Objekte nicht sind?


Aber auch ein anderers Problem sehe ich hier. Ein Problem, das ich auch ganz generell in der prozeduralen Programmierung habe, wenn ich den Code auf verschiedene Funktionen aufteile, wenn auch nicht ganz so drastisch.

Gehen wir mal davon aus, zur Bestimmung des Log-Services, aber auch zur Bestimmung des Emailanbieters wird ein ähnlicher Code ausgeführt. Was ich meine ist das. Ein berechnetes Zwischenergebnis beim Finden des LogServices kann dazu hergenommen werden, den Emailanbieter zu bestimmen. Trenne ich das so strikt, wie du es gemacht hast, so kann ich ein temporär berechnetes Zwischenergebnis dafür nicht hernehmen. Und das kann ganz sicher kein Compiler wegoptimieren.

prozedural würde ich das dann so ähnlich machen. sendMail(mailData) -> calculate LogService(mailData)
aus dem LogService kommen jetzt zwei Datensäze zurück, einerseits der LogService an sich und zweitens irgend ein beschleunigendes Zwischenergebnis tmpData

Also rufe ich jetzt ne prozedur auf, die wie folgt heisst: ->calculate Emailanbieter (mailData, tmpData)
Im Falle dessen, dass mir tmpData nicht zur Verfügung steht, weil ich bspw. auf den LogService verzichtet habe, schreibe ich natürlich auch noch einfach ein calculateEmailanbieter(struct mailData) Prozedur

Wie würdest du das objektorientierisieren?
Und warum würdest du das überhaupt mit 5 veschiedenen Klassen machen, wenn es doch auch straightforward geht, in java also mit static functionen.

Und wie gehst du an die Sache heran, wenn du es Top-Bottom oder Bottom-Up aufbaust?
Wie sind deine Gedankengänge, das direkt von Anfang an richtig zu machen?
In dem Fall, gibt es eben ein Objekte, was als Cache für das Zwischenergebnis fungiert, oder die Mail selbst kann das zurückgeben und cacht es entsprechend, alles kein Hexenwerk...

gibt zig Varianten, ohne die genauen Anforderungen zu kennen, kann man da keine von Anfang an richtige Lösung liefern - genausowenig bei der Prozeduralen Variante.

Warum ich das mit 5 Klassen machen würde?
Weil 5 Klassen, die jeweils für genau ein Ding zuständig sind, deutlich einfacher sind, als 17 Prozeduren, die jeweils 32 Dinge tun.
Prozedural habe ich immer eine extreme Bindung von allen Dingen, was das ganze extrem starr und unflexibel macht. (Um mal auf Alan Key zurückzukommen: spätes Binden ist einer der wesentlichen Punkte von OO, wenn OO gescheitert ist, dürfe spätes Binden ja auch schlecht sein?)


Ja in C++ kann ich sie mir auch auf den Stack oder sonst wohin packen. War wirklich etwas falsch von mir formuliert.

Und wo packst du die zum Versenden nötigen Daten jetzt hin, damit sie weniger Platz als in der OO-Variante brauchen? Oder übernimmt dann die DB das verschicken, und deine Anwendung benutzt die Daten gar nicht? o_O

Wenn die Daten in der Datenbank liegen, dann kann ich sie erstmal durch umständliche OR-Mapper oder händisch in meinen Speicher holen und dabei schön nen Graphen aufbauen und dann damit arbeiten -> das finde ich nicht ressourcenschonend.

Andererseits kann ich wieder meine prozeduren/Service-Klassen verwenden, die dann direkt mit der DatenbankAPI kommunizieren....... Das ist meiner Meinung nach sinnvoll, da ich mir hier vor allem schnell arbeitende Datenbanken zu Nutze machen kann.
Du wirst immer Fälle finden, für die irgendwas ungeeignet ist...

Wenn ich keinerlei Objekte brauche und auch keinerlei Business-Logik in diesen habe, dann nutz ich halt auch keine Objekt-Orientierte Sprache.
Wenn du natürlich nicht zueinander passende Sprache und Anforderungen wählst, sind solche Probleme klar...

Das musste mir etwas näher erklären^^
Du forderst: "vernünftiges und vor allem flexibles und dynamisch einsetzbares domain-model"

Definier doch mal bitte, was deiner Meinung nach ein eben dieses ist?

Ein domain-model ist für eine spezifische Problemdomäne da - was muss da für dich flexibles und dynamisch sein?

Nach Fowler sollten sie auch nicht vorkommen. Und ich sehe das genau wie Fowler, objektorientierung besteht nicht aus service-Klasssen. Der Einsatz dieser Klassen macht das ganze wieder prozedural mit oo-ästhetik. Nur dass es dann icht vielleicht sogar besser wäre auf diese Ästhetik zu verzichten und direkt prozedural (das schließt eine Modualisierung nicht aus!) zu programmieren.

Ein Service-Layer, welches die gesamte Business-Logik enthält, hat nichts mit OO zu tun - und eben das kritisiert Fowler!
Ein Service, ohne "-Layer", nach DDD, gehört durchaus zur Problemdomäne, auch bei Fowler. Leseempfehlung (von Fowler): Domain Driven Design.

Aus "ein kleiner Teil des Objekt-Orientierten Designs ist prozedural" ist für mich auch noch kein "besser ganz auf OO verzichten"...

Ja Objekte schicken sich Nachrichten, aber was steht deiner Meinung nach in diesen Narchrichten drinnen.
In deiner MailService Klasse wird ein MailObjekt/Struct geschickt........... prozeduren schicken sich auch derartige Nachrichten. Wie sollen sich die Nachrichten bei ordentlicher oo-programmierung unterscheiden?

Um mich selbst zu zitieren: "wenn man geschachtelte Objekte, die über Messages kommunizieren, als prozedural bezeichnet, programmieren alle prozedural."

Wenn für dich OO == Prozedural ist, warum führen wir dieses Gespräch eigentlich?

Was ich vor allem als undynamisch ansehe ist das folgende:
Die festverdrahteten Objekte. Die Membervariablen, die fest verdrahtet auf andere Objekte zeigen.

Ich muss mich mich im Graph entlang hangeln bis ich an entsprechender Information bin, die aber in sich dann ein anemic Objekt darstellt.

Gute Erkenntnis: schlecht umgesetztes OO ist schlecht. Herzlichen Glückwunsch dazu.

Sind die Argumente eigentlich bewusst nicht für sinnvolles OO zutreffend, sondern nur für Objekte als Datencontainer, die man bei Prozeduraler Programmierung üblicherweise nutzt?
*Dabei* musst du dich durch die Daten hangeln - mit OO dagegen kannst du es anders lösen (und machst das bei vernünftigem OO auch).


Wenn ich jetzt aus den Anemic-Objekten richtige Objekte mache, sprich funktionalität dazuimplementiere, dann arbeitet die funktionalität auf THIS, also auf den Daten des Objekts selbst. Und das macht das ganze unflexibel, da ich ja nur auf den THIS Daten arbeiten darf/kann.
Also werden unabhängige Serviceklassen/prozeduren eingeführt um in die ganze Bude wieder etwas Schwung/dynamic zu bringen^^
Und eine Prozedur kann nur auf den ihr übergebenen Daten arbeiten, ist Prozedual jetzt etwa noch stärker eingeschränkt?

Das Objekte mit sich selbst arbeiten, habe ich bisher weder als Einschränkung noch als Unflexibel empfunden...
Ganz im Gegenteil, eine Einschränkung ist doch völlig nötig. Wie soll denn ein Entwurf, bei dem alles Zugriff auf alles hat, in irgendwer Art und Weise überschaubar bleiben?

Das Hauptthema ist die Frage, warum verwenden soviele Programmierer, wenn Sie meinen OO zu programmieren, ServiceKlassen.
Meiner Meinung nach, weil Sie das reine OO nicht akzeptieren und das somit ein klares Indiz, dass es gescheitert ist.

Deine Meinung, von der dich auch niemand abbringen können wird.

Meine Meinung: Prozedural ist gescheitert, weil die meisten Leute OO oder Funktional programmieren.
 

mrBrown

Super-Moderator
Mitarbeiter
Es soll einen negativen Gesichtspunkt von OO hervorheben und gleichzeitig darauf hinweisen, dass Prozedurale Programmierung diese Schwäche nicht hat.
Du kannst also rein prozedural ein "flexibel und generisch einsetzbares Programmgerüst" schreiben?


Aber es fühlt sich halt einfach schlecht an, "schlechten" Code zu schreiben, auch wenn der Compiler das nacher wegoptimieren würde. Warum nicht gleich straightforward programmieren?
Für mich fühlt es sich besser an, klaren und verständlichen Code zu schreiben, anstatt hochoptimierten...

Es sieht vllt. ästhetisch aus
"ästhetisch aussehen" im Sinne von "klar, verständlich, strukturiert, etc" (was Lesbarkeit, Wartbarkeit und Testbarkeit impliziert) ist zumindest mir deutlich wichtiger als performant und Maschinennah...


Zudem, wann arbeitet eine Objektmethode auch nur auf den Daten, die sie selbst bereithält?
Es gibt einen Fall, wo das so ist: ADTs und einfachste Datentypen......... but that was it.
Nö, eigentlich ist das in den meisten vernünftigen, Domänenorientierten Entwürfen der Fall.


Erstens möchte ich nicht lange Graphoperationen durchführen um an irgend ein Objekt/Datensatz im letzten Zweig des grossen Urwalds zu kommen und zweitens möchte ich vllt. auch gar keinen Graphen haben, sondern schön losgekoppelte Objekte/Daten.
Beides kannst du mit vernünftigem Design verhindern.

Zum Thema "OOP als \"Datensammelsatz\"":
Nein ich sehe das nicht so. Aber genau weil ich das nicht so sehe, sehe ich OO als gescheitert an^^
Wann macht es deiner Meinung nach Sinn Funktionen an die Daten zu knüpfen.
Meiner Meinung nach - wie oben geschrieben - bei ADTS (Listen,.....) und einfachsten Datentypen (boolean, int) (also Datentypen, wo die Funktionen auch wirklich nur auf dem Datentyp arbeitet).
Viellicht solltest du dich mal tiefergehend mit Objekt-Orientiertem Design beschäftigen? Grundlektüre wäre da Domain Driven Design.

Die liegen alle vor: Als Datensatz direkt im Programmcode oder auch in der Datenbank.
Das witzige an deinem Codebeispiel ist eben, dass es meiner Meinung nach prozedural arbeitet.

Dein Mailserver (1Funktion -> 1Prozedur) ruft weitere Subprozeduren (-> loggger und sendMail).
Diese sind eben in weiteren Prozedurenklassen hinterlegt.
Ich sehe hier keinen Vorteil.
Welchen Vorteil siehst du darin?...... Das sind weitestgehend stateless Prozedurenklassen, die keiner Klasse bedürften.

Na gut, ich seh's ein: wenn man geschachtelte Objekte, die über Messages kommunizieren, als prozedural bezeichnet, programmieren alle prozedural.

Nur so als Frage: was ist für dich Objektorientiert, wenn es über Messages kommunizierende Objekte nicht sind?


Aber auch ein anderers Problem sehe ich hier. Ein Problem, das ich auch ganz generell in der prozeduralen Programmierung habe, wenn ich den Code auf verschiedene Funktionen aufteile, wenn auch nicht ganz so drastisch.

Gehen wir mal davon aus, zur Bestimmung des Log-Services, aber auch zur Bestimmung des Emailanbieters wird ein ähnlicher Code ausgeführt. Was ich meine ist das. Ein berechnetes Zwischenergebnis beim Finden des LogServices kann dazu hergenommen werden, den Emailanbieter zu bestimmen. Trenne ich das so strikt, wie du es gemacht hast, so kann ich ein temporär berechnetes Zwischenergebnis dafür nicht hernehmen. Und das kann ganz sicher kein Compiler wegoptimieren.

prozedural würde ich das dann so ähnlich machen. sendMail(mailData) -> calculate LogService(mailData)
aus dem LogService kommen jetzt zwei Datensäze zurück, einerseits der LogService an sich und zweitens irgend ein beschleunigendes Zwischenergebnis tmpData

Also rufe ich jetzt ne prozedur auf, die wie folgt heisst: ->calculate Emailanbieter (mailData, tmpData)
Im Falle dessen, dass mir tmpData nicht zur Verfügung steht, weil ich bspw. auf den LogService verzichtet habe, schreibe ich natürlich auch noch einfach ein calculateEmailanbieter(struct mailData) Prozedur

Wie würdest du das objektorientierisieren?
Und warum würdest du das überhaupt mit 5 veschiedenen Klassen machen, wenn es doch auch straightforward geht, in java also mit static functionen.

Und wie gehst du an die Sache heran, wenn du es Top-Bottom oder Bottom-Up aufbaust?
Wie sind deine Gedankengänge, das direkt von Anfang an richtig zu machen?
In dem Fall, gibt es eben ein Objekte, was als Cache für das Zwischenergebnis fungiert, oder die Mail selbst kann das zurückgeben und cacht es entsprechend, alles kein Hexenwerk...

gibt zig Varianten, ohne die genauen Anforderungen zu kennen, kann man da keine von Anfang an richtige Lösung liefern - genausowenig bei der Prozeduralen Variante.

Warum ich das mit 5 Klassen machen würde?
Weil 5 Klassen, die jeweils für genau ein Ding zuständig sind, deutlich einfacher sind, als 17 Prozeduren, die jeweils 32 Dinge tun.
Prozedural habe ich immer eine extreme Bindung von allen Dingen, was das ganze extrem starr und unflexibel macht. (Um mal auf Alan Key zurückzukommen: spätes Binden ist einer der wesentlichen Punkte von OO, wenn OO gescheitert ist, dürfe spätes Binden ja auch schlecht sein?)


Ja in C++ kann ich sie mir auch auf den Stack oder sonst wohin packen. War wirklich etwas falsch von mir formuliert.

Und wo packst du die zum Versenden nötigen Daten jetzt hin, damit sie weniger Platz als in der OO-Variante brauchen? Oder übernimmt dann die DB das verschicken, und deine Anwendung benutzt die Daten gar nicht? o_O

Wenn die Daten in der Datenbank liegen, dann kann ich sie erstmal durch umständliche OR-Mapper oder händisch in meinen Speicher holen und dabei schön nen Graphen aufbauen und dann damit arbeiten -> das finde ich nicht ressourcenschonend.

Andererseits kann ich wieder meine prozeduren/Service-Klassen verwenden, die dann direkt mit der DatenbankAPI kommunizieren....... Das ist meiner Meinung nach sinnvoll, da ich mir hier vor allem schnell arbeitende Datenbanken zu Nutze machen kann.
Du wirst immer Fälle finden, für die irgendwas ungeeignet ist...

Wenn ich keinerlei Objekte brauche und auch keinerlei Business-Logik in diesen habe, dann nutz ich halt auch keine Objekt-Orientierte Sprache.
Wenn du natürlich nicht zueinander passende Sprache und Anforderungen wählst, sind solche Probleme klar...

Das musste mir etwas näher erklären^^
Du forderst: "vernünftiges und vor allem flexibles und dynamisch einsetzbares domain-model"

Definier doch mal bitte, was deiner Meinung nach ein eben dieses ist?

Ein domain-model ist für eine spezifische Problemdomäne da - was muss da für dich flexibles und dynamisch sein?

Nach Fowler sollten sie auch nicht vorkommen. Und ich sehe das genau wie Fowler, objektorientierung besteht nicht aus service-Klasssen. Der Einsatz dieser Klassen macht das ganze wieder prozedural mit oo-ästhetik. Nur dass es dann icht vielleicht sogar besser wäre auf diese Ästhetik zu verzichten und direkt prozedural (das schließt eine Modualisierung nicht aus!) zu programmieren.

Ein Service-Layer, welches die gesamte Business-Logik enthält, hat nichts mit OO zu tun - und eben das kritisiert Fowler!
Ein Service, ohne "-Layer", nach DDD, ist aber eben nicht zu ersetzen und gehört durchaus zur Problemdomäne. Leseempfehlung (von Fowler): Domain Driven Design.

Aus "ein kleiner Teil des Objekt-Orientierten Designs ist prozedural" ist für mich auch noch kein "besser ganz auf OO verzichten"...

Ja Objekte schicken sich Nachrichten, aber was steht deiner Meinung nach in diesen Narchrichten drinnen.
In deiner MailService Klasse wird ein MailObjekt/Struct geschickt........... prozeduren schicken sich auch derartige Nachrichten. Wie sollen sich die Nachrichten bei ordentlicher oo-programmierung unterscheiden?

Um mich selbst zu zitieren: "wenn man geschachtelte Objekte, die über Messages kommunizieren, als prozedural bezeichnet, programmieren alle prozedural."

Wenn für dich OO == Prozedural ist, warum führen wir dieses Gespräch eigentlich?

Was ich vor allem als undynamisch ansehe ist das folgende:
Die festverdrahteten Objekte. Die Membervariablen, die fest verdrahtet auf andere Objekte zeigen.

Ich muss mich mich im Graph entlang hangeln bis ich an entsprechender Information bin, die aber in sich dann ein anemic Objekt darstellt.

Gute Erkenntnis: schlecht umgesetztes OO ist schlecht. Herzlichen Glückwunsch dazu.

Sind die Argumente eigentlich bewusst nicht für sinnvolles OO zutreffend, sondern nur für Objekte als Datencontainer, die man bei Prozeduraler Programmierung üblicherweise nutzt?
*Dabei* musst du dich durch die Daten hangeln - mit OO dagegen kannst du es anders lösen (und machst das bei vernünftigem OO auch).


Wenn ich jetzt aus den Anemic-Objekten richtige Objekte mache, sprich funktionalität dazuimplementiere, dann arbeitet die funktionalität auf THIS, also auf den Daten des Objekts selbst. Und das macht das ganze unflexibel, da ich ja nur auf den THIS Daten arbeiten darf/kann.
Also werden unabhängige Serviceklassen/prozeduren eingeführt um in die ganze Bude wieder etwas Schwung/dynamic zu bringen^^
Und eine Prozedur kann nur auf den ihr übergebenen Daten arbeiten, ist Prozedual jetzt etwa noch stärker eingeschränkt?

Das Objekte mit sich selbst arbeiten, habe ich bisher weder als Einschränkung noch als Unflexibel empfunden...
Ganz im Gegenteil, eine Einschränkung ist doch völlig nötig. Wie soll denn ein Entwurf, bei dem alles Zugriff auf alles hat, in irgendwer Art und Weise überschaubar bleiben?

Das Hauptthema ist die Frage, warum verwenden soviele Programmierer, wenn Sie meinen OO zu programmieren, ServiceKlassen.
Meiner Meinung nach, weil Sie das reine OO nicht akzeptieren und das somit ein klares Indiz, dass es gescheitert ist.

Deine Meinung, von der dich auch niemand abbringen können wird.

Meine Meinung: Prozedural ist gescheitert, weil die meisten Leute OO oder Funktional programmieren.
 

mrBrown

Super-Moderator
Mitarbeiter
Nur mal so als Beispielproblemstellung:

Es soll Konten geben, die einen Kontostand haben (Ganzzahlig reicht). Man kann den Kontostand erhöhen und verringern (um ganzzahlige Werte), er darf aber nie negativ sein.
Wie bildest du das Prozedural ab?
 

knotenpunkt

Mitglied
Du kannst also rein prozedural ein "flexibel und generisch einsetzbares Programmgerüst" schreiben?
Ich denke du stimmst mir zu, wenn ich bspw. auf alle Daten von überall zugreifen kann, dass ich da sehr flexibel und dynamisch unterwegs bin. Klar, macht das den Code schwerer testbar, aber bei ordentlicher Strukturierung ist man auch hier sicher gut testbar und mit wenig Code unterwegs.

Für mich fühlt es sich besser an, klaren und verständlichen Code zu schreiben, anstatt hochoptimierten...
"ästhetisch aussehen" im Sinne von "klar, verständlich, strukturiert, etc" (was Lesbarkeit, Wartbarkeit und Testbarkeit impliziert) ist zumindest mir deutlich wichtiger als performant und Maschinennah...
Ich glaube das ist einfach eine subjektive Haltung dazu.
Aber, auch gut strukturierter Prozeduraler Code ist sehr gut lesbar, wartbar und testbar.
Warum nicht performance mit dazu nehmen, wenn man auf oo verzichtet?
Und ich glaube, vor allem weniger Code ist viel eher wartbar und erweiterbar als überdimensionierter oo-code?!

Na gut, ich seh's ein: wenn man geschachtelte Objekte, die über Messages kommunizieren, als prozedural bezeichnet, programmieren alle prozedural.

Nur so als Frage: was ist für dich Objektorientiert, wenn es über Messages kommunizierende Objekte nicht sind?
Das ist ein Teil was OO ausmacht.
Ein weiterers Teil wäre Verhalten wäre: Verhalten und Daten zu einem Paket zusammengeschürt (und damit habe ich meine Probleme)


In dem Fall, gibt es eben ein Objekte, was als Cache für das Zwischenergebnis fungiert, oder die Mail selbst kann das zurückgeben und cacht es entsprechend, alles kein Hexenwerk...

gibt zig Varianten, ohne die genauen Anforderungen zu kennen, kann man da keine von Anfang an richtige Lösung liefern - genausowenig bei der Prozeduralen Variante.

Warum ich das mit 5 Klassen machen würde?
Weil 5 Klassen, die jeweils für genau ein Ding zuständig sind, deutlich einfacher sind, als 17 Prozeduren, die jeweils 32 Dinge tun.
Prozedural habe ich immer eine extreme Bindung von allen Dingen, was das ganze extrem starr und unflexibel macht. (Um mal auf Alan Key zurückzukommen: spätes Binden ist einer der wesentlichen Punkte von OO, wenn OO gescheitert ist, dürfe spätes Binden ja auch schlecht sein?)
So programmiere ich viel Code, also 5 Klassen um einen UseCase abzudecken. Das ganze ist dann zudem schlecht erweiterbar.
Nein ich habe für die gleiche Anwendung dann keine 17 Prozeduren.
Und möchte ich hier etwas erweitern, reichen schon 1-2 weitere Prozeduren aus.
Ich code somit zielorientiert und nicht overheadorintiert

Und wo packst du die zum Versenden nötigen Daten jetzt hin, damit sie weniger Platz als in der OO-Variante brauchen? Oder übernimmt dann die DB das verschicken, und deine Anwendung benutzt die Daten gar nicht? o_O
Die ein oder andere Datenbank kann EXEC aufrufe^^
in der prozeduralen Anwendung lade ich mir EXAKT die Daten, die ich brauche.
BSP, ein zielgerichtete SELECT anweisung mit diversen JOINS
OO und relational passen nicht zusammen.
In der OO müsste ich ganze Tabellen laden, diese zu Graphen zusammenbauen. Meinetwegen via lazy-loading Daten nachladen etc. pp und anschließend traversieren.

Du forderst: "vernünftiges und vor allem flexibles und dynamisch einsetzbares domain-model"

Definier doch mal bitte, was deiner Meinung nach ein eben dieses ist?

Ein domain-model ist für eine spezifische Problemdomäne da - was muss da für dich flexibles und dynamisch sein?
ich definiere mir meine Datenstrukturen. Wenn ich eine Datenbank verwende also die Datenentitäten mit entsprechenden Schlüsseln.

So und jetzt je nach UseCase, den ich brauche, picke ich mir entsprechende zusammenaggregierte Daten aus der Datenbank heraus und arbeite damit. Dies kann von einer Prozedur/Service-Klasse oder auch mehreren erledigt werden. Verwende ich mehrere Prozeduren, baue ich das ganze somit Modular auf.

ein BSP:

ein User kauft sich auf meiner Webseite irgendwas:
Die Hauptprozedur fragt deligierend dann erstmal bei meiner Warenwirtschaftsprozedur nach ob Artikel verfügbar.
Wenn ja dann ruft diese die Rechnungsprozedur, Versandprozedur, Loggingproezdur, Warenwirtschaftsprozedur2 auf und erledigt damit den UseCase. Diese Proezduren haben unter Umständen selbst weitere Unterprozeduren.
Die Daten aus der Datenbank, anderweitigen API-Quellen holen sich diese Proezeduren selbstständig.
Im Falle dessen dass ich jetzt nur eine Datenbankanbindung habe und nicht auf externe APIs setze kann ich somit auch noch ganz toll das ganze transactional umsetzen..... also falls irgendwo was schief gehen sollte.

Somit arbeite ich zwar bis zu einem gewissen Grad global auf den Daten, aber durch die modulare Trennung von Rechnungsprozeduren, Warenwirtschaf............. schränke ich den globalen Spielraum wieder etwas ein.
Somit bin ich hochflexibel und auch nicht an irgendwelche Objektgraphen gebunden.


Ein Service-Layer, welches die gesamte Business-Logik enthält, hat nichts mit OO zu tun - und eben das kritisiert Fowler!
Ein Service, ohne "-Layer", nach DDD, ist aber eben nicht zu ersetzen und gehört durchaus zur Problemdomäne. Leseempfehlung (von Fowler): Domain Driven Design.

Aus "ein kleiner Teil des Objekt-Orientierten Designs ist prozedural" ist für mich auch noch kein "besser ganz auf OO verzichten"...
Warum lässt sich dann eine Anwendung nicht 100%ig oo bauen?
In welchen Fällen verwendest du selbst Services, in welchen Fällen nicht?
Wie entscheidest du das von Anfang an. Vor allem wenn du dein System gut erweiterbar aufbauen möchtest.


Gute Erkenntnis: schlecht umgesetztes OO ist schlecht. Herzlichen Glückwunsch dazu.

Sind die Argumente eigentlich bewusst nicht für sinnvolles OO zutreffend, sondern nur für Objekte als Datencontainer, die man bei Prozeduraler Programmierung üblicherweise nutzt?
*Dabei* musst du dich durch die Daten hangeln - mit OO dagegen kannst du es anders lösen (und machst das bei vernünftigem OO auch).
Könntest du hierzu mal ein Beispiel posten, wie du das bei vernünftigen OO lösen würdest?
Ganz allgemein würde mich interessieren, wie du schlechtes umgesetztes OO von gutem OO abgrenzt




Und eine Prozedur kann nur auf den ihr übergebenen Daten arbeiten, ist Prozedual jetzt etwa noch stärker eingeschränkt?

Das Objekte mit sich selbst arbeiten, habe ich bisher weder als Einschränkung noch als Unflexibel empfunden...
Ganz im Gegenteil, eine Einschränkung ist doch völlig nötig. Wie soll denn ein Entwurf, bei dem alles Zugriff auf alles hat, in irgendwer Art und Weise überschaubar bleiben?

Ein OO-Konstrukt sollte meiner Meinung nach etwas langlebigeres sein. Eine Prozedur arbeitet ja nur kurz einmal mit ihren übergebenen Daten.
Methoden in diesem langlebigen Konstrukt, sind eingeschränkt, da sie nur mit der starren langlebigen Situation arbeiten sollten/dürfen. kurzlebige zusammenaggregierte Daten in einer Prozedur kann ich immer wieder frisch/neu zusammensetzen. Auch in Abhängigkeit des Algorithmuses. Also schön flexibel und dynamisch.
Also ein weiterer Punkt PRO Prozedur bzw. PRO Daten und Verhalten gehören nicht zusammen, nicht auf langlebige Sicht.

Zu zweiterem: Das setzt aber eine festverdrahtete in sich geschlossene Objektlandschaft vorraus.
Ein Entwurf, der nicht auf alle Daten zugriff hat, wird unflexibel. Wie greifst du bspw. vom MotorradObjekt auf das Sonnenobjekt zu und zwar exakt auf die Sonneninnentemperatur, sodass dein MotorradObjekt irgendwas machen kann, sollte dieser Anwendungsfall von Anfang an noch nicht vorgehsehen sein.
Um diesen Datenzugriff zu ermöglichen, musst du jetzt einiges erweitern.
Eventuell private Daten der Sonne freigeben, seis über ein getSonnenInnenTemperatur()...... Zwischenobjekte, also sprich Objekte die im Objektgraphen zwischen Motorrad und Sonne liegen, müssen diese Information irgendwie weitergeben, etc. pp
Das ist doch kein sinnvoller Entwurf mehr?!

Ausserdem stellt sich dann auch noch die Frage, sollte für die MotorradMotrensteuerung nicht nur die SonnenInnenTemperatur von Interesse sein, sondern allerlei anderes auch noch, gehört das fachlich dann überhaupt in eine Objektmethode des Motorrads/Motorradsmotors? Oder ist hier nicht sogar ein Service sinnvoller?


Nur mal so als Beispielproblemstellung:

Es soll Konten geben, die einen Kontostand haben (Ganzzahlig reicht). Man kann den Kontostand erhöhen und verringern (um ganzzahlige Werte), er darf aber nie negativ sein.
Wie bildest du das Prozedural ab?
Er darf nie negativ werden, wenn ein KontoInhaber diese Transaction macht.
Ein Admin darf das Konte des Users aber auch gerne mal ins Negative abrutschen lassen.
Das Verhalten gehört meiner Meinung nach nicht zum Konto, sondern zum Anwendungsfall
Ein klares Indiz dafür, dass Daten und Verhalten getrennt werden müssen.

Oder das Konto darf nicht negativ werden, wenn es 7 Uhr abends ist, der User eine negativen Schufaeintrag hat und wenn der Milchbauer vom Nebenort mindestens 70% seiner Photovoltaikanlage in den letzten 2Wochen als Eigenverbrauch verwendet hat.


Vllt. aber möchte ich diesen UseCase nicht von Anfang an haben, sondern wirklich erstmal Konto darf nicht negativ werden. Ich möchte aber trotzdem die Möglichkeit haben, es zu meinem erweiterten UseCase hingehend zu erweitern.
Wie würdest du das machen?
Auch unter dem Gesichtspunkt betrachtet, dass ein nerviges Entlanghageln des Objektgraphen nicht erwünscht ist. Wie komme ich zu den Informationen 7Uhr, 70%, Nachbar....... Ohne dass ich mich in einem komplexen Objektgraphen entlanghangeln muss?


*****************************************************************
Das ist eben der gängige Weg, objektorientiertes Design umzusetzen, wenn man nur eine prozedurale Sprache hat. Ist wohl eher ein Argument pro OO.
Nein: OO != Polymorphie
OO == Verhalten und Daten zusammengekapselt
OO == Austausch via Messages
und noch ein paar Kriterien

Zum Thema polymorphie ist mir gerade noch ein weiteres OO Designproblem aufgefallen.
Man nehme bspw. ein Dreieck (Klasse Dreieck)
In der Schule wird einem gelehrt, dass bspw das Dreick am besten selbst weiß wie es sich zeichnen soll


Die Polymorphie besteht in dem Fall darin, dass sich bei aufruf der draw()-Methode das geometrische Unterobjekt Dreieck anders selbst zeichnen wird, wie die draw()-Methode eines Vierecks.

Problem: Wo soll sich das Dreieck hinzeichnen?: Auf ein Canvaselement bspw!
Ist das aber wirklich die Aufgabe des Dreiecks, sich auf das Canvasobjekt zu zeichnen?
Meiner Meinung nach gehört diese Funktionaltät nicht in die Dreiecksklasse.
Sie gehört meiner Meinung nach in ein Modul, das für Zeichnen zuständig ist.

Möchte ich das Dreieck wiederverwenden, in einem System, in dem rein gar nichts gezeichnet wird, ist ein Dreieck ohne derartiges Verhalten definitiv besser aufgehoben.
Und das macht die Proezudurale programmierung auch so schön wiederverwendbar.
Ich schmeisse entsprechende Prozeduren raus, wenn ich sie woanders nicht brauche, oder ich nehme welche dazu und erweitere das system so.



@mrBrown

Was mir immer noch nicht ganz klar ist:

ich habe eine KlassenGraphStruktur, die wie folgt aussehen könnte.

Code:
class X
{

K übergerordnetes Objekt; //also sprich hier ist eine Referenz drinnnen von dem Objekt, das X enthält

T memVariable1 -> Y memVariable2 aus T -> memVariable3 aus Y
usw

methode1();
methode2();
methode3();

}
//ja klasse und klasseninstanz etwas vermischt, aber ich denke es wird klar, was ich meine

1 Frage ) wie greife ich von methode1 auf irgendwas von memVariable3 zu?
Eventuell: ich hangle mich im Objektgraphen entlang (aber du meintest, das wäre nicht sinnvoll)

2 Frage )
das ganze ist doch dann auch wieder global zu sehen, weil sich methode1 sich die Daten selbst holt
Ich habe lediglich den Overhead des Graphenentlanghangelns drinnen.

3 Frage )
greift methode2 auf K zu, bzw. ganz allgemein die Existenz von K in X macht meinen Objektgraphen zyklisch
Ist das sinnvoll in OO?

4 Frage )
Nehmen wir an X ist ein Konto:
Es soll in dem Konto ein Betrag/Variable geändert werden.
Es wird dazu memVariable3 aus Y benötigt, ABER auch in dem Objektgraphen nicht vorhanden, ein anderes Konto X...... das ich aber zur Compilezeit noch nicht kenne, sondern erst dynamisch ermitteln muss.
Ganz generell, dieser Objektgraph wie oben angegeben ist ein ein starres Gebilde........ und mindestens aber Teil einer längerlebigen Struktur.

Für viele UseCases brauche ich nur kurzlebige strukturen...... und da finde ich es quatsch einen Objektgraphen aufzubauen, DO_METHODE() ausführen...... Objektgraphen dem Destruktur/GC zuzuführen.

Meine Idee wäre hier: Ich brauche mal wieder eine Prozedur, die das übergeordnet erledigt.
Also habe ich wieder eine Service-Klasse, die aber kein reines OO darstellt.
Wie würdest du das in reines OO umwandeln?

5 Frage )
Viele Use-Cases wollen keine starre Strukur, also kein vorgegebenen Objektgraphen, sondern entscheiden erst zur Laufzeit, welche Daten sie genau benötigen.
Wie würdest du das OO umsetzen?



Noch eine ganz allgemeine Frage:
Wenn du deine Software iterativ aufbaust, am Anfang noch nicht genau weißt wie du was du haben möchtest, wie programmierst du da, bzw. wie gehst du da vor, wie ist dein Buildprozess.
Das Problem sieht man ja am Konto mit dem negativen Kontostand. Wenn ich ein USeCase habe, den aber erst im laufe meines programmierens erarbeite, wie gehst du da vor?
Aufwändige Refactorings sollten ausgeschlossen sein, da diese Vorgehensweise (der iterative, top-bottom.... Aufbau) zur Programmiermethodik gehören soll.
Ich möchte eine Software so aufbauen, dass ich sie immer gut und schmerzfrei erweitern kann, das sehe ich irgendwie bei der OO nicht gegeben, bei PP dagegen schon.


lg knotenpunkt
 
Zuletzt bearbeitet von einem Moderator:

mrBrown

Super-Moderator
Mitarbeiter
Ich denke du stimmst mir zu, wenn ich bspw. auf alle Daten von überall zugreifen kann, dass ich da sehr flexibel und dynamisch unterwegs bin. Klar, macht das den Code schwerer testbar, aber bei ordentlicher Strukturierung ist man auch hier sicher gut testbar und mit wenig Code unterwegs.
Nein, wenn von überall alles verändert werden kann, hast du einen großen Haufen Scheiße produziert.

Daran ist überhaupt nichts wartbar, strukturiert oder testbar.

Ich glaube das ist einfach eine subjektive Haltung dazu.
Aber, auch gut strukturierter Prozeduraler Code ist sehr gut lesbar, wartbar und testbar.
Warum nicht performance mit dazu nehmen, wenn man auf oo verzichtet?
Und ich glaube, vor allem weniger Code ist viel eher wartbar und erweiterbar als überdimensionierter oo-code?!
gut strukturierter Code ist lesbar, wartbar und testbar.
Gut strukturierten Code (mit klaren Zuständigkeiten, Abhängigkeiten etc) erreicht man häufig am besten mit OO.

Weniger Code produziert man mit Prozeduralem Code nicht zwingend - oft sogar ganz im Gegenteil.


So programmiere ich viel Code, also 5 Klassen um einen UseCase abzudecken. Das ganze ist dann zudem schlecht erweiterbar.
Nein ich habe für die gleiche Anwendung dann keine 17 Prozeduren.
Und möchte ich hier etwas erweitern, reichen schon 1-2 weitere Prozeduren aus.
Ich code somit zielorientiert und nicht overheadorintiert

Ich habe 5 Klassen, wenn es 5 *verschiedene* Zuständigkeiten gibt. Daran ist dann nichts schlecht erweiterbar.

Um aber mal deine Argumentation zu benutzen:

Ich schreibe als 5 Prozeduren, um die 5 Aspekte eines UseCases abzudecken. Das ganze ist dann schlecht erweiterbar.
Möchte ich in der OO-Variante etwas erweitern, reicht schon eine entsprechende Klasse.
Ich code somit zielorientiert und nicht overheadorintiert

ich definiere mir meine Datenstrukturen. Wenn ich eine Datenbank verwende also die Datenentitäten mit entsprechenden Schlüsseln.

So und jetzt je nach UseCase, den ich brauche, picke ich mir entsprechende zusammenaggregierte Daten aus der Datenbank heraus und arbeite damit. Dies kann von einer Prozedur/Service-Klasse oder auch mehreren erledigt werden. Verwende ich mehrere Prozeduren, baue ich das ganze somit Modular auf.
Und wo ist jetzt der relevante Unterschied, der das ganze bei OO weniger flexibel macht?

Ich definiere meine Klassen, die sich *aus* den Use-Cases ergeben. Mit diesen arbeite ich dann.

Kommen *andere* Use-Cases dazu, muss man in beiden Fällen was anpassen.

Warum lässt sich dann eine Anwendung nicht 100%ig oo bauen?
In welchen Fällen verwendest du selbst Services, in welchen Fällen nicht?
Wie entscheidest du das von Anfang an. Vor allem wenn du dein System gut erweiterbar aufbauen möchtest.
Ich verwende Services in den Fällen, in denen es sinnvoll ist.
Ich entschiede das, indem ich nicht einfach wild drauf los programmiere, sondern erstmal vernünftig modelliere.

Einen vernünftigen Entwurf brauchst du *immer*, das löst sich nicht durch die Wahl des Programmier-paradigmas.

Könntest du hierzu mal ein Beispiel posten, wie du das bei vernünftigen OO lösen würdest?
Ganz allgemein würde mich interessieren, wie du schlechtes umgesetztes OO von gutem OO abgrenzt
Ähm, wie man"durchhangeln" durch Objektgraphen vermeidet? Information-Hiding!
Die nutzende Klasse gehen die Interna der genutzen nichts an.
Aber wir vermeidest du denn prozedural das durchhangeln durch Datenstrukturen, zb um ein bestimmtes Kind eines Baumes zu finden?

Zu "gutes OO": du redest doch die ganze Zeit von Fowler, lies doch einfach mal irgendwas von ihm? Eine zusätzliche Leseempfehlung habe ich hier auch schon mehrfach geliefert, die können das wesentlich besser erklären als ich...

Ein OO-Konstrukt sollte meiner Meinung nach etwas langlebigeres sein. Eine Prozedur arbeitet ja nur kurz einmal mit ihren übergebenen Daten.
*Deiner* Meinung nach. Meiner nicht.

Methoden in diesem langlebigen Konstrukt, sind eingeschränkt, da sie nur mit der starren langlebigen Situation arbeiten sollten/dürfen. kurzlebige zusammenaggregierte Daten in einer Prozedur kann ich immer wieder frisch/neu zusammensetzen. Auch in Abhängigkeit des Algorithmuses. Also schön flexibel und dynamisch.
Also ein weiterer Punkt PRO Prozedur bzw. PRO Daten und Verhalten gehören nicht zusammen, nicht auf langlebige Sicht.
Gehören *deiner Sicht* nach nicht zusammen.

Zu zweiterem: Das setzt aber eine festverdrahtete in sich geschlossene Objektlandschaft vorraus.
Nö, setzt es nicht.
Spätes Binden = nicht festverdrahtet ist einer der wesentlichen Punkte von OO.

Ein Entwurf, der nicht auf alle Daten zugriff hat, wird unflexibel. Wie greifst du bspw. vom MotorradObjekt auf das Sonnenobjekt zu und zwar exakt auf die Sonneninnentemperatur, sodass dein MotorradObjekt irgendwas machen kann, sollte dieser Anwendungsfall von Anfang an noch nicht vorgehsehen sein.
Um diesen Datenzugriff zu ermöglichen, musst du jetzt einiges erweitern.
Eventuell private Daten der Sonne freigeben, seis über ein getSonnenInnenTemperatur()...... Zwischenobjekte, also sprich Objekte die im Objektgraphen zwischen Motorrad und Sonne liegen, müssen diese Information irgendwie weitergeben, etc. pp
Das ist doch kein sinnvoller Entwurf mehr?!

Ausserdem stellt sich dann auch noch die Frage, sollte für die MotorradMotrensteuerung nicht nur die SonnenInnenTemperatur von Interesse sein, sondern allerlei anderes auch noch, gehört das fachlich dann überhaupt in eine Objektmethode des Motorrads/Motorradsmotors? Oder ist hier nicht sogar ein Service sinnvoller?

Konstruierter, als ein Motoradobjekt auf die Innentemperatur der Sonne zugreifen zu lassen, ist nicht möglich gewesen?

*Wenn* das Motorrad Zugriff auf die Umgehung braucht, dann hat es Zugriff auf die Umgebung, in der es sich befindet.
*Wenn* die Sonneninnentemperatur ermittelbar ist, gibt es entsprechende Methoden/Objekte/Messages.
*Wenn* die Anforderungen vorher keinen Zusammenhang von Sonne und Motorrad vorgesehen haben, muss ich anpassen.
*Wenn* mein Entwurf schlecht ist, muss ich viel anpassen.
Wenn er halbwegs gut ist, übergebe ich nur dem Motorrad die Umwelt (mit der Sonne) und stelle die Sonneninnentemperatur in der Sonne zur Verfügung, zwei Änderungen + was auch immer das Motorrad mit der Sonneninnentemperatur machen soll.

*Wenn* für die MotorradMotrensteuerung die SonnenInnenTemperatur relevant ist, gehört das natürlich zur MotorradMotrensteuerung. Warum sollte denn ein künstliches Konstrukt eingeführt werden, welches die Motorensteuerung und die SonnenInnenTemperatur kennt und weiß, wie beide zusammenwirken?


Aber das ganze mal prozedural betrachtet: Irgendwo gibt es ein Motorrad-Struct und ein Sonnen-Struct.
Das MotorradMotrensteuerung wird irgendwo in einer lieg geschachtelten Prozedur verändert. Wie bekommst du da jetzt ohne Änderung das Sonnenobjekt hin? Global ein einzelnes Sonnenstruct vorhalten, welches dann jeder verändern kann (zb einfach die SonnenInnenTemperatur negieren)?


Ich hätte übrigens beim ersten *Wenn* noch mal gewaltig über die Anforderungen nachgedacht.


Er darf nie negativ werden, wenn ein KontoInhaber diese Transaction macht.
Ein Admin darf das Konte des Users aber auch gerne mal ins Negative abrutschen lassen.
Das Verhalten gehört meiner Meinung nach nicht zum Konto, sondern zum Anwendungsfall
Ein klares Indiz dafür, dass Daten und Verhalten getrennt werden müssen.

Oder das Konto darf nicht negativ werden, wenn es 7 Uhr abends ist, der User eine negativen Schufaeintrag hat und wenn der Milchbauer vom Nebenort mindestens 70% seiner Photovoltaikanlage in den letzten 2Wochen als Eigenverbrauch verwendet hat.
Nö, ein klares Indiz, dass du die Anforderungen so umbiegst, dass sie dir besser passen.
Die Anforderungen war klar: *Der Kontostand darf durch Abheben nicht negativ werden*.

Vllt. aber möchte ich diesen UseCase nicht von Anfang an haben, sondern wirklich erstmal Konto darf nicht negativ werden. Ich möchte aber trotzdem die Möglichkeit haben, es zu meinem erweiterten UseCase hingehend zu erweitern.
Wie würdest du das machen?
Auch unter dem Gesichtspunkt betrachtet, dass ein nerviges Entlanghageln des Objektgraphen nicht erwünscht ist. Wie komme ich zu den Informationen 7Uhr, 70%, Nachbar....... Ohne dass ich mich in einem komplexen Objektgraphen entlanghangeln muss?
Wie würdest du es denn machen?
Wie kommt die entsprechenden Prozedur an die Uhrzeit, die Photovoltaikanlage und den Nachbarn? (ohne, dass du dich durch irgendwelche Structs hangeln musst)

Nein: OO != Polymorphie
OO == Verhalten und Daten zusammengekapselt
OO == Austausch via Messages
und noch ein paar Kriterien
Durch messages hast du quasi Polymorphie, da verschiedene Objekte verschieden auf die gleiche Message reagieren können.

Zum Thema polymorphie ist mir gerade noch ein weiteres OO Designproblem aufgefallen.
Man nehme bspw. ein Dreieck (Klasse Dreieck)
In der Schule wird einem gelehrt, dass bspw das Dreick am besten selbst weiß wie es sich zeichnen soll


Die Polymorphie besteht in dem Fall darin, dass sich bei aufruf der draw()-Methode das geometrische Unterobjekt Dreieck anders selbst zeichnen wird, wie die draw()-Methode eines Vierecks.

Problem: Wo soll sich das Dreieck hinzeichnen?: Auf ein Canvaselement bspw!
Ist das aber wirklich die Aufgabe des Dreiecks, sich auf das Canvasobjekt zu zeichnen?
Meiner Meinung nach gehört diese Funktionaltät nicht in die Dreiecksklasse.
Sie gehört meiner Meinung nach in ein Modul, das für Zeichnen zuständig ist.

Möchte ich das Dreieck wiederverwenden, in einem System, in dem rein gar nichts gezeichnet wird, ist ein Dreieck ohne derartiges Verhalten definitiv besser aufgehoben.
Und das macht die Proezudurale programmierung auch so schön wiederverwendbar.
Ich schmeisse entsprechende Prozeduren raus, wenn ich sie woanders nicht brauche, oder ich nehme welche dazu und erweitere das system so.

*Wenn* es ein grafisches Dreiecks-Element ist, ist es ganz offensichtlich Aufgabe des Dreiecks, sich selbst zu zeichnen. Das ist nämlich dann genau die Funktionalität, für die dieses Dreieck da ist.

*Wenn* es ein Dreieck ohne Bezug zur Ausgabe ist, ist es nicht Aufgabe des Dreiecks, sich zu zeichnen.
Ersteres Dreieck kann aber intern alles an letztes Dreieck delegieren, sodass man keine doppelte Funktionalität hat.


Das schöne an OO-Programmierung: das System ist flexibel und dynamisch. Brauche ich ein Viereck, erstell ich einfach eine Viereck-Klasse, und muss *nichts anderes* anpassen.

ganze anders bei Prozeduraler-Programmierung: brauche ich Vierecke, muss ich *jede* Prozedur, die mit Formen arbeitet, ändern. An zig Stellen müssen dann die Eigenheiten eines Vierecks bekannt sein, das ganze ist also völlig verstreut im gesamten Programm, anstatt an einer Stelle gebündelt zu sein.


"Und das macht die OO-Programmierung auch so schön wiederverwendbar.
Ich schmeisse entsprechende Objekte/Klassen raus, wenn ich sie nicht brauche, oder ich nehme welche dazu und erweitere das System so."
Da die Abhängigkeiten der Objekte klar definiert sind, hält ich durch rausnehmen auch der Schaden in Grenzen.
Wenn aber Alles auf alles zugreifen kann, kann ich nichts gefahrlos entfernen, ohne irgendwas kaputt zu machen


ich habe eine KlassenGraphStruktur, die wie folgt aussehen könnte.
Ich habe ein Struct, was so aussieht:
Code:
struct X
{

K übergerordnetes struct; //also sprich hier ist eine Referenz drinnnen von dem struct, das X enthält

T variable1 -> Y variable2 aus T -> variable3 aus Y
}

und drei Methoden:
methode1();methode2();methode3();
Beantworte bitte einmal selbst deine drei Fragen.

1 Frage ) wie greife ich von methode1 auf irgendwas von memVariable3 zu?
Eventuell: ich hangle mich im Objektgraphen entlang (aber du meintest, das wäre nicht sinnvoll)
Frage: warum braucht methode1 Dinge von Objekten, die es gar nicht kennt?
Im Zweifel: es bittet T um die entsprechenden Daten. Das memVariable3 in irgendeinem Unterobjekt existiert, weiß X im Optimalfall nicht

2 Frage )
das ganze ist doch dann auch wieder global zu sehen, weil sich methode1 sich die Daten selbst holt
Ich habe lediglich den Overhead des Graphenentlanghangelns drinnen.
Keine Ahnung was du damit meinst, global ist da ganz sicher nichts.
T *ist Teil von* X, Y *ist Teil von* T. nicht Y und T sind global.
(analog: Stuhl steht im Zimmer, Zimmer ist in nem Haus, was ist daran bitte global?)

3 Frage )
greift methode2 auf K zu, bzw. ganz allgemein die Existenz von K in X macht meinen Objektgraphen zyklisch
Ist das sinnvoll in OO?
nicht ohne Grund vermeidet man Zyklen, soweit möglich.
Sind Zyklen in Structs sinnvoll, bei Prozeduraler Programmierung?

4 Frage )
Nehmen wir an X ist ein Konto:
Es soll in dem Konto ein Betrag/Variable geändert werden.
Es wird dazu memVariable3 aus Y benötigt, ABER auch in dem Objektgraphen nicht vorhanden, ein anderes Konto X...... das ich aber zur Compilezeit noch nicht kenne, sondern erst dynamisch ermitteln muss.

Meine Idee wäre hier: Ich brauche mal wieder eine Prozedur, die das übergeordnet erledigt.
Also habe ich wieder eine Service-Klasse, die aber kein reines OO darstellt.
Wie würdest du das in reines OO umwandeln?
Ja, für sowas kann man Services nutzen. Services sind aber durchaus mehr, als nur reine Prozeduren.

Die Service-Klasse kann aber in diesem Fall auch keine Service-Klasse, sondern einfach ein "Bank-Objekt" sein, welches die Transaktion vornimmt.

Ganz generell, dieser Objektgraph wie oben angegeben ist ein ein starres Gebilde........ und mindestens aber Teil einer längerlebigen Struktur.

Für viele UseCases brauche ich nur kurzlebige strukturen...... und da finde ich es quatsch einen Objektgraphen aufzubauen, DO_METHODE() ausführen...... Objektgraphen dem Destruktur/GC zuzuführen.

Weder sind Objektgraph starre Gebilde, noch müssen sie langlebig sein.

Prozeduren (bzw Aufruf-Graphen) sind starre Gebilde, nicht Objekte!

5 Frage )
Viele Use-Cases wollen keine starre Strukur, also kein vorgegebenen Objektgraphen, sondern entscheiden erst zur Laufzeit, welche Daten sie genau benötigen.
Wie würdest du das OO umsetzen?
OO hat keine feste Struktur, das späte Binden zur Laufzeit ist eines der wichtigsten Merkmale!


Abgesehen davon: wie entscheiden denn zur Compiler-Zeit statisch gebundene Prozeduren magisch zur Laufzeit irgendwas?


Noch eine ganz allgemeine Frage:
Wenn du deine Software iterativ aufbaust, am Anfang noch nicht genau weißt wie du was du haben möchtest, wie programmierst du da, bzw. wie gehst du da vor, wie ist dein Buildprozess.
Das Problem sieht man ja am Konto mit dem negativen Kontostand. Wenn ich ein USeCase habe, den aber erst im laufe meines programmierens erarbeite, wie gehst du da vor?
Aufwändige Refactorings sollten ausgeschlossen sein, da diese Vorgehensweise (der iterative, top-bottom.... Aufbau) zur Programmiermethodik gehören soll.
Ich möchte eine Software so aufbauen, dass ich sie immer gut und schmerzfrei erweitern kann, das sehe ich irgendwie bei der OO nicht gegeben, bei PP dagegen schon.
Die Probleme hast du *genauso* bei PP.
Das du sie dort nicht und nur bei OO siehst, liegt an deinem Mangelnden Verständnis von OO (und deinem extremen Hype von PP).

Ansonsten:
Noch eine ganz allgemeine Frage:
Wenn du deine Software iterativ aufbaust, am Anfang noch nicht genau weißt wie du was du haben möchtest, wie programmierst du da, bzw. wie gehst du da vor, wie ist dein Buildprozess.
Das Problem sieht man ja am Konto mit dem negativen Kontostand. Wenn ich ein USeCase habe, den aber erst im laufe meines programmierens erarbeite, wie gehst du da vor?
Aufwändige Refactorings sollten ausgeschlossen sein, da diese Vorgehensweise (der iterative, top-bottom.... Aufbau) zur Programmiermethodik gehören soll.
Ich möchte eine Software so aufbauen, dass ich sie immer gut und schmerzfrei erweitern kann, das sehe ich irgendwie bei der PP nicht gegeben, bei OO dagegen schon.
 

Meniskusschaden

Top Contributor
Ich denke du stimmst mir zu, wenn ich bspw. auf alle Daten von überall zugreifen kann, dass ich da sehr flexibel und dynamisch unterwegs bin. Klar, macht das den Code schwerer testbar, aber bei ordentlicher Strukturierung ist man auch hier sicher gut testbar und mit wenig Code unterwegs.
Verstehe ich das richtig, dass du da gerade für die extensive Verwendung von globalen Variablen wirbst? Dann müssen wir kaum über OO diskutieren, denn das wäre auch innerhalb der prozeduralen Programmierung schon weitab vom Mainstream.
Selbst wenn du diese tolle Strukturierung schaffen würdest, würde dir das wenig nützen, weil dir jedes andere Teammitglied über unkontrollierte Seiteneffekte alles zerschiessen könnte. Diese Art von "Flexibilität" will man durch Einführung des Geheimnisprinzips ja gerade vermeiden.
Warum lässt sich dann eine Anwendung nicht 100%ig oo bauen?
Wer hatte denn jemals die Absicht das zu 100% zu tun? Wenn man eine adäquate Klassenhierarchie geschaffen hat, wird innerhalb der Methoden natürlich vieles prozedural gemacht.
Nein: OO != Polymorphie
Polymorphie ist nicht dasselbe wie OO, aber ein wichtiger Bestandteil davon. Ein Motor ist auch kein Auto, aber trotzdem ein wichtiger Bestandteil davon.
OO == Verhalten und Daten zusammengekapselt
Ja. Setzt man in OO-Sprachen mit Objektattributen und -methoden um und in prozeduralen Sprachen mit Verbundtypen und Funktionszeigern. Das sind zwei unterschiedliche technische Möglichkeiten, OO-Design zu implementieren.
Die Polymorphie besteht in dem Fall darin, dass sich bei aufruf der draw()-Methode das geometrische Unterobjekt Dreieck anders selbst zeichnen wird, wie die draw()-Methode eines Vierecks.

Problem: Wo soll sich das Dreieck hinzeichnen?: Auf ein Canvaselement bspw!
Ist das aber wirklich die Aufgabe des Dreiecks, sich auf das Canvasobjekt zu zeichnen?
Meiner Meinung nach gehört diese Funktionaltät nicht in die Dreiecksklasse.
Sie gehört meiner Meinung nach in ein Modul, das für Zeichnen zuständig ist.

Möchte ich das Dreieck wiederverwenden, in einem System, in dem rein gar nichts gezeichnet wird, ist ein Dreieck ohne derartiges Verhalten definitiv besser aufgehoben.
Und das macht die Proezudurale programmierung auch so schön wiederverwendbar.
Ich schmeisse entsprechende Prozeduren raus, wenn ich sie woanders nicht brauche, oder ich nehme welche dazu und erweitere das system so.
Natürlich benötigt man keine Zeichenmethode, wenn man nicht zeichnen will. Wenn man aber ein Shape-Objekt haben möchte, das ein Dreieck zeichnet, kann es sich die Mathematik doch von einem Triangle-Objekt erledigen lassen. Dazu muß man auch keine Prozeduren raus werfen oder rein kopieren, sondert verwendet einfach seine Triangle-Klasse wieder.
 

AndiE

Top Contributor
So richtig kann ich der Argumentation nicht folgen. Ich habe doch als erstes eine Use-Story. Nehmen wir mal als Beispiel eine Lieferscheinerstellung. In einem Lager gibt es Waren, die an Kunden verschickt werden und dabei werden Lieferscheine erstellt. Ich würde ich als erstes den "best case" modellieren, aber schnell darauf kommen, dass ich erst dem "worst case" benötige. Im "worst case" kennt das System weder einen Kunden noch eine Ware, und natürlich keinen Lieferschein. Im "best case" sind Kunde und Ware im System enthalten. In Falle des "worst case"muss ich also erst die Ware erstellen und dann den Kunden, und dann beides in einem Lieferschein verbinden. Wahrscheinlich würde ich die Waren und die Kunden noch als Collections erstellen.
Diese "use cases" "Ware erstellen" und "Kunde erstellen", erstellen ein neues Objekt, dass sie einfach den vorhandenen Collections hinzufügen.
Im Normalfall würde ich in der OOP die attribute private deklarieren und die getter und setter als public. Offensichtich wurde die Methode Ware.entimm(int anzahl) auch public sein. Da ich eine Formularansicht habe, habe ich auch immer ein aktuelles Objekt der Klasse Ware, das ich bearbeite. Und der Lieferschein wächst bei Ausführung dynamisch.
Will ich nun eine Anzahl Fuhrunternehmer mit verwalten, die die Lieferungen zum Kunden bringen, dann habe ich zwischen der Klasse Fuhrunternehmen und der Klasse Lieferscheine ein m:n-Kardinalität. In der Regel würde ich sogar eine Super-Klasse Geschäftspartner erstellen, von der Kunde und Fuhrunternehmer "erben", weil diese Klasse die anderen beiden generalisiert.

Natürlich könnte ich das auch prozedural machen, aber "addnewItem()" liest sich schwerer und ist unverständlicher als "ItemList.addItem()". Bei zweitem weiß ich, wer was macht, und durch die längere Schreibweise haben wir beim ersten auch mehr Fehlerwahrscheinlichkeit.
 

knotenpunkt

Mitglied
Hey,

Weniger Code produziert man mit Prozeduralem Code nicht zwingend - oft sogar ganz im Gegenteil.
Da wäre ich aber an einem Beispiel interessiert^^

Möchte ich in der OO-Variante etwas erweitern, reicht schon eine entsprechende Klasse.
Wie genau definierst du erweitern?
erweitern heisst für mich, teilweise bestehende Funktionlität weiter verwenden und diese um einen Gesichtspunkt zu erweitern. Also eine weitere Prozedur, die bestehende Prozeduren in irgendeiner Weise verwendet.


Kommen *andere* Use-Cases dazu, muss man in beiden Fällen was anpassen.
Es kommt glaube ich darauf an, wie man erweitert.
Es gibt meiner Meinung nach zwei Erweiterungsklassen (ich verwende hier den Begriff Klasse nicht im Zusammenhang von Programmierung, sondern einfach als normalen Sprachgebrauch^^)

Erweitere ich horizontal oder vertikal?
Sprich erweitere ich Funktionalität aussen rum (heisst ich verwende subsysteme einfach so wie sie sind -> So ein Art Schichtensystem) oder erweitere ich switch-cases, in der oo dann weitere Typen, polymorphe Aufrufe (switch-case und polymorphe Typen, sind ja das gleiche^^)

Bei zweiterem hast du recht, da muss ich bei beiden was anpassen.
Bei ersterem, die Frage an dich, wie erweiterst du sinnvoll in der oo "Funktionalität aussen rum".
//Ich habe dazu noch keine Meinung, würde mich auf ein Beispiel von dir freuen^^


Zu dem Switch-Case habe ich aber gleich mal ne Frage:
{
data x;
data y;
data z;

switch(variable k)
case 1;
case 2;
usw
}

im case 1 werden x und y verändert im case 2 werden y und z verändert/zugegriffen/oder whatever.
variable k steht erst zur Laufzeit und erst bei aufruf dieses Blocks { } fest!
wie würdest du das sinnvoll ins oo umformen?

ich würde sagen es ist schwierig solange k keinen fest defnierten/unflexiblen Zustand aufspannt.
Außerdem ist es kritisch für oo, da sowohl case 1 als auch case 2 y benötigen, entsprechende cases aber nicht jeweils x und z benötigen.

Konstruierter, als ein Motoradobjekt auf die Innentemperatur der Sonne zugreifen zu lassen, ist nicht möglich gewesen?

*Wenn* das Motorrad Zugriff auf die Umgehung braucht, dann hat es Zugriff auf die Umgebung, in der es sich befindet.
*Wenn* die Sonneninnentemperatur ermittelbar ist, gibt es entsprechende Methoden/Objekte/Messages.
*Wenn* die Anforderungen vorher keinen Zusammenhang von Sonne und Motorrad vorgesehen haben, muss ich anpassen.
*Wenn* mein Entwurf schlecht ist, muss ich viel anpassen.
Wenn er halbwegs gut ist, übergebe ich nur dem Motorrad die Umwelt (mit der Sonne) und stelle die Sonneninnentemperatur in der Sonne zur Verfügung, zwei Änderungen + was auch immer das Motorrad mit der Sonneninnentemperatur machen soll.

*Wenn* für die MotorradMotrensteuerung die SonnenInnenTemperatur relevant ist, gehört das natürlich zur MotorradMotrensteuerung. Warum sollte denn ein künstliches Konstrukt eingeführt werden, welches die Motorensteuerung und die SonnenInnenTemperatur kennt und weiß, wie beide zusammenwirken?


Aber das ganze mal prozedural betrachtet: Irgendwo gibt es ein Motorrad-Struct und ein Sonnen-Struct.
Das MotorradMotrensteuerung wird irgendwo in einer lieg geschachtelten Prozedur verändert. Wie bekommst du da jetzt ohne Änderung das Sonnenobjekt hin? Global ein einzelnes Sonnenstruct vorhalten, welches dann jeder verändern kann (zb einfach die SonnenInnenTemperatur negieren)?


Ich hätte übrigens beim ersten *Wenn* noch mal gewaltig über die Anforderungen nachgedacht.

So konstruiert finde ich das Beispiel nicht. Es zeigt, dass ich in speziellen Fällen, eben solche Anforderungen brauche und dies in der pp kein Problem darstellt.

Punkt 1 (Motorrad und Umgebung):
Das Motorrad hat die Umgebung und die Umgebung hat das Motorrad -> zirkuläre Abhängigkeit (du hast ja selbst geschrieben dass zirkuläre Abhängigkeiten schlecht sind)

Punkt 2 (Sonneninnentemperatur und entsprechende Methoden/Objekte/Messages)
Ich hatte ja ähnliches beispiel:
struct X
{
T mV1 -> mV2 -> mv3 ->.....
}

Du meintest hier, das im best case X mV2 und mV3...... nicht kennen sollte

Auf die Sonneninnentemperatur bezogen, würde das folgendes bedeuten:
Das Motorrad kennt nur die Umgebung, also nicht die Sonne
Oder noch überspitzter, Der Motorradmotor kennt nur sein umliegendes Motorrad und nichtmal die Umgebung.

Wie kommt also das Motorrad zur Sonneninnentemperatur.
Wenn du die Umgebung jetzt zu einer Art Facade umfunktionierst, dann hast du da zig-Tausend Methoden drinnen stehen. Motorrad fragt Umgebung ->giveMeSonneninnenTemperatur
und die Sonne delegiert das weiter z.B an die WeltallFacade
Das würde bedeuten, ich müsste in der Umgebung alle Methoden mit aufnehmen, die die memberVariable der Umgebung auch besitzen (und von aussen, hier jetzt das Motorrad benötigt werden) usw.
Das ist doch ein Krampf!

Also wie würdest du das richtig designen?


Punkt 3 (schlechter Entwurf):
Was ist ein guter und was ist ein schlechter Entwurf. Vllt hättest du da ein paar Antworten für mich auch auf konkreter Ebene und nicht nur abstrakt^^

Punkt 4 (Sonnen-Struct)
Warum nicht global, zumindest Teilglobal.
Sollte es in einem anderen Modul (Modul !=oo) liegen, kann man sich ja überlegen, das ganze nur über eine Wächterprozedur accessable zu machen.
Was mir hier aber wichtig ist, ich füge hier nicht daten und behaviour zusammen, sondern ich habe nur Behaviour.
Die Daten können meintwegen in der Datenbank liegen, sie können via fremd-API zugreifbar sein.
Und vor allem, die Daten haben keine Abhängigkeiten untereinander.
Um zu deinem Bankkonto zurückzukommen: Irgendwie hat dein Bankkonto sagen wir mal zwei mem_variablen, die was auch immer aussagen, was genau ist egal^^

möchte ich gewährleisten, dass mem_variable1 und 2 aus dem Bankkonto nur eine bestimmte Zustandsmenge annehmen dürfen, dann baue ich dafür eine Wächtermethode, die eben nur Elemente aus meiner eingeschränkten Zustandsmenge zulässt, oder diese sogar selbst baut! (angereichterte Wächtersetterfunktionen oder angereichterte Buisnessfunktionen). Das coole an der prozeduralen Programmierung ist aber jetzt im Vergleich zur OOP, dass diese Daten hier nicht in einer location gekapselt werden müssen. Zudem kann es sein, dass diese zwei mem_variablen auch nur in der einen Prozedur zueinander in Relation stehen.

Nö, ein klares Indiz, dass du die Anforderungen so umbiegst, dass sie dir besser passen.
Die Anforderungen war klar: *Der Kontostand darf durch Abheben nicht negativ werden*.
Ich mache die Anforderungen komplexer.
Mich würde interessieren, wie du das mit meiner Anforderung machen würdest.
Wie ich es mit deiner machen würde, steht direkt über diesem Zitat..... Eine Wächterprozedur, die deinen Use-Case abdeckt.

Wie würdest du es denn machen?
Wie kommt die entsprechenden Prozedur an die Uhrzeit, die Photovoltaikanlage und den Nachbarn? (ohne, dass du dich durch irgendwelche Structs hangeln musst)
Falls entsprechende Daten im Speicher stehen sollten, habe ich mir vorher optmierte direktZugriffCachingstrukturen gebaut. Falls nicht:
In der OOP bin ich mehr oder weniger gezwungen einen Graphen aufzubauen und damit zu arbeiten.
in der PP kann ich optimierte Datenstrukturen her nehmen. Dazu zählt auch das Wissen über Alignment im Speicher und anderes (diverse Hashtabeln, Binärvektoren, etc pp). Wenn ich möchte kann ich natürlich auch eine Baum/Graphstruktur aufbauen und damit arbeiten. Ich bin in der PP diesbezüglich sehr flexibel.
Sollten die Daten in der Datenbank liegen, kann mich mir die Effizienz des Datenbanksystems zu nutze machen^^

Sowie ich dich und auch die OOP im Allgemeinen verstanden habe, ist das aber streng genommen keine OOP wenn ich mir die Daten zusammenglaube und dann an einer Stelle einen Algorithmus ausführe. OOP besteht ja aus verteilten Algorithmen.
Meiner Meinung nach kann man mit verteilten Algorithmen einen "gesammelten" Algorithmus (bspw. eine Prozedur)
in seiner Gesamtheit nur abbilden, wenn ich State und Steuerungsvariablen mitdurchschleife etc pp. Und wenn dann noch transaktionen/atomarität etc pp dazukommen....... gute nacht^^

Der Testbarkeit zuliebe finde ich bei verteilte Algorithmen jetzt auch nicht wirklich so gut gegeben.


*Wenn* es ein grafisches Dreiecks-Element ist, ist es ganz offensichtlich Aufgabe des Dreiecks, sich selbst zu zeichnen. Das ist nämlich dann genau die Funktionalität, für die dieses Dreieck da ist.

*Wenn* es ein Dreieck ohne Bezug zur Ausgabe ist, ist es nicht Aufgabe des Dreiecks, sich zu zeichnen.
Ersteres Dreieck kann aber intern alles an letztes Dreieck delegieren, sodass man keine doppelte Funktionalität hat.


Das schöne an OO-Programmierung: das System ist flexibel und dynamisch. Brauche ich ein Viereck, erstell ich einfach eine Viereck-Klasse, und muss *nichts anderes* anpassen.

ganze anders bei Prozeduraler-Programmierung: brauche ich Vierecke, muss ich *jede* Prozedur, die mit Formen arbeitet, ändern. An zig Stellen müssen dann die Eigenheiten eines Vierecks bekannt sein, das ganze ist also völlig verstreut im gesamten Programm, anstatt an einer Stelle gebündelt zu sein.


"Und das macht die OO-Programmierung auch so schön wiederverwendbar.
Ich schmeisse entsprechende Objekte/Klassen raus, wenn ich sie nicht brauche, oder ich nehme welche dazu und erweitere das System so."
Da die Abhängigkeiten der Objekte klar definiert sind, hält ich durch rausnehmen auch der Schaden in Grenzen.
Wenn aber Alles auf alles zugreifen kann, kann ich nichts gefahrlos entfernen, ohne irgendwas kaputt zu machen

Wie genau sieht für dich Delegieren aus?
DreiecktGraphisch-> ruft methode getXCoordinate() auf in DreieckNormal und dieses -> ruft getXCoordinate() auf sich selbst auf. Das ist für mich eben overheadorientiertes programmieren^^


Frage: warum braucht methode1 Dinge von Objekten, die es gar nicht kennt?
Im Zweifel: es bittet T um die entsprechenden Daten. Das memVariable3 in irgendeinem Unterobjekt existiert, weiß X im Optimalfall nicht
Wie soll es dann an die Daten kommen?
Ok ich kann facadenhaft die Schnittstellen platt klopfen, sprich ich biete in T Methoden an, die nichts anderes tun, als einfaches Weiterdeligieren an Methoden von memVariable3
Das ist doch nicht sinnvoll.


Keine Ahnung was du damit meinst, global ist da ganz sicher nichts.
T *ist Teil von* X, Y *ist Teil von* T. nicht Y und T sind global.
(analog: Stuhl steht im Zimmer, Zimmer ist in nem Haus, was ist daran bitte global?)
Naja ich finde schon, ob ich jetzt via eines direkten Access an die Daten komme oder via Hangelns, ist für das Ergebnis egal. Ich habe in der Konsquenz dann die Daten.
Nur Zweiteres bedeutet viel Overhead in der Umsetzung

nicht ohne Grund vermeidet man Zyklen, soweit möglich.
Sind Zyklen in Structs sinnvoll, bei Prozeduraler Programmierung?
Zyklen in prozeduraler Programmierung? Wie meinst du das?
Wie du vorhin ja selbst geschrieben hast: Die Motorsteuerung muss Zugriff auf sein Embeddendes Objekt, also die Umgebung haben, somit würde ich zirkulär programmieren.
Also wie würdest du die Motorsteuerung auf die Innentemperatur der Sonne zugreifen lassen?


Ja, für sowas kann man Services nutzen. Services sind aber durchaus mehr, als nur reine Prozeduren.

Die Service-Klasse kann aber in diesem Fall auch keine Service-Klasse, sondern einfach ein "Bank-Objekt" sein, welches die Transaktion vornimmt.

Und dann brauche ich später Transfers zwischen Banken usw.
Jetzt könnte ich ja gemäß Bottom-Up hier wieder ein "SammelObjekt"-Schreiben. vllt. eine Bankenvereinigungsklasse^^
Möchte ich aber jetzt nur einen Transfer innerhalb einer Bank machen, dann muss ich die Schnittstellen aus der Bankklasse wieder facadenhaft nach aussen reichen, also in der Bankenvereinigungsklasse Delegationsmethoden anbieten. Oder die Bankenvereinigungsklasse muss wieder Internas rausgeben, sprich eine Methode getBank() haben. Diese Lösung findest du selbst nicht so gut, wie du bei meinem Beispiel mit X->mem1->mem2->mem3 geschrieben hast.

Weder sind Objektgraph starre Gebilde, noch müssen sie langlebig sein.

Prozeduren (bzw Aufruf-Graphen) sind starre Gebilde, nicht Objekte!
Die Struktur der Prozeduren ist starr, ja das ist richtig^^
Die Ausführung dagegen ist hochdynamisch und zwar wesentlich dynamischer als bei Objekten^^
Und das möchte ich jetzt etwas näher erklären:
Prozeduren:
Jeder verschiedenartige Input hat ein anderes Verhalten zur Folge (auch durch switch-cases gegeben)
das macht das meinetwegen auch etwas schwerer testbar, aber somit auch hoch dynamisch^^

Objekte:
Jedes andere Objekt mit anderem State verhält sich eigentlich genauso wie ich es eben unter dem Punkt Prozedur geschrieben habe.
Möchte ich in einem Batch-Prozess dieses Verhalten hervorrufen, muss ich sowas in der Art machen:
new ActionObjekt(data1,data2,data3)->process(); //anschließend kann der GC das ActionObjekt wegwerfen.
Empfinde ich nicht so sinnvoll, hier den Heap und den GC zu stressen, obwohl man auch direkt callFunction(data1,data2,data3) aufrufen könnte.

Also habe ich in der Objektwelt eher starre Gebilde
Irgendwo mal vereinzelt ein C=new Objekt(dataA,dataB,dataC); und dann rufe ich auf C methoden auf.
Dieses verhalten ist gemäß der konstruktordaten dataA bis dataC, sehr eingeschränkt.
Flexibilität sehe ich hier nicht!


OO hat keine feste Struktur, das späte Binden zur Laufzeit ist eines der wichtigsten Merkmale!


Abgesehen davon: wie entscheiden denn zur Compiler-Zeit statisch gebundene Prozeduren magisch zur Laufzeit irgendwas?
prozeduren, durch switch-cases und zwar je nach aktuell stattfindender parameterübergabe.

OO kann das gleiche Verhalten vorweisen, aber wie ich schon direkt über diesem zitat geschrieben habe:
ActionObjekte sind nicht so sinnvoll.
So zeigen zwar auch Objekte in dem Punkt dynamisches Verhalten auf. Diese Dynamik ist aber nicht flexibel, da die Objekte mit der Dynamik zwar konfiguriert werden (Konstruktoraufruf new Object(dataA,dataB,dataC)), diese Flexibilität dann aber nicht weiter zur Laufzeit haben. (sonst wären wir ja wieder bei den ActionObjects^^)
Ich denke du kannst nachvollziehen, was ich meine, oder?

Ansonsten:
Noch eine ganz allgemeine Frage:
Wenn du deine Software iterativ aufbaust, am Anfang noch nicht genau weißt wie du was du haben möchtest, wie programmierst du da, bzw. wie gehst du da vor, wie ist dein Buildprozess.
Das Problem sieht man ja am Konto mit dem negativen Kontostand. Wenn ich ein USeCase habe, den aber erst im laufe meines programmierens erarbeite, wie gehst du da vor?
Aufwändige Refactorings sollten ausgeschlossen sein, da diese Vorgehensweise (der iterative, top-bottom.... Aufbau) zur Programmiermethodik gehören soll.
Ich möchte eine Software so aufbauen, dass ich sie immer gut und schmerzfrei erweitern kann, das sehe ich irgendwie bei der PP nicht gegeben, bei OO dagegen schon.

Touche^^, aber meinen Text empfinde ich als richtiger^^



Wer hatte denn jemals die Absicht das zu 100% zu tun? Wenn man eine adäquate Klassenhierarchie geschaffen hat, wird innerhalb der Methoden natürlich vieles prozedural gemacht.
Und wo ist dann der Unterschied zu gut strukturiertem prozeduralen Programmieren?

Polymorphie ist nicht dasselbe wie OO, aber ein wichtiger Bestandteil davon. Ein Motor ist auch kein Auto, aber trotzdem ein wichtiger Bestandteil davon.
Ich wollte damit nur klarstellen, dass OO nicht Polymorphie ist.
Polymorphie kann in OO verwendet werden, genauso aber auch in prozeduraler Programmierung



Natürlich könnte ich das auch prozedural machen, aber "addnewItem()" liest sich schwerer und ist unverständlicher als "ItemList.addItem()". Bei zweitem weiß ich, wer was macht, und durch die längere Schreibweise haben wir beim ersten auch mehr Fehlerwahrscheinlichkeit.
Zweiteres setzt vorraus, dass ich die ItemList vor mir habe.
Bei ersterem muss ich die ItemListe nicht vor mir haben.
Wenn ich als Identifikator bspw. eine ID, ein SuchString etc pp übergebe, dann liegt es in der Verantwortung der addNewItem() Prozedur die Daten zu holen, und nicht mir bei mir als Client, diese Daten bereitzustellen bzw. vorzuenthalten. Und das ist schon sehr wertvoll^^


Offensichtich wurde die Methode Ware.entimm(int anzahl) auch public sein.
Hier sehe ich allgemein das Problem, dass man viel overhead programmiert. Und zwar: Einen Wrapper um bestehende Collection-Klassen.


So jetzt hätte ich mal noch ein paar Praxisbeispiele, wo ich versucht habe einen OO-Ansatz umzusetzen, aber nicht wirklich glücklich darüber bin. Im folgenden werde ich ausführen, warum ich darüber nicht glücklich bin, bzw. welche Probleme ich da so sehe^^


Problem 1:

Wenn ich jetzt bei den Collection-Klassen bleibe:
Ich habe oft folgendes Problem:
wenn ich Daten abspeichere, dann speichere ich die unterschiedlich indexiert ab.
Sprich einmal sequenziell in einer ArrayList
Ein zweitesmal in einer HashMap mit dem Key Name
Ein drittesmal in einer Hasmap mit dem Key Alter


class User
{
string name;
string alter;
string mail;
string gender;
//weitere....
}

class IrgendEinUserContainer
{
ArrayList<User> sequenziell;
HashMap<String,User>indexedByName;
HashMap<String,User>indexedByAlter;
}


So kann ich später je nachdem mit welchem Suchindex ich drauf zugreifen möchte, entsprechend schnell an das UserObjekt gelangen.
Ist das sinnvoll das so zu machen?
Weil bei jedem push/remove muss ich alle indexStrukturen anpassen.

Wie ich SQL an der Stelle liebe. Einfach ein entsprechender Suchstring eingeben und ich bekomme super schnell mein Ergebnis (das habe ich geschrieben, weil ich früher viel web-zeugs gemacht habe^^)

Möchte ich in der OO-Welt einen weiteren Index hinzufügen, muss ich einiges anpassen. Bei SQL dagegen interessiert mich das nicht!



Problem 2:
verteilte Algorithmen:

@mrBrown du hast vorhin ja geschrieben, wenn X überhaupt Informationen von memVariable3 braucht.
Wenn nicht, dann impliziert das, dass in memVarbiable3 selbst entsprechender Algorithmus definiert ist.
Aber das geht ja leider nicht überall, dass man Algorithmen aufteilt.
Und genau deshalb brauche ich managed/service-Prozeduren, wo der Algorithmus an einer zentralen Stelle definiert ist und sich die Daten holt/via Argumente bekommt und basierend auf diesen dann entsprechendes Programmverhalten zeigt^^

Ich habe bspw ne RestApi von einer Gartenbeleuchtung

Was für Module habe ich:
Kommunikationsmodul (tcp/connection.... Restparsing etc pp)
SmartHomeModul
BelechtungsschalterHighLevel
RelaisSchalterLowLevel


Wo bearbeite ich den Request?
Der Request kommt im Kommunikationsmodul an..... theorethissch könnte ich ihn hier schon komplett abarbeiten
dazu würde ich mir entsprechende State-Daten aus untergeordneten Modulen/Objekte holen

Ich kann den Request aber auch irgendwie weitergeben

Aber dann hat mein Kommunikationsmodul nur noch zur Aufgabe Requests weiterzudeligieren und im Falle eines "Protokollfehlers" rauszufiltern und wegzuschmeissen.


Ok funktioniert alles -> ich delegiere weiter an das SmartHomeModul
Warum würde ich als prozedural-denkender Mensch spätestens hier die die komplette Logik verbauen?

Das mag ich erklären:

Das RelaisSchalterModul hat dafür Sorge zu tragen, dass das Relai-Modul immer nur alle 2sec einen Umschaltvorgang tätigen darf, sollte es sich bei einem Mehrkanalsrelais um den gleichen Relaikanal handeln.

Das BeleuchtungsschalterHighLevel Modul sorgt dafür, dass ein entsprechender User immer nur alle 30sec etwas schalten darf.

Ausserdem sollte in der Requestantwort detailiert stehen, im Falle eines Negativs, warum der Schaltvorgang nicht funktioniert hat.

Zudem, sollte das Relaismodul in der letzten Stunde bereits 30 Schaltvorgänge getätigt haben und ein User X selbst in der letzten Stunde 20 Schaltvorgänge gemacht haben, dann wird das ganze System für 3h gesperrt

Was ich damit sagen möchte, das ist alles so sehr verwoben, dass es nicht viel Sinn macht zu versuchen den Algorithmus da aufzuteilen.


Vllt möchte ich auch noch folgendes miteinbauen:
Wenn User X 50RestCalls gesendet hat und nur 10 Schaltvorgänge getätigt hat, das Relaismodul 5 Schaltvorgänge verzeichnet hat und das alles in den letzten 40min, auch dann sollte eine Sperre, diesmal für 5h eingerichtet werden.

Sprich ich brauch in dem Use-Case bzw. in dem Algorithmus, Daten aus allen Modulen/Ebenen (vorwärt/rückwärts/zirkulierend, etc pp^^)

Eine Prozedur, die die Daten erhält, sie erhalten kann oder whatever ist hier wesentlich besser geeignet als der Versuch den Algorithmus aufzuteilen, was ja auch gar nicht geht.


Problem 3

Vllt. geht das ganze ja doch?!

Also was ich auch schon öfters gemacht habe:
Bei vielen MethodenCalls schleife ich einen Art Kontext mit durch^^

Bei einer Mischpultsteuerung die ich mal programmiert habe, habe ich bspw. folgenden Kontext mitdurchgeschleift.
Mitdurchgeschleift bedeutet in dem Fall, dass in einer Event-basierten-Architektur, auch die die abgefeuerten Events, Delegationen, etc, pp den Kontext miterhalten haben.

Z.b.

channel5.setEQDBHighLevelBand(3,kontext);

kontext enhält folgende Daten
{
isNetwork
isUserCall
isAutomaticCall
isDCACall
}

Warum brauche ich die Daten?
Im Falle des DCA-Calls unterbinde ich somit Schleifen, sollte es sich um einen Fader handeln, der selbst in einer DCA-Gruppe ist.
IsNetwork bedeutet, ich habe die Parameteränderung von einem ClientRechner aus dem Netwerk erhalten/oder vica versa...... Das isNetwork unterbindet dann später im NetworkEventProcessor, dass der Request wieder zurückgeleitet wird, was dann übers netzwerk eine endlosRekursion zur Folge hätte

....

Also wie man sieht, kann man Algorithmen schon etwas aufteilen, ABER ich muss Steuerungskontext Daten mitdurchschleifen?!
Eine andere Lösung sehe ich da nicht?!
Oder seht ihr da eine andere Lösung?


Also nochmal zusammenfassend was stört mich an OO:
-der Versuch einen zentralen Algorithmus in einen dezentralen umzuwandeln, was oft nicht funktioniert
-ich brauche das Objekt an der Stelle, wo ich etwas mit Ihm bzw. dessen Daten anstellen möchte //siehe addItem Beispiel
-unnötige Abhängigkeiten -> sobald ich membervariablen in einem Objekt zusammenfasse und auf diesem Methoden definiere, so ist die Methode immer abhängig von ALLEN Membervariablen, auch wenn nicht alle benötigt werden.
-sehr unflexibel -> siehe Beispiel mit ActionObjekt/NormalenObjekt/SwitchCases
-weiteres


soweit mal wieder


lg knotenpunkt
 

AndiE

Top Contributor
Zu 1.:
Hier kommt es doch auch auf die Modellierung an. Angenommen, ich hätte eine Bibliotheksverwaltung, und das Buch-Objekt hat die Membervariablen (Platz,Titel, Autor, Kategorie, Umfang). Dann kann ich ja nach den ersten 3 Variablen indizieren. Dann habe ich doch zumindest 2 Listen, die aufsteigend Titel und Autor beinhalten.
Die OOP entspricht für mich dem natürlichen Denken.

Erzeuge ein Buchobjekt. Nimm das Bibliotheksobjekt. Füge de Bibliotheksobjekt das Buchobjekt hinzu.

Und damit fertig. Was da im Hintergrund passiert, ist mir egal. Das macht doch das Programmieren viel übersichtlicher und damit auch wartbarer.

Ich brauche eine inizierung? Wer besitzt die? Sicher das Bibliotheksobjekt. Das hole ich mir und kann dann damit arbeiten.
 

mrBrown

Super-Moderator
Mitarbeiter
Da wäre ich aber an einem Beispiel interessiert^^
Gefühlt alles, wo Polymorphie benutzt wird...


Wie genau definierst du erweitern?
erweitern heisst für mich, teilweise bestehende Funktionlität weiter verwenden und diese um einen Gesichtspunkt zu erweitern. Also eine weitere Prozedur, die bestehende Prozeduren in irgendeiner Weise verwendet.
Ich definiere das kaum anders - dem Programm neue Funktionalität hinzufügen.
In welcher Form ist egal, relevant ist nur, dass das Programm am Ende mehr Funktionalität hat.

Es kommt glaube ich darauf an, wie man erweitert.
Es gibt meiner Meinung nach zwei Erweiterungsklassen (ich verwende hier den Begriff Klasse nicht im Zusammenhang von Programmierung, sondern einfach als normalen Sprachgebrauch^^)

Erweitere ich horizontal oder vertikal?
Sprich erweitere ich Funktionalität aussen rum (heisst ich verwende subsysteme einfach so wie sie sind -> So ein Art Schichtensystem) oder erweitere ich switch-cases, in der oo dann weitere Typen, polymorphe Aufrufe (switch-case und polymorphe Typen, sind ja das gleiche^^)

Bei zweiterem hast du recht, da muss ich bei beiden was anpassen.
Bei ersterem, die Frage an dich, wie erweiterst du sinnvoll in der oo "Funktionalität aussen rum".
//Ich habe dazu noch keine Meinung, würde mich auf ein Beispiel von dir freuen^^
Okay, um meine ursprüngliche Aussage anzupassen: man muss die für den neuen Use-Case relevanten Teile anpassen ;)

Das "Funktionalität aussen rum" ohne Probleme klappt, ist doch einer der wesentlichen Punkte von OO. Bestehenden Teilen ist es vollkommen egal, wie sie genutzt werden - solange die der Spezifikation entsprechend genutzt werden, und das erzwingt man z.T. durch Kapselung.

(switch-case und polymorphe Typen, sind ja das gleiche^^)
Ja, genau wie ein if+goto das gleiche ist wie while und for...also theoretisch schon, praktisch wird es aber ganz sicher nicht so genutzt^^


Zu dem Switch-Case habe ich aber gleich mal ne Frage:
{
data x;
data y;
data z;

switch(variable k)
case 1;
case 2;
usw
}

im case 1 werden x und y verändert im case 2 werden y und z verändert/zugegriffen/oder whatever.
variable k steht erst zur Laufzeit und erst bei aufruf dieses Blocks { } fest!
wie würdest du das sinnvoll ins oo umformen?

ich würde sagen es ist schwierig solange k keinen fest defnierten/unflexiblen Zustand aufspannt.
Außerdem ist es kritisch für oo, da sowohl case 1 als auch case 2 y benötigen, entsprechende cases aber nicht jeweils x und z benötigen.

Irgendeinen Prozederuralen Code dahin klatschen und den zu OO umformen geht immer schief.
Liefer passenden Kontext dazu, dann liefer ich dir den passenden Code ;)

Wenn das interne Daten eines Objekts sind, ist da durchaus auch mal ein Switch valide, u.U. fährt man aber schon da mit Command-Objekten besser.


So konstruiert finde ich das Beispiel nicht. Es zeigt, dass ich in speziellen Fällen, eben solche Anforderungen brauche und dies in der pp kein Problem darstellt.
Ein Motorrad, welches die Innentemperatur der Sonne kennt, zeigt vor allem, dass man, um sein Argument zu unterstützen, die absurdesten Dinge konstruiert ;)

Das Motorrad hat die Umgebung und die Umgebung hat das Motorrad -> zirkuläre Abhängigkeit (du hast ja selbst geschrieben dass zirkuläre Abhängigkeiten schlecht sind)
Deshalb vermeidet man sie soweit möglich - ein Beispiel, in dem man sie zwingend braucht, kann man natürlich immer Konstruieren ;)

Punkt 2 (Sonneninnentemperatur und entsprechende Methoden/Objekte/Messages)
Ich hatte ja ähnliches beispiel:
struct X
{
T mV1 -> mV2 -> mv3 ->.....
}

Du meintest hier, das im best case X mV2 und mV3...... nicht kennen sollte

Auf die Sonneninnentemperatur bezogen, würde das folgendes bedeuten:
Das Motorrad kennt nur die Umgebung, also nicht die Sonne
Oder noch überspitzter, Der Motorradmotor kennt nur sein umliegendes Motorrad und nichtmal die Umgebung.

Wie kommt also das Motorrad zur Sonneninnentemperatur.
Wenn du die Umgebung jetzt zu einer Art Facade umfunktionierst, dann hast du da zig-Tausend Methoden drinnen stehen. Motorrad fragt Umgebung ->giveMeSonneninnenTemperatur
und die Sonne delegiert das weiter z.B an die WeltallFacade
Das würde bedeuten, ich müsste in der Umgebung alle Methoden mit aufnehmen, die die memberVariable der Umgebung auch besitzen (und von aussen, hier jetzt das Motorrad benötigt werden) usw.
Das ist doch ein Krampf!

Also wie würdest du das richtig designen?
Wenn(!) so etwas völlig absurdes nötig ist, könnte man über die Umgebung an die Sonne kommen und über das Motorrad an dessen Umgebung. Aber dann würde es bei mir daran scheitern, das man die Innentemperatur der Sonne nicht einfach weiß ;)

Aber mal andersrum gefragt: Wie würdest du so etwas sauber(!) designen?

Punkt 3 (schlechter Entwurf):
Was ist ein guter und was ist ein schlechter Entwurf. Vllt hättest du da ein paar Antworten für mich auch auf konkreter Ebene und nicht nur abstrakt^^
Schlechte (also richtig beschissen und absolut unbrauchbarer) Entwurf: Motoradmotor braucht Sonneninnentemperatur
Besserer Entwurf: Motoradmotor braucht Umgebungstemperatur, welche die Umgebung hat. Wie die berechnet wird, und ob es Überhaut eine Sonne gibt (Meine Motorräder fahren nämlich nur in anderen Galaxien, in denen es entweder keine oder sieben Sonnen gibt), interessiert den Motor nicht sondern weiß die Umgebung.

Punkt 4 (Sonnen-Struct)
Warum nicht global, zumindest Teilglobal.
Sollte es in einem anderen Modul (Modul !=oo) liegen, kann man sich ja überlegen, das ganze nur über eine Wächterprozedur accessable zu machen.
Was mir hier aber wichtig ist, ich füge hier nicht daten und behaviour zusammen, sondern ich habe nur Behaviour.
Die Daten können meintwegen in der Datenbank liegen, sie können via fremd-API zugreifbar sein.
Und vor allem, die Daten haben keine Abhängigkeiten untereinander.
Um zu deinem Bankkonto zurückzukommen: Irgendwie hat dein Bankkonto sagen wir mal zwei mem_variablen, die was auch immer aussagen, was genau ist egal^^

möchte ich gewährleisten, dass mem_variable1 und 2 aus dem Bankkonto nur eine bestimmte Zustandsmenge annehmen dürfen, dann baue ich dafür eine Wächtermethode, die eben nur Elemente aus meiner eingeschränkten Zustandsmenge zulässt, oder diese sogar selbst baut! (angereichterte Wächtersetterfunktionen oder angereichterte Buisnessfunktionen). Das coole an der prozeduralen Programmierung ist aber jetzt im Vergleich zur OOP, dass diese Daten hier nicht in einer location gekapselt werden müssen. Zudem kann es sein, dass diese zwei mem_variablen auch nur in der einen Prozedur zueinander in Relation stehen.

Ich bin ein ziemlich naiver programmiere, sehe also diese beiden mem_variablen und änder sie einfach, wie ich das grad brauche. Von der Wächtermethode weiß ich nichts, die ist ja irgendwo völlig anders.
Mein etwas weniger naiver Kollege will die Variablen nicht direkt nutzen, sondern denkt sich "ah, um die zu schützen braucht ich eine Wächtermethode" und schreibt sich einfach eine passende. Natürlich eine völlig andere, weil er deine Wächtermethode nicht kennt.
Die mem_variablen sind aber Unternehmensrelevant und dürfen nicht getrennt geändert werden. Dummerweise weiß ich das nicht und änder sie beliebig und mein Kollege ändert sie leider falsch.

Klingt sinnvoll, oder?

Ich mache die Anforderungen komplexer.
Mich würde interessieren, wie du das mit meiner Anforderung machen würdest.
Wie ich es mit deiner machen würde, steht direkt über diesem Zitat..... Eine Wächterprozedur, die deinen Use-Case abdeckt.
Du gehst abet nicht wirklich zu nem Kunde und sagst dem "ich habe deine Anforderungen komplexer gemacht, weil deine Geschäftsregeln waren irgendwie doof und ich wollte sie gern anders. Das dein Geschäft jetzt nicht mehr Funktioniert ist mir egal. Benutz aber bitte meine Wächterprozedur".

Was ich mit deiner kontextlosen Wächterprozedur mache, hab ich ja auch schon gesagt: sie ignorieren. Warum sollte ich auch eine wild im Programm stehenden Prozedur nutzen, wenn die Daten eh global verfügbar sind und ich sie viel einfacher direkt benutzen kann?


Falls entsprechende Daten im Speicher stehen sollten, habe ich mir vorher optmierte direktZugriffCachingstrukturen gebaut. Falls nicht:
In der OOP bin ich mehr oder weniger gezwungen einen Graphen aufzubauen und damit zu arbeiten.
in der PP kann ich optimierte Datenstrukturen her nehmen. Dazu zählt auch das Wissen über Alignment im Speicher und anderes (diverse Hashtabeln, Binärvektoren, etc pp). Wenn ich möchte kann ich natürlich auch eine Baum/Graphstruktur aufbauen und damit arbeiten. Ich bin in der PP diesbezüglich sehr flexibel.
Sollten die Daten in der Datenbank liegen, kann mich mir die Effizienz des Datenbanksystems zu nutze machen^^
und das jetzt bitte in der prozeduralen Sprache Pascal.

[...] OOP besteht ja aus verteilten Algorithmen. [...]
Ich habe keine Ahnung, was du plötzlich zu verteilten Algorithmen meinst und wie du dazu kommst und was diese mit dem Thema zu tun haben, deshalb ignorier ich's einfach mal...


Wie genau sieht für dich Delegieren aus?
DreiecktGraphisch-> ruft methode getXCoordinate() auf in DreieckNormal und dieses -> ruft getXCoordinate() auf sich selbst auf. Das ist für mich eben overheadorientiertes programmieren^^
Dann nimm ne Sprache, in der sowas inlined wird, und es nur noch ein direkter Speicherzugriff ist. (Um's dir leichter zu machen: bist sogar schon auf der passenden Seite dafür ;) )
Und ja, genau so sieht Delegation aus.

Wie soll es dann an die Daten kommen?
Ok ich kann facadenhaft die Schnittstellen platt klopfen, sprich ich biete in T Methoden an, die nichts anderes tun, als einfaches Weiterdeligieren an Methoden von memVariable3
Das ist doch nicht sinnvoll.
Wenn(!) die internen Daten relevant für außen sind, doch, dann ist es sinnvoll, für diese eine Schnittstelle zu bieten.
Um mal ein reales Beispiel zu geben Üblicherweise hab ich auch keinen direkten Zugriff auf dein Portmonee und kann mir einfach Geld rausnehmen, sondern frage dich, wenn du mir Geld geben sollst.

Zyklen in prozeduraler Programmierung? Wie meinst du das?
Wie du vorhin ja selbst geschrieben hast: Die Motorsteuerung muss Zugriff auf sein Embeddendes Objekt, also die Umgebung haben, somit würde ich zirkulär programmieren.
Also wie würdest du die Motorsteuerung auf die Innentemperatur der Sonne zugreifen lassen?

So meine ich Zyklen in Structs:
Code:
struct A {
  B b;
}
struct B {
  A a;
}

Wenn nötig kann man durchaus zirkuläre Referenzen nutzen - im Idealfall aber nur als Laufzeit und nicht Kompilezeit-Abhängigkeit. In den meisten Fällen kann man es aber vermeiden.

Und dann brauche ich später Transfers zwischen Banken usw.
Jetzt könnte ich ja gemäß Bottom-Up hier wieder ein "SammelObjekt"-Schreiben. vllt. eine Bankenvereinigungsklasse^^
Möchte ich aber jetzt nur einen Transfer innerhalb einer Bank machen, dann muss ich die Schnittstellen aus der Bankklasse wieder facadenhaft nach aussen reichen, also in der Bankenvereinigungsklasse Delegationsmethoden anbieten. Oder die Bankenvereinigungsklasse muss wieder Internas rausgeben, sprich eine Methode getBank() haben. Diese Lösung findest du selbst nicht so gut, wie du bei meinem Beispiel mit X->mem1->mem2->mem3 geschrieben hast.
Und wie kommst du prozedural an die Bank?
Der Nutzer hat nur den Banknamen eingetippt, wird das in deinem Programm magisch zur Bank?
In OO wäre das einfach ein bankRepo.getBankByName(name) - ein Interface. welches für genau den Zweck da ist.
Aber spaßeshalber einfach mal von der echten Welt ableiten: Huch, sieht ja da wie in der OO Variante aus. Bestimmt total unhilfreich für jeden Domänenexperten, wenn das Programm die Domäne eins-zu-eins abbildet....


Prozeduren:
Jeder verschiedenartige Input hat ein anderes Verhalten zur Folge (auch durch switch-cases gegeben)
das macht das meinetwegen auch etwas schwerer testbar, aber somit auch hoch dynamisch^^
Also hast du Code, den fast jeder direkt als absolut unbenutzbar abtun würde?

new ActionObjekt(data1,data2,data3)->process(); //anschließend kann der GC das ActionObjekt wegwerfen.
Empfinde ich nicht so sinnvoll, hier den Heap und den GC zu stressen, obwohl man auch direkt callFunction(data1,data2,data3) aufrufen könnte.
In meiner lustigen, grad erfundenen Prozeduralen Sprache erzeugt jeder Prozeduraufruf 17GB an Daten auf dem Heap, welche mit 1Kb/sek erzeugt werden.
In realen OO-Sprachen kann das alles nur mit Allokation auf dem Stack oder sogar ohne jegliche Speicher-Allokation für Objekte passieren.

Also haben wir: Prozedural super langsam vs OO super schnell
(Vielleicht fällt dir jetzt auf, dass du aus "Konzept vs Konzept" ständig "schnelle Laufzeitumgebung vs langsame Laufzeitumgebung" machst - wenn du lieber darüber reden willst, solltest du einen dazu passenden Thread aufmachen.)
Irgendwo mal vereinzelt ein C=new Objekt(dataA,dataB,dataC); und dann rufe ich auf C methoden auf.
Dieses verhalten ist gemäß der konstruktordaten dataA bis dataC, sehr eingeschränkt.
Flexibilität sehe ich hier nicht!
Für dich ist also "3 Variablen im Speicher ablegen und eine Funktion aufrufen" deutlich unflexibler als "3 Variablen im Speicher ablegen und eine Funktion aufrufen"? Kann man so sehen, ist aber etwas merkwürdig...

ActionObjekte sind nicht so sinnvoll.
So zeigen zwar auch Objekte in dem Punkt dynamisches Verhalten auf. Diese Dynamik ist aber nicht flexibel, da die Objekte mit der Dynamik zwar konfiguriert werden (Konstruktoraufruf new Object(dataA,dataB,dataC)), diese Flexibilität dann aber nicht weiter zur Laufzeit haben. (sonst wären wir ja wieder bei den ActionObjects^^)
Ich denke du kannst nachvollziehen, was ich meine, oder?
Nö, kann ich nicht, weil es Unsinn ist. Siehe den Punkt hier drüber.


Und wo ist dann der Unterschied zu gut strukturiertem prozeduralen Programmieren?

Was bedeutet für dich "gut strukturiert"?
Zusammengehörende Dinge zusammen legen ist es nicht, einzelne Teile nur bestimmte Dinge machen auch nicht (weil zu unflexibel).
Wenn ich alle deine Erklärungen zusammen nehme, hab ich völlig unstrukturierten prozeduralen Code vor Augen.

Zweiteres setzt vorraus, dass ich die ItemList vor mir habe.
Bei ersterem muss ich die ItemListe nicht vor mir haben.
Wenn ich als Identifikator bspw. eine ID, ein SuchString etc pp übergebe, dann liegt es in der Verantwortung der addNewItem() Prozedur die Daten zu holen, und nicht mir bei mir als Client, diese Daten bereitzustellen bzw. vorzuenthalten. Und das ist schon sehr wertvoll^^

Das ist der Unterschied zwischen Dependeny Injection und dem großen Haufen Code, ersteres hat sich nicht ohne Grund durchgesetzt ;)

Wie ich SQL an der Stelle liebe.
Glückwunsch, du hast erkannt, dass deklarative Sprachen oft toller sind als Prozedurale Sprachen :) Gehe ich vollkommen konform mit ;)

Ich kann den Request aber auch irgendwie weitergeben

Aber dann hat mein Kommunikationsmodul nur noch zur Aufgabe Requests weiterzudeligieren und im Falle eines "Protokollfehlers" rauszufiltern und wegzuschmeissen.
Da ich später noch ein Bluetooth-Kommunikationsmodul, über das die gleiche Funktionalität verfügbar ist, hinzufügen möchte, beschränken sich die Kommunikationsmodule nur auf den Kommunikationsteil, ist doch völlig logisch?



Was ich damit sagen möchte, das ist alles so sehr verwoben, dass es nicht viel Sinn macht zu versuchen den Algorithmus da aufzuteilen.
Oder: der Algorithmus, wie du ihn dort aufgeschrieben hast, ist zu kompliziert.
Einfach mal vernünftig modellieren, dann kann man damit vielleicht was anfangen ;)

Eine Prozedur, die die Daten erhält, sie erhalten kann oder whatever ist hier wesentlich besser geeignet als der Versuch den Algorithmus aufzuteilen, was ja auch gar nicht geht.
Wie würde denn die Prozedur aussehen? 2345 Zeilen und die tiefste Schachtelung etwa 19? ;)

Also wie man sieht, kann man Algorithmen schon etwas aufteilen, ABER ich muss Steuerungskontext Daten mitdurchschleifen?!
Eine andere Lösung sehe ich da nicht?!
Oder seht ihr da eine andere Lösung?
Ich hab keine Ahnung von der Domäne, deshalb kann ich nur "vielleicht, vielleicht auch nicht" sagen...
Wie schon mal gesagt: völlig Ahnungslos vollkommen kontextlose Dinge zu modellieren geht immer schief, und in dem Fall hab ich weder Ahnung noch Kontext.
Generell gibts aber durchaus sowas wie Context and Dependency Injection, ist oftmals ganz hilfreich bei sowas...

-der Versuch einen zentralen Algorithmus in einen dezentralen umzuwandeln, was oft nicht funktioniert
Ganz im Gegenteil: meistens funktioniert das wunderbar. Macht man btw auch in prozeduraler Programmierung (ich weiß, ist schwer zu glauben, aber die meisten Programme haben mehr als eine Prozedur).

-ich brauche das Objekt an der Stelle, wo ich etwas mit Ihm bzw. dessen Daten anstellen möchte //siehe addItem Beispiel
Joa, üblicherweise brauche ich Objekte, wenn ich sie ändern will. Finde ich jetzt auch nicht sehr störend.
Wenn ich irgendein Objekt gar nicht haben will, will ich es auch üblicherweise nicht verändern (ich find es schon ganz geil, dass meine Kaffeemaschine nicht plötzlich die Sonneninnentemperatur ändert...)

-unnötige Abhängigkeiten -> sobald ich membervariablen in einem Objekt zusammenfasse und auf diesem Methoden definiere, so ist die Methode immer abhängig von ALLEN Membervariablen, auch wenn nicht alle benötigt werden.
Stichwort: sinnvolle Modellierung. Wenn man nicht zusammengehörende Daten beliebig zusammenfasst, bekommt man *immer* Probleme - völlig unabhängig welches Konzept und welche Sprache und sogar in der realen Welt.

"sobald ich [ganz prozedural viele Variablen in einem Struct] zusammenfasse und [Prozeduren, die dieses Nutzen] definiere, so ist die [Prozeduren] immer abhängig von ALLEN [Variablen des Structs], auch wenn nicht alle benötigt werden."

-sehr unflexibel -> siehe Beispiel mit ActionObjekt/NormalenObjekt/SwitchCases
Wie gesagt: der Punkt an sich ist schon völliger Unsinn.
 
Zuletzt bearbeitet:

AndiE

Top Contributor
Einsprungspunkt in PP:
Code:
// Header
void functuin1();
void function2();

main(){
int auswahl;
switch(auswahl=menu()){
case 1: function1();
break;
case 2:function2();
break;
}

Einsprungspunkt in OOP
Java:
public class App{

static vord main(){
DatenObjekt do= new DatenObjekt();
AnsichtObjekt ao= new AnsichtObjekt(do);
ao.start();

Auf den ersten Blick scheint hier nur die switch-Anweisung in die AO-Klasse verrutscht. Für mich ist es aber unübersichtlich, die Header-Dateien vorher anzugeben. Noch blöder fand ich bei C, dass auch die structs in der Header-Datei liegen. Bei C++ hat man wenigstens die gesamte Klassendeklaration in der Header-Datei und nur die Methoden in der cpp.

Und hier unterscheidet sich OOP von PP ganz gewaltig. Auch sowohl die Konsolenanwendung als auch eine GUI wird im PP immer eine Zahl für die Auswahl zurückgeben. In der OOP ist das nicht so eindeutig. Der ActionListener wird bei einer GUI die entsprechende Methode aufrufen. Auf der Konsolenebene kann man das mit einem switch machen, aber auch anders.

Und genau das ist der Vorteil. Ich muss nicht den ganzen Top-Down-Botton-Up-Zweig durchgehen und Dumpings setzen. Ich kann auch seperat Funktionalitäten( Klassen) schreiben und separat testen. Das könnte ich bei PP auch, aber ich kann Klassen durch einen Status beschreiben und den testen. Bei der PP werden Datenobjekte nie als solches gesehen und ich kann ihren Status auch nicht testen. Das ist von der Modellierung her ein gewaltiger Unterschied.

Und aus diesem Ansatz heraus, das Objekte einen Status haben, erwächst ja auch die Grundlage, dass Nachrichten bei der OOP den Status der Objekte ändern. Dadurch kann ich letztendlich auch Multitasking und Threads(parallele Abarbeitung) machen, aber das nur so am Rande.
 

mrBrown

Super-Moderator
Mitarbeiter
Für mich ist es aber unübersichtlich, die Header-Dateien vorher anzugeben. Noch blöder fand ich bei C, dass auch die structs in der Header-Datei liegen. Bei C++ hat man wenigstens die gesamte Klassendeklaration in der Header-Datei und nur die Methoden in der cpp.
Das sind Sprachdetails, die nichts mit dem Konzept zu tun haben...

Und hier unterscheidet sich OOP von PP ganz gewaltig. Auch sowohl die Konsolenanwendung als auch eine GUI wird im PP immer eine Zahl für die Auswahl zurückgeben. In der OOP ist das nicht so eindeutig. Der ActionListener wird bei einer GUI die entsprechende Methode aufrufen. Auf der Konsolenebene kann man das mit einem switch machen, aber auch anders.
Ich wüsste nicht, warum man in PP nicht Listenerbasiert arbeiten sollte? Man kann da ja auch durchaus Funktionen übergeben...

Dadurch kann ich letztendlich auch Multitasking und Threads(parallele Abarbeitung) machen, aber das nur so am Rande.
Das geht auch in PP :p
Und sogar noch deutlich besser mit Funktionaler Programmierung, die völlig ohne veränderlichen Zustand auskommt...
 

AndiE

Top Contributor
Nun müssen wir vielleicht auch mal sehen, was wir genau meinen. Nehme ich das "Prozedurale Programmierparadigma", dann sage ich, dass ich da den Programmablauf eindeutig in einem PAP oder NSD abbilden kann. Ich kann den Ablauf selbst bei strukturierter Programmierung mit einem Instruction-Pointer nachvollziehen. Nebenläufige Prozesse werden hardwaremäßig abgedeckt(DMA, CRT). Schon die Arbeit mit Prozessen widerspricht diesem Paradigma, wie ich es definiert habe. Selbst die Steuerung der Abarbeitung mit festgelegter Bearbeitungszeit( Drehscheibe), benötigt doch Objekte im Speicher, die Informationen zur Abarbeitung uns zum Status der Anwendungen haben. Also letztendlich Objekte im Sinne der OOP. Diese Abarbeitung, dass ich einen Prozess aufrufen der dann die anderen startet, finde ich typisch in OOP. Im Prinzip reiche ich alle Prozesse an die Hauptnachrichtenschleife weiter, die jedoch nicht das Programm wie bei der PP, sondern das Betriebssystem hält. Erfolgt eine zeitlang keine Nachricht, geht das System in "Idle"-Modus über und zeigt i.d.R. den Bildschirmschoner.
Natürlich hat man das bei den "*-Commander" auch ohne OOP gemacht(glaube ich jedenfalls), aber die Übergabe von Funktionen ( void proc( function func1) ist meiner Ansicht nach nicht typisch PP.
 

mrBrown

Super-Moderator
Mitarbeiter
Schon die Arbeit mit Prozessen widerspricht diesem Paradigma, wie ich es definiert habe.
Wie *du* es definiert hast. Für eine Diskussion darüber sollten wir aber lieber bei einer allgemein gültigen Definition bleiben.

Nehme ich das "Prozedurale Programmierparadigma", dann sage ich, dass ich da den Programmablauf eindeutig in einem PAP oder NSD abbilden kann.
Sowohl in PAP (zb join und fork in Flowcharts) als auch in NSD (Parallel-Processing Symbol) lassen sich parallele Prozesse abbilden.

Selbst die Steuerung der Abarbeitung mit festgelegter Bearbeitungszeit( Drehscheibe), benötigt doch Objekte im Speicher, die Informationen zur Abarbeitung uns zum Status der Anwendungen haben. Also letztendlich Objekte im Sinne der OOP. Diese Abarbeitung, dass ich einen Prozess aufrufen der dann die anderen startet, finde ich typisch in OOP.
Bitte lies dir noch mal die Definition von OOP im allerersten Beitrag durch.
Daten im Speicher sind noch lange keine Objekte im Sinne von OOP und haben mit OOP erstmal gar nichts zu tun. Der relevante Teil, Objekte bestehen aus Daten und Methoden und kommunizieren über Messages, ist bei "Daten im Speicher" keineswegs erfüllt.

Im Prinzip reiche ich alle Prozesse an die Hauptnachrichtenschleife weiter, die jedoch nicht das Programm wie bei der PP, sondern das Betriebssystem hält. Erfolgt eine zeitlang keine Nachricht, geht das System in "Idle"-Modus über und zeigt i.d.R. den Bildschirmschoner.
Keine Ahnung was du damit meinst, aber ein Bildschirmschoner hat sicherlich nichts mit PP vs OOP zu tun.

die Übergabe von Funktionen ( void proc( function func1) ist meiner Ansicht nach nicht typisch PP.
nur "nicht typisch" oder widerspricht es der Definition? ;)
 

AndiE

Top Contributor
Gut. das war jetzt zu doll. Aber um den Unterschied und den Vorteil zwischen OOP und PP mal zu demonstrieren, hilft der hier auch so strapazierte Taschenrechner.

In der OOP würde ich eine Zahl als Objekt der Klasse "Zahl" definieren, die die Methoden "clean, addDigit, add, sub, div, mul und get" hat. Diese Methoden "Löschen, Fügen eine Stelle hinzu, Addieren, Subtrahieren, Dividieren, Multiplizieren und geben das Ergebnis aus." Offensichtlich kann ich diese Klasse "Zahl" einfach in die Klasse "Money" umwandeln, um Geldbeträge in Cent oder 1/10-Cent abzuspeichern. Daneben kann ich die Klasse natürlich auch für sich selbst testen, ohne eine GUI oder Menu-Stuktur zu haben.
Und bei einer Warenverwaltung bedeuten Item.add() und Bill.add() beides verschiedene Dinge: einmal das Hinzufügen einer Ware und einmal das Hinzufügen eines Rechnungspostens. Ich kann somit die Camel-Notation umgehen und die Methodennamen werden kürzer.
Das alles lässt doch OOP viel besser planen und leichter entwickeln.
Natürlich kann ich den Taschenrechner mit Pp erstellen, aber es ist doch verständlicher wenn die Klasse "Rechner" zwei "Zahlobjekte" "res"(result) und "op"(operator) hält. Jeder Eingabe eines Zeichens ruft dann entweder eine Nachricht für das Objekt res oder op auf. Damit habe ich eben auch Darstellung und Berechnung voneinander getrenn und die Fehlersuche wird leichter. Statt "case '*': z= e1+e2;" rufe ich auf "case '*' : erg.mul(op);" . Ich finde letzteres besser.
 

mihe7

Top Contributor
nur "nicht typisch" oder widerspricht es der Definition?
Der Punkt dürfte sein, dass man nicht dadurch prozedural programmiert, indem man Objekte auf structs und function pointer abbildet. Gleichzeitig sind function pointer wohl kein notwendiges Kriterium für PP. Insofern vereinfacht es die Sache schon, wenn man sie aus der Betrachtung rausnimmt.
 

mrBrown

Super-Moderator
Mitarbeiter
Und bei einer Warenverwaltung bedeuten Item.add() und Bill.add() beides verschiedene Dinge: einmal das Hinzufügen einer Ware und einmal das Hinzufügen eines Rechnungspostens. Ich kann somit die Camel-Notation umgehen und die Methodennamen werden kürzer.
Na, das ist aber auch nur ein Pseudo-Beispiel...

Ob man jetzt item.add() oder add(item) schreibt, nimmt sich vom Schreibaufwand wirklich nichts (außer hier der gesparte Punkt in der PP Variante :p ).
Abgesehen davon kann man durchaus eine PP-Sprache schreiben, in der das erste Argument auch in "Prefix-Notation" gegeben werden kann. Analog, zu Java mit Lombok und statischen Methoden.
Und genauso könnte man eine OO-Sprache schreiben, die zwar völlig Objektorientiert ist, in der aber der Message-Empfänger als erstes Argument übergebe werden muss ;)

Natürlich kann ich den Taschenrechner mit Pp erstellen, aber es ist doch verständlicher wenn die Klasse "Rechner" zwei "Zahlobjekte" "res"(result) und "op"(operator) hält. Jeder Eingabe eines Zeichens ruft dann entweder eine Nachricht für das Objekt res oder op auf. Damit habe ich eben auch Darstellung und Berechnung voneinander getrenn und die Fehlersuche wird leichter. Statt "case '*': z= e1*e2;" rufe ich auf "case '*' : erg.mul(op);" . Ich finde letzteres besser.

Grad so ein Rechner lässt sich ganz wunderbar mit Stack (reine Datenstrukturen sind ja wohl erlaubt? sonst halt n Array...) und Prozeduren abbilden, vor allem in polnischer Notation ;)

Ich bevorzuge zwar auch die OO-Variante - aber dann doch bitte nicht case '*' : erg.mul(op); (was auch nicht wirklich schöner ist als case '*': z= e1+e2;, eher hässlicher), sonder Objekt-Orientiert und gleich operand.calc(first, second) ;)


Der Punkt dürfte sein, dass man nicht dadurch prozedural programmiert, indem man Objekte auf structs und function pointer abbildet. Gleichzeitig sind function pointer wohl kein notwendiges Kriterium für PP. Insofern vereinfacht es die Sache schon, wenn man sie aus der Betrachtung rausnimmt.
Das man nicht mehr wirklich p. p., wenn man beide miteinander verbindet, stimme ich voll zu, wurde ja auch schon weiter oben erwähnt ;)

Aber beide für sich genommen sind doch wesentlichen Teile von Prozeduraler Programmierung. Zumindest für eine Nachbildung von Listenern/Callbacks/HOF reicht auch die Übergabe von Prozeduren aus - aber keine Ahnung, ob man dass dann noch Prozedural nennt, wird aber zumindest häufig benutzt.
 

mihe7

Top Contributor
wurde ja auch schon weiter oben erwähnt
Sorry, das habe ich wohl übersehen.

Ich versuche mich, dem Thread zu nähern :) Mir fehlt die Abgrenzung zwischen PP und OO. Es gibt in dem Thread zwar eine Definition von OO, nicht aber von PP (außer ich habe auch diese nicht gesehen).

Der Argumentation von @knotenpunkt bzgl. der Flexibilität von PP kann ich nicht ganz folgen. Klar, es ist einfach in PP neue Prozeduren hinzuzufügen - so lange diese mit bereits existierenden Typen arbeiten. Umgekehrt ist es sehr viel aufwändiger, neue Typen hinzuzufügen. Außerdem steht der Abhängigkeitsgraph der betreffenden Prozeduren bereits zur Übersetzungszeit fest (außer man verwendet function pointer...) - was daran flexibel sein soll, ist mir nicht klar. Die Kanten zeigen dabei immer in Richtung low-level. Eine kleine Änderung einer Funktion auf niedriger Ebene und das Programm ist im A...
 

AndiE

Top Contributor
@mrBrown: Der Taschenrechner war ein Beispiel, um OOP und PP miteinander zu vergleichen. Dabei gehe ich davon aus, dass die zugrundeliegende Maschine mit wahlfreiem Speicherzugriff arbeitet. Bei einer Stack- oder Queue-basierten Maschine sehe ich das Problem, dass der Stack typischerweise nur Daten eines Typs aufnehmen kann. Übrigens auch ein typisches Array. Man muss also hier auch Möglichkeiten schaffen, Daten auf dem Speicher abzulegen und wieder abzurufen. Wobei ich das hier nicht soweit auseinanderteilen möchte: Offensichtlich gibt es ja auch bei einem Programm mit wahlfreien Zugriff einen Adressstack, um die Unterprogramm-Aufrufe durchführen zu können und einen Datenstack, um Daten zwischen den Prozeduren übergeben zu können(der kann auch ein Queue sein).

Eine der wichtigsten Dinge bei der OOP, selbst wenn ich mich an der Smalltalk-Syntax orientiere, ist doch, dass ein Aufruf typischerweise nicht nur einfach Präfix wie
Code:
proc(arg)
, sondern doppelt Präfix
Code:
obj.meth(member)
. erfolgt. Das bedeutet, dass ich dem Objekt obj eine Nachricht meth mit dem Argument member( oder mehreren) schicke. Auf den ersten Blick ist das bei der OOP zwar mehr Schreibarbeit, aber es bringt mir Sicherheit. Im übertragenen Sinne kann ich doch bei der PP sagen "Ich bin hier mitten im Wald und kaufe mir ein Brot". Bei der OOP dagegen habe ich "Bäcker, gib mir ein Brot". Und genau beim PP-Beispiel ist dann im täglichen Programmiergeschäft die Frage im Code: "Wo kommen diese Werte plötzlich her, die da benutzt werden?". Vergleichbare Sammelklassen mit final-Werten in OOP(Java) würden mit Global.PRICE_PER_DAY daherkommen. Dann weiß man als Programmierer aber auch, wo diese Konstante zu sehen ist, und zu ändern ist.

Ich gehe übrigens nicht mit der Aussage des 1. Posts mit, dass Vererbung keine OOP ist. Ich habe zwar nicht gefunden, wie Klassen in Smalltalk definiert werden, würde aber sagen, es ist da auch möglich, die Funktionalität der Klasse mittels Nachricht zu erweitern, um so Member und Methoden einzufügen. Ich denke nämlich, dass das die konsequente Weiterführung dessen ist, was beim Übergang von der maschinennahen zur prozeduralen Programmierung erfolgt ist, nämlich die zunehmende Abstraktion. Wenn ich also Kontakte mittels Bitmuster schalte, dann würde ich das in der nächsten Stufe so abstrahieren, dass Kontakt_ein(int nummer) aufgerufen wird. Wie das erfolgt ist dann doch egal. Und diese PP-Darstellung würde ich doch in OOP so erweitern, dass ich erstmal eine Klasse "Kontakt" habe und diese erweitere zu solchen mit Selbsthaltung und verzögertem Ein- oder Ausschalten usw.
 

mrBrown

Super-Moderator
Mitarbeiter
@mrBrown: Der Taschenrechner war ein Beispiel, um OOP und PP miteinander zu vergleichen. Dabei gehe ich davon aus, dass die zugrundeliegende Maschine mit wahlfreiem Speicherzugriff arbeitet. Bei einer Stack- oder Queue-basierten Maschine sehe ich das Problem, dass der Stack typischerweise nur Daten eines Typs aufnehmen kann. Übrigens auch ein typisches Array. Man muss also hier auch Möglichkeiten schaffen, Daten auf dem Speicher abzulegen und wieder abzurufen. Wobei ich das hier nicht soweit auseinanderteilen möchte: Offensichtlich gibt es ja auch bei einem Programm mit wahlfreien Zugriff einen Adressstack, um die Unterprogramm-Aufrufe durchführen zu können und einen Datenstack, um Daten zwischen den Prozeduren übergeben zu können(der kann auch ein Queue sein).
Ich glaube wir können voraussetzen, dass es in jeder Hochsprache Datenstrukturen wie Queue und Stack gibt...


Eine der wichtigsten Dinge bei der OOP, selbst wenn ich mich an der Smalltalk-Syntax orientiere, ist doch, dass ein Aufruf typischerweise nicht nur einfach Präfix wie
Code:
proc(arg)
, sondern doppelt Präfix
Code:
obj.meth(member)
erfolg. Das bedeutet, dass ich dem Objekt obj eine Nachricht meth mit dem Argument member( oder mehreren) schicke.

Ein passenderes Beispiel wäre meth(obj, arg) vs obj.meth(arg) - das ist nur ein Syntax-Unterscheid, Objekt-Orientiert oder Prozedural kann aber beides sein.
Ein Beispiel hab ich extra genannt mit Java+Lombok. Damit lassen sich vollkommen prozedurale Aufrufe von statischen Methode in dem ""OO-Stil" aufrufen: sort(int[] array) lässt sich dann statt sort(ints) als ints.sort() aufrufen. Beides gegensätzliche Syntax, obwohl es identisch ist.

Auf den ersten Blick ist das bei der OOP zwar mehr Schreibarbeit, aber es bringt mir Sicherheit. Im übertragenen Sinne kann ich doch bei der PP sagen "Ich bin hier mitten im Wald und kaufe mir ein Brot". Bei der OOP dagegen habe ich "Bäcker, gib mir ein Brot".
Ja, aber der Unterschied kommt doch nicht durch die Syntax, sondern durch das Konzept dahinter.
Und wenn man drüber diskutiert, sollte man nicht mit der (quasi beliebigen) Syntax argumentieren, sondern mit dem Konzept.
Typsicher sind beide Varianten gleichermaßen.
Und ob man jetzt schreibt gibMirBrot(Bäcker) oder Bäcker.gibMirBrot() ist auch egal - in beiden Fällen brauche ich eine Bäcker-(Objekt|Type), und eine Methode, deren Interna ich nicht kenne, wird ausgeführt. Der wesentliche Unterschied liegt woanders ;)

Ich gehe übrigens nicht mit der Aussage des 1. Posts mit, dass Vererbung keine OOP ist.
Vererbung ist keine OOP ist eine völlig andere Aussage als OOP hat Vererbung. Nur der zweiten Aussage wurde widersprochen.
Und ja, es gibt genügend OO-Sprachen, die keine Vererbung haben.
 

AndiE

Top Contributor
Lasst uns noch mal zum Stack zurückkommen, und nehmen wir mal an, ich benötige einen Integer-Stack von einer Tiefe von 8. Rein prozedural würde ich zwar ein Modul "stack" meinem Programm hinzufügen. Dann müsste ich doch als erstes den notwendigen Speicherplatz besorgen, also allokieren. Dabei bekomme ich die Adresse, wo mein Stack im Speicher losgeht. Dann würde je nach Sprache so etwas wie Zeigerarithmetik kommen, mit deren Hilfe ich auf die Speicherplätze zugreifen kann, um so das Stackverhalten zu bekommen. Auch wenn ich das mit Funktionen kapsele, müsste ich doch immer als Argument den Adresspunkt aufrufen, wo der allokierte Speicher losgeht.
Alternativ kann ich aber auch ein OOP-Modul laden, das dann als Blaupause für ein Objekt steht, das ich mit einer nachricht instanzieren kann. Ja nach Syntax kann ich dann dem Objekt Nachrichten schicken, wie groß der Stack sein soll und dass ich Daten abrufen will. Hier kann ich nach der Instanzierung direkt auf mein Datenobjekt zugreifen.

Der Unterschied zwischen diesen beiden Modulen besteht für mich darin, dass ein PP-Modul eine Reihe von Methoden enthält aber selbst nichts im Speicher tut. Bei einem OOP-Modul wird nun aber ein Objekt als Abbild der im Modul gemachten Deklarationen angelegt, das auch gleich den notwendigen Speicherplatz belegt.

Nun gibt es aber auch noch was dazwischen, das ich im Perl-Umfeld als IOP(indirekte objektorientiere Programmierung) kennengelernt habe. Dazu erkläre ich mal, dass jedes Programm einen Startpunkt hat. Scriptsprachen fangen meist am Anfang an, andere nach einer Main-Funktion. rein PP erfolgen nun eine Reihe strukturiert angelegter Funktionsaufrufe. Rein OOP-mäßig dagegen würde man ein Objekt anlegen und in diesem eine Startfunktion aufrufen, wenn man es nicht schon beim Anlegen startet(mach ich nicht gerne). Leider machen das nicht alle Programmierer. Stattdessen entwickeln sie hinter dem Einsprungspunkt eine Hauptprozedur, wobei sie aber (vorgegebene) Objekte implementieren und deren Methoden als Funktionen nutzen. Das ist also im Prinzip ein Misch-Masch aus PP und OOP.

PS: Ich beziehe mich bei meinen Beiträgen auf Erfahrungen mit C ,C++, C#, Forth, (etwas)Modula, (etwas )Pascal( auch mit OOP), Java, Perl(auch mit OOP) und etwas (Linux)-Shell. Ruby und Python sind mir so gut wie nicht geläufig. Natürlich ist das nur ein verschwindend geringer Anteil der vorhandenen Programmiersprachen.
 

mihe7

Top Contributor
@AndieE Ja, man hat bei PP sozusagen "lose Kopplung" zwischen Algorithmen und Daten. Das was Du als "Misch-Masch" aus PP und OOP beschreibst, nenne ich PP im OO-Gewand. Die Fälle sind leider oftmals sehr subtil und führen zu unerwünschten Abhängigkeiten.
 

mrBrown

Super-Moderator
Mitarbeiter
Lasst uns noch mal zum Stack zurückkommen, und nehmen wir mal an, ich benötige einen Integer-Stack von einer Tiefe von 8. Rein prozedural würde ich zwar ein Modul "stack" meinem Programm hinzufügen. Dann müsste ich doch als erstes den notwendigen Speicherplatz besorgen, also allokieren. Dabei bekomme ich die Adresse, wo mein Stack im Speicher losgeht. Dann würde je nach Sprache so etwas wie Zeigerarithmetik kommen, mit deren Hilfe ich auf die Speicherplätze zugreifen kann, um so das Stackverhalten zu bekommen. Auch wenn ich das mit Funktionen kapsele, müsste ich doch immer als Argument den Adresspunkt aufrufen, wo der allokierte Speicher losgeht.
Oder: ich rufe eine Prozedur auf, die newStack heißt, übergebe der die Länge, und bekomme einen stack zurück, und rufe dann mit dem push(stack, 1) auf, um etwas auf den Stack zu legen. Nichts mit direkter Zeigerarithmetik oder Adressen oder ähnlichem ;)

Alternativ kann ich aber auch ein OOP-Modul laden, das dann als Blaupause für ein Objekt steht, das ich mit einer nachricht instanzieren kann. Ja nach Syntax kann ich dann dem Objekt Nachrichten schicken, wie groß der Stack sein soll und dass ich Daten abrufen will. Hier kann ich nach der Instanzierung direkt auf mein Datenobjekt zugreifen.
vs in OO: [Stack alloc], womit man erstmal nur Speicher hat, den man noch initialisieren muss, damit man irgendwas machen kann: Stack *stack = [[Stack alloc] initWithSize:5]. Und das ganze gibt mir einen Zeiger zurück, an den man dann Nachrichten schicken kann: [stack push:1]

Wie gesagt: die Unterschiede, die du aufführst, sind im wesentlichen Unterschiede zwischen Sprachen und Implementierungen, aber nicht den Konzepten ;)

Die relevanten Konzepte merkt man bei solchen Vergleichen noch überhaupt nicht, weshalb ich sie eigentlich für ziemlich ungeeignet halte, um irgendwen von OOP zu überzeugen (ich bin allerdings völlig pro OOP)
 

AndiE

Top Contributor
Wir reden hier aneinander vorbei, denke ich. Bei der PP habe ich doch an primitiven Datentypen: Charakter, Festzahl, Fließkommazahl. Komplexere Datentypen sind dann Arrays, Zeichenketten und Sammlungen von anderen Daten in einem Gebilde(Struct oder Record), denen aber die primitiveren Daten zu Grunde liegen. Ich habe versucht, das ziemlich allgemein zu halten, um das Konzept dahinter zu zeigen, wie ich es verstehe. Die Anwendung von Zeigern, die auf die Adresse von Variablen zeigen, ist nicht in allen PP-Konzepten gegeben, aber ich sage mal, er soll dazu gehören. Und hier habe ich dann den Fall, dass bei einem Aufruf "stack=newIntStack(5)" die Variable stack ein Zeiger auf ein int ist(int* stack). Natürlich kann dieses Verhalten für den Endprogrammierer im Verborgenen sein, aber konzeptionell geht es bei einem PP nicht anders.
Diese Aufrufmethode beim OOP ist mir noch nicht untergekommen. Ich kenne das so, dass man mit dem Schlüsselwort "new" das Objekt so anlegt, dass man damit dann arbeiten kann. Oft musste man mit "delete" hinterher auch aufräumen, wenn kein Garbage Collector vorgesehen war. Übrigens gibt das Schlüsselwort "new" eine Referenz auf das angelegte Objekt zurück. Und mit dieser Referenz( Stack* stack) kann ich dann arbeiten, indem ich die Methoden dieses Objektes als Nachrichten aufrufe.

Ich bin übrigens auch voll OOP und ärgere mich, wenn Leute im Studium und Ausbildung mit IOP (PP in OOP) arbeiten, was ich weiter oben erläutert habe.

Und die Konzepte, die hier angezweifelt werden, kann und sollte man auch bei kleinsten Programmen anwenden. So wie es BlueJ tut. Hier im Forum sehe ich bei SWT und Swing immer wieder Erweiterungen von JFrame, in der dann auch die Main-Methode ist. Das mag funktionieren, eine Anwendung der Regeln der OOP ist es nicht. Ebenso eben der Taschenrechner. Ich muss schon bei solch kleinen Dingen mal überlegen, dass jedes Ding ein Objekt ist, also auch die Zahlen. Und bei sowas läßt sich doch auch der Nachrichtenaustausch viel besser verstehen.
 

mrBrown

Super-Moderator
Mitarbeiter
Wir reden hier aneinander vorbei, denke ich. Bei der PP habe ich doch an primitiven Datentypen: Charakter, Festzahl, Fließkommazahl. Komplexere Datentypen sind dann Arrays, Zeichenketten und Sammlungen von anderen Daten in einem Gebilde(Struct oder Record), denen aber die primitiveren Daten zu Grunde liegen.
Ist das in OOP groß anders? Objekte bestehen aus primitiveren Objekten, die wiederum aus primitiveren Objekten bestehen (also, von Methoden abgesehen.)
(Java Objekte sind im Speicher nicht großartig anders als Structs, im wesentlichen nur primitive Datentypen, Arrays und Zeiger auf andere Objekte.)

Die Anwendung von Zeigern, die auf die Adresse von Variablen zeigen, ist nicht in allen PP-Konzepten gegeben, aber ich sage mal, er soll dazu gehören. Und hier habe ich dann den Fall, dass bei einem Aufruf "stack=newIntStack(5)" die Variable stack ein Zeiger auf ein int ist(int* stack). Natürlich kann dieses Verhalten für den Endprogrammierer im Verborgenen sein, aber konzeptionell geht es bei einem PP nicht anders.

Zeiger vs nicht Zeiger ist sicher kein Unterscheidungsmerkmal von PP vs OOP-Sprachen.
Mit deiner Begründung geht es ohne Zeiger auch konzeptionell in OOP-Sprachen nicht anders, denn auch o gut wie jede OOP-Sprache arbeitet intern mit Zeigern (nichts anderes sind Referenzen in Java.)
Sagst ja sogar selbst im ersten Satz, dass Zeiger nicht in allen allen PP-Konzepten gegeben sind... ;)


Diese Aufrufmethode beim OOP ist mir noch nicht untergekommen. Ich kenne das so, dass man mit dem Schlüsselwort "new" das Objekt so anlegt, dass man damit dann arbeiten kann.
Ist Objective C, und ist deutlich näher an der ursprünglichen Definition von OOP, als zB Java.
Deiner Definition nach (weil man ständig mit Zeigern hantiert und es kein new gibt [gab, im Alter von 20 dann schon]), ist es dann aber wohl doch keine OOP-Sprache ;)


Und mit dieser Referenz( Stack* stack) kann ich dann arbeiten, indem ich die Methoden dieses Objektes als Nachrichten aufrufe.
*Das* ist der relevante Punkt, aber nicht ob es Zeiger gibt oder wie die Syntax beim Prozeduren/Methoden-Aufruf oder Nachrichten aussieht ;)
 

AndiE

Top Contributor
Wo habe ich denn geschrieben, dass das Vorhandensein der Anwendung von Zeigern den Unterschied macht? Es geht doch vielmehr um die Qualität des Zeigers. Bei einem PP-int-Stack ist der Zeiger auf den Stack ein (int* stack). Diesen würde ich mit stack=malloc(5*sizeof(int)) festlegen. Damit nicht wieder das bekannte Argument kommt, das muss für den Programmierer so nicht sichtbar sein. Die Methode um einen Wert auf den TOS zu legen müsste dann etwa als "pop(int* stack, int wert)" deklariert und mit pop(stack,10) aufgerufen werden. Das wäre dann C-Notation.

Vergleichbar würde mit OOP aber Stack* stack= new Stack(5); einen Zeiger auf eine Instanz einer Blaupause mit dem Namen Stack ergeben. Un in dieser Blaupause steht dann drin, dass die Construktor genannte Methode mit dem Namen der Blaupause einen int-Wert für die Stacktiefe bekommen kann. Um hier einen Wert auf diesen Stack zu legen, würde man einen Zuweisungsoperator "->" benutzen, und die in der Blaupause definierte Methode "pop(int wert)" als "stack->pop(10); aufrufen.(Syntax wie in C++).
 

mrBrown

Super-Moderator
Mitarbeiter
Wo habe ich denn geschrieben, dass das Vorhandensein der Anwendung von Zeigern den Unterschied macht?
Okay, du sagtest nur, PP ohne Zeiger ist konzeptionell nicht möglich, und über OOP hast du nichts gesagt ;)
Aber das ist eben bei OOP genauso.

Es geht doch vielmehr um die Qualität des Zeigers. Bei einem PP-int-Stack ist der Zeiger auf den Stack ein (int* stack). Diesen würde ich mit stack=malloc(5*sizeof(int)) festlegen. Damit nicht wieder das bekannte Argument kommt, das muss für den Programmierer so nicht sichtbar sein. Die Methode um einen Wert auf den TOS zu legen müsste dann etwa als "pop(int* stack, int wert)" deklariert und mit pop(stack,10) aufgerufen werden. Das wäre dann C-Notation
Nein, der Zeiger ist kein Zeiger auf int! Der Zeiger ist ein Zeiger auf struct Stack, das ist ein relevanter Unterschied!
Keine Ahnung, warum du jedes Mal aus meinem Stack einen int machst, das int und Stack sind zwei völlig unterschiedliche Dinge (oder wie speicherst du in deinem int die 10 Variablen?)


Um das noch mal Gegenüber zu stellen:


Objective-C:
Stack* stack = [Stack alloc];
stack = [stack initWithSize:5];
[stack push:10]
vs:
C:
Stack* stack = malloc(sizeof(Stack));
stack = initWithSize(stack, 5);
push(stack,10);

In beiden Fällen: Speicher allozieren.
In beiden Fällen: initialisieren.
In beiden Fällen: Methode ausführen.
Was man in beiden Fällen nicht sieht: ist Stack Objekt oder Struct? Sind alloc/malloc, initWithSize, push Messages oder Methoden?


Die Syntax ist nämlich nur Schall und Rauch - die kann in beiden Fällen identisch aussehen:
Python:
stack = Stack(5)
Stack.push(stack, 10)
stack.push(10)

In beiden Malen wird genau die gleiche Funktion ausgeführt - ist das jetzt Prozedural oder Objektorientiert? ;)
(Pointer gibts da btw keine ;) )



Und eben deshalb: die Syntax beim Aufruf ist völlig ungeeignet für einen Vergleich PP vs OOP.
Die relevanten Dinge (z.B. das von dir angesprochene mit "Blaupause") bekommt man an dieser Stelle nämlich überhaupt nicht mit.
Es gibt genügend gute Argumente für OOP, aber an dieser Stelle kommt eben keins wirklich zur Geltung - und das sage ich als absoluter OOP-Befürworter.
 

AndiE

Top Contributor
Wenn wir an dieser Stelle wieder an die Umfrage zurückkommen, dann kann ich bei "wiki" nachlesen was für OOP nach Alan Key bedeutsam ist. Und der erste Satz ist da :"Everything ist a object". Wenn dieser Satz nicht mehr gelten soll, wie müsste er dann lauten? "Somethings are objects, something not" oder "Nothing is an objekt"? Wenn der Satz von Alan Key die OOP beschreibt, beschreit eine Umkehrung keine "reine" OOP, sondern eine PP im OOP oder eine reine PP. Von den beiden Verneinungssätzen gehe ich übrigens vom 2. aus. Dabei ist ein "String" auch immer ein "char[]", und ich kann mit Index auch auf einzelne Zeichen zugreifen. Ebenso ist ein Stack ja erstmal eine Ansammlung gleichartiger Felder, also würde ich das auch als Array anlegen. Ob ich als Endprogrammierer dieses Array mit stack=stack(5) anlege oder mit int stack[5] anlege, ist doch egal. Das ist ja der Sinn der Prozeduren, die Entwicklung von der Maschinennähe hin zu leichter Lesbarkeit zu bringen.

Beim Nachdenken ist mir aber aufgefallen, dass der Threadopener eher die erste Verneinung meint, es also sowohl Datenobjekte gibt als auch Methodenobjekte, während man Alan Key bei der klassischen OOP so versteht, dass beides zusammengehört. Und nun hält er "kombinierte Objekte" für gescheitert. So verstehe ich den Anfang und das Anliegen des Threads jetzt.

Das würde doch bedeuten, dass sich eine "Money"-Funktionalität aus einem Objekt, dass den Betrag speichert (Slot - von Geldschlitz)und ausgibt, zusammensetzt, und einem, dass die Werte verändert(Banker). Wenn ich das jetzt weiterdenke, dann habe ich auch für das "Konto"zwei Objekte(Account und Clerk). Hier sieht man, dass die Methodenobjekte eher Menschen entsprechen, was zur Modellierung vielleicht sinnvoll sein kann. Das Problem für mich ist aber, dass in diesem Beispiel der "clerk" auch auf "slot" zugreifen kann und Geld entnehmen kann.
 

mrBrown

Super-Moderator
Mitarbeiter
Dabei ist ein "String" auch immer ein "char[]", und ich kann mit Index auch auf einzelne Zeichen zugreifen. Ebenso ist ein Stack ja erstmal eine Ansammlung gleichartiger Felder, also würde ich das auch als Array anlegen. Ob ich als Endprogrammierer dieses Array mit stack=stack(5) anlege oder mit int stack[5] anlege, ist doch egal.
Auch wenn du es so machen würdest, gibt es einen ziemlichen Unterschied zwischen stack und int* und auch zwischen string und char*


Beim Nachdenken ist mir aber aufgefallen, dass der Threadopener eher die erste Verneinung meint, es also sowohl Datenobjekte gibt als auch Methodenobjekte, während man Alan Key bei der klassischen OOP so versteht, dass beides zusammengehört. Und nun hält er "kombinierte Objekte" für gescheitert. So verstehe ich den Anfang und das Anliegen des Threads jetzt.
Ja, darum ging's im wesentlichen die ganze Zeit.
 

AndiE

Top Contributor
Mal ein Beispiel: Eine Möbelbaufirma betreibt drei Lager, ein Materiallager, ein Fertigteillager und ein Produktlager. Wenn ich jetzt der Idee von Knotenpunkt folge, habe ich ein einen Aufruf wie Dispatcher. Nimm("Stuhlbeine", 25). Dieser Dispatcher kann also auf alle drei Lager gleichermaßen zugreifen und Inhalte in diesen drei Lagern verändern und auch Inhalte abrufen. Was ist aber, wenn das räumlich größer ist, und die Lagerverwaltung aus drei räumlich getrennten Lagern mit drei Lagerverwaltern besteht? Dann macht es doch viel mehr Sinn, wenn die Lagerverwalter jeder über sein Lager Bescheid weiß und darüber Auskunft geben kann.

Anderes Beispiel: Für ein Ernährungsprogramm werden bei Lebensmitteln die Anteile von Kohlenhydraten, Fetten und Eiweißen je 100g und die durchschnittliche Portionsgröße gespeichert. Um jetzt auszurechnen, wie groß diese werte, z.B. bei 3 Burgern und zwei Salat ist, macht es doch Sinn, wenn Burger und Salat ihre Werte selbst ausrechnen. ergebis= salat.calgehalt+3*burger.calgehalt();

Wie kann ich das ohne OOP sonst so lösen, dass es nachvollziehbar ist.
 

mihe7

Top Contributor
Code:
struct essen { int kohlenhydrate, fett, eiweiss;  }
int calcgehalt(struct essen *v) { return (v->kohlenhydrate + v->eiweiss) * 4 + 9*v->fett; }
int ergebnis = calcgehalt(salat) + 3 * calcgehalt(burger);
 

mrBrown

Super-Moderator
Mitarbeiter
Mal ein Beispiel: Eine Möbelbaufirma betreibt drei Lager, ein Materiallager, ein Fertigteillager und ein Produktlager. Wenn ich jetzt der Idee von Knotenpunkt folge, habe ich ein einen Aufruf wie Dispatcher. Nimm("Stuhlbeine", 25). Dieser Dispatcher kann also auf alle drei Lager gleichermaßen zugreifen und Inhalte in diesen drei Lagern verändern und auch Inhalte abrufen. Was ist aber, wenn das räumlich größer ist, und die Lagerverwaltung aus drei räumlich getrennten Lagern mit drei Lagerverwaltern besteht? Dann macht es doch viel mehr Sinn, wenn die Lagerverwalter jeder über sein Lager Bescheid weiß und darüber Auskunft geben kann.
Der "Dispatcher" implizier ein Objekt, und das gäbe es in PP eher nicht ;)

In PP wäre das einfach nimm(materiallager, "Stuhlbeine", 25)

Anderes Beispiel: Für ein Ernährungsprogramm werden bei Lebensmitteln die Anteile von Kohlenhydraten, Fetten und Eiweißen je 100g und die durchschnittliche Portionsgröße gespeichert. Um jetzt auszurechnen, wie groß diese werte, z.B. bei 3 Burgern und zwei Salat ist, macht es doch Sinn, wenn Burger und Salat ihre Werte selbst ausrechnen. ergebis= salat.calgehalt+3*burger.calgehalt();
ergebnis = calgehalt(salat) + 3 * calgehalt(burger) ;)
 

AndiE

Top Contributor
Code:
struct essen{ int kh;int e; int  f, int prtion};

int gehalt(struct essen* v){ //Berechnung};

main(){
struct essen burger;

burger.kh=3;
burger.e=5;
burger.f=20;
burger.size=150;

int cal=gehalt(&burger);

printf(cal);
}

Das mag jetzt nicht ganz genau stimmen- da bin ich etwas aus dem Training.
In OOP(Java)
Code:
public class Essen{
int kh;
int e;
int f;
int size;

public Essen( int kh,int e, int f, int size){
// Werte festlegen
}

int gehalt(){
//Berechnung
}

};

public class Kalo{

public static void main(){
Essen e= new Essen (3,5,20,150); //kh,e,f,size
int cal=e.gehalt();
println(cal);
}

};

Für mich ist der Vorteil der 2. Version, dass in der main nur die statischen Daten deklariert, die ganze Dynamik aber in die Methoden im Essenobjekt delegiert werden. In der 1. Version habe ich das Anlegen der Daten UND deren Abarbeitung in der main und ihren Unterfunktionen.

Im anderen Fall, der der TO wohl meint, habe ich aber:

Code:
class Essen{
//Variablen
//Setter und Getter
};

class Rechne{

int gehalt(Essen e){
//Berechne
}

};

class Kalo{

main(){
Essen e= new Essen();
Rechne r= new Rechne();
int cal= r.gehalt(e);
println(cal)
}
};
 

mrBrown

Super-Moderator
Mitarbeiter
Für mich ist der Vorteil der 2. Version, dass in der main nur die statischen Daten deklariert, die ganze Dynamik aber in die Methoden im Essenobjekt delegiert werden. In der 1. Version habe ich das Anlegen der Daten UND deren Abarbeitung in der main und ihren Unterfunktionen.
main aus Variante 1:
C:
main(){
struct essen burger = {3,5,20,150};//kh,e,f,size
int cal=gehalt(&burger);
printf(cal);
}

main aus Variante 2:
Java:
public static void main(){
Essen e= new Essen (3,5,20,150); //kh,e,f,size
int cal=e.gehalt();
println(cal);
}

Da passiert nicht wirklich mehr in der einen Main, als in der anderen...
 

AndiE

Top Contributor
Der "Dispatcher" implizier ein Objekt, und das gäbe es in PP eher nicht ;)

Im Sinne der ersten Posts des Threadopeners würde dieser Dispatcher aber einem der Service-Objekte entsprechen, die dort mokiert wurden, also einem Objekt( also tatsächlich einem OO-Konstrukt), das aber nur Methoden enthält.

Und statt mit Sonnentemperatur und Motorleistung sind hier die Dinge etwas überschaubarer. Sicher können, Applikationen, die nach verschiedenen Mustern entwickelt werden, das Selbe machen. Daher kann man doch nicht davon Sprechen oder Schreiben, dass eines gescheitert ist.
 

mrBrown

Super-Moderator
Mitarbeiter
Im Sinne der ersten Posts des Threadopeners würde dieser Dispatcher aber einem der Service-Objekte entsprechen, die dort mokiert wurden, also einem Objekt( also tatsächlich einem OO-Konstrukt), das aber nur Methoden enthält.
Gehört denn das Lager, auf dem der Dispatcher arbeitet, zum Dispatcher (also als Instanzvariable) oder gibt es drei globale Lager (etwa static in Java) und der Aufruf findet völlig ohne Bezug zu irgendeinem Lager statt?
Ersteres wäre auch laut TO OOP.


Und statt mit Sonnentemperatur und Motorleistung sind hier die Dinge etwas überschaubarer.
Überschaubar heißt aber bei sowas auch oft soweit reduziert, dass die wesentlichen Dinge untergehen ;)
Polymorphie und Verbindung von Daten+Verhalten spielt dabei nämlich nur selten eine Rolle ;)
 

mihe7

Top Contributor
Durch Kapselung wird letztlich festgelegt, welche Teile eines Stücks Software von anderen Teilen nicht benutzt werden dürfen. Das hat natürlich nichts mit OO im Speziellen zu tun und lässt sich auch in PP erreichen, sei es z. B. durch Möglichkeiten, die die Sprache bietet (z. B. Headerfiles in C), "Disziplin" oder auch durch Aufteilung in mehrere separate Programme.

In jedem Fall führt Kapselung zu einer Definition eines "Inneren" und eines "Äußeren", die über eine gemeinsame Schnittstelle zusammenhängen. Damit wird allerdings auch definiert, welche Daten und/oder welche Prozeduren irgendwie zusammengehören. Kapselung ist also kein grundsätzliches Problem in PP.

Das sehe ich an anderer Stelle. Wir formulieren das Problem als Prozedur, die eine Eingabe bekommt und eine Ausgabe liefert. Eine Prozedur ist eine Aneinanderreihung von Anweisungen, die ihrerseits Prozeduraufrufe darstellen können. Wird das Problem zu groß, teilen wir es in kleinere Probleme (=Prozeduren) auf usw. Die Abstraktion erfolgt somit in Form von Prozeduren. Dadurch entsteht ein statischer Graph voneinander abhängiger Prozeduren.

In diesem Graph kann ich eine Dynamik während der Laufzeit nur durch entsprechende Kontrollstrukturen erreichen - der Graph steht aber zur Übersetzungszeit fest.

Dieses Manko kann ich in PP durch Funktionszeiger beheben. Dann bin ich aber m. E. dort angelangt, wo OO anfängt.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
K Modellierung Chat-Server (von OO ist gescheitert) Softwareentwicklung 14

Ähnliche Java Themen

Neue Themen


Oben