OOP: soll man jetzt alles in ne Klasse packen oder watt?



  • kartoffelsack schrieb:

    Ein guter C++Programmierer kann in C++ wahrscheinlich 'bessere' (im Sinn von ausdrucksstärkere Formulierungen und allem was daraus in Sachen Wartbarkeit etc. erwächst) Programme schreiben als ein guter Java-Programmierer, weil ihm die Sprache mehr Möglichkeiten dafür lässt.

    Nein. Java ist viel einfacher und deshalb auch besser wartbar.



  • kartoffelsack schrieb:

    Das sqrt-Beispiel ist ein gutes Beispiel dafür, dass es - nur weil es eine 'objektorientierte' Sprache ist - nicht immer sinnvoll ist, alles objektorientiert zu machen (bzw. dass Java eben doch keine 100% oo-Sprache ist, sonst ginge ja 1.1.sqrt() )

    Math.add()
    ist _nicht_ objekt orientiert.

    ich sehe hier keine objekte.

    Dazu braucht man aber halt mehr Erfahrung als in Java, wo es eben nur eine Möglichkeit gibt, die zwar nicht so perfekt passt aber im Endeffekt dasselbe erreicht.

    es gibt genug andere moeglichkeiten. zB eben 2.sqrt() waere eine davon 😉

    aber worum es geht ist, dass Math.add() kein bisschen objekt orientiert ist. Es ist nichts anderes als ein kompliziertes int add(int, int);

    und fuehrt dazu dass klassen in java oft nichts anderes sind als namespace ersatz.

    ob das jetzt gut ist oder nicht, weill ich mal nicht bewerten. es geht eher mehr darum dass die leute verstehen muessen dass eine klasse erstmal rein garnichts mit OOP zu tun hat. Und Java ist kein bisschen 100% OOP oder so... ich habe schon genug C programme in Java gesehen: klappt super, da Java C so aehnlich ist kann man da viel 1 zu 1 uebernehmen. Und nur weil es dann Foo.bar() heisst statt foo_bar() macht es das nicht OO 😉



  • Shade Of Mine schrieb:

    und fuehrt dazu dass klassen in java oft nichts anderes sind als namespace ersatz.

    "oft"? Übertreib mal nicht. Das betrifft vielleicht 2% aller Klassen, die man in einem Projekt hat.



  • Gregor schrieb:

    Shade Of Mine schrieb:

    und fuehrt dazu dass klassen in java oft nichts anderes sind als namespace ersatz.

    "oft"? Übertreib mal nicht. Das betrifft vielleicht 2% aller Klassen, die man in einem Projekt hat.

    dann halt manchmal. egal. tut eigentlich nicht viel zur sache.

    auch wenn ich denke dass man eben viele dieser globalen funktionen dann halt einfach in die klasse packt wo man sie braucht und so wirkt es nicht so viel. und die macht man dann auch nicht static, und schon ists OO :p ok, das ist wohl nur bei schlechten programmierern der fall.

    ich denke aber dennoch das java oft ein falsches gefuehl fuer anfaenger vermittelt. naemlich dass alles was in einer klasse ist, objekt orientiert ist. komplett unabhaengig davon was ein guter programmierer macht.

    java vertritt dieses "pure OO" und C++ ist eben nicht "pure OO". Dabei hat man im endeffekt doch aehnliche ansaetze wie man manches loest. zB math.sqrt ist nichts anderes als std::sqrt. Kein bisschen OO der ansatz, aber halt praktisch. wobei c++ eben namespaces als konzept bietet und diese sind was namespace funktionalitaet betrifft den java klassen ueberlegen (ich kann jederzeit einem namespace ne funktion hinzufuegen). aber darum soll es hier ja nicht gehen: sondern darum dass es, egal wie man es dreht und wendet: Math ist ein namespace und sqrt eine globale funktion.



  • Shade Of Mine schrieb:

    wobei c++ eben namespaces als konzept bietet und diese sind was namespace funktionalitaet betrifft den java klassen ueberlegen (ich kann jederzeit einem namespace ne funktion hinzufuegen).

    Och, das kann man auch anders sehen. In C++ können Elemente eines Namespaces überall zu finden sein. In Java hat man durch den Namen der Klasse (und des Packages) gleich den genauen Ort der entsprechenden Datei gegeben. Ist also übersichtlicher, wartbarer.

    ...das ist ja auch eigentlich DER negative Aspekt, wenn man über globale Funktionen redet. Man sieht nicht gerade auf den ersten Blick, welche es gibt und wo sie sind. Das führt dann natürlich zu einer geringeren Wartbarkeit, zu einer geringeren Produktivität usw.. Dieser negative Aspekt wird durch die Namespaces in C++ nur teilweise aufgehoben. In Java hat man das mit statischen Funktionen in Klassen komplett erschlagen. ...das einzige, was da schlecht ist, sind die statischen Imports, die es jetzt neu gibt. Keine Ahnung, warum die eingeführt wurden: Ich habe sie noch kein einziges mal genutzt und halte sie auch für ein schlechtes Feature. ...und das sehen viele Leute so, die sich mit Java beschäftigen.



  • beispiel:

    a=Math.sqrt(a);

    sieht ok aus, oder?
    Problem: a ist vom Typ MyInteger.

    ich definiere durch gewisse funktionen eben ein interface. zB dass ich die quadratwurzel aus einer zahl mit hilfe von sqrt ziehen kann. dazu muss ich aber in der lage sein sqrt fuer meine typen zu spezialisieren.

    deshalb finde ich namespaces schoener als klassen - weil sie naemlich erweiterbar sind.

    aber ich glaube wir schwenken vom thema ab 😉

    [edit]
    omfg, ich hab den spamschutz aktiviert
    [/edit]



  • Shade Of Mine schrieb:

    ich definiere durch gewisse funktionen eben ein interface. zB dass ich die quadratwurzel aus einer zahl mit hilfe von sqrt ziehen kann. dazu muss ich aber in der lage sein sqrt fuer meine typen zu spezialisieren.

    deshalb finde ich namespaces schoener als klassen - weil sie naemlich erweiterbar sind.

    Ach so... dann erweiterst Du bei Dir also den Namespace std für Deine eigenen Projekte? Und andere Bibliotheken, die Du nutzt... machen die das auch? Ich bin der Meinung, dass wenn Du eigene Typen hast, für die Du eine schon vorhandene Funktion überladen musst, Du auch einen eigenen "Namespace" dafür machen solltest. Wo kommt man denn da hin, wenn sich jeder seinen eigenen Namespace std "definiert". Ein Wartungsalptraum. Wenn ein anderer Deinen Code liest, wird er ganz schön lange nach der Definition dieser Funktionen suchen. ...oder bezog sich das nur auf Deine eigenen "Namespaces"? ...naja, da kannst Du Deine Erweiterungen ja eh problemlos da unterbringen, wo sie hingehören und bist nicht auf die Erweiterungsfähigkeit des Namespaces angewiesen.



  • Gregor schrieb:

    Ach so... dann erweiterst Du bei Dir also den Namespace std für Deine eigenen Projekte? Und andere Bibliotheken, die Du nutzt... machen die das auch?

    dann interpretiert er std::sqrt so, daß dies der name für's radizieren ist. und den darf er natürlich bei völliger bedeutungsbeibehaltung duch zuatzimplemetierung erweitern. man darf ja auch dort lebende templates spezialisieren oder von dort lebenden klassen erben.

    Ich bin der Meinung, dass wenn Du eigene Typen hast, für die Du eine schon vorhandene Funktion überladen musst, Du auch einen eigenen "Namespace" dafür machen solltest.

    warum?

    Wo kommt man denn da hin, wenn sich jeder seinen eigenen Namespace std "definiert". Ein Wartungsalptraum. Wenn ein anderer Deinen Code liest, wird er ganz schön lange nach der Definition dieser Funktionen suchen.

    ja. rechtsklick auf den funktionsnamen und aussuchen des kontextmenupunks "go to definition". ewig lange zwei sekunden später hat er sie. welche geschwindigkeiten bist du mit eclipse so gewohnt, daß du 2 sekunden (inklusive zweimal klicken) für ganz schön lang halten tust?



  • volkard hat das meiste schon gesagt, aber ich will noch etwas hinzufuegen:

    natuerlich fuege ich nicht nach belieben funktionen in std hinzu. Das waere natuerlich bloedsinn.

    Aber man muss das jetzt etwas aus einem objekt orientierten aspekt sehen: ich stelle ein interface fuer bestimmte operationen zur verfuegung: zB habe ich sqrt, copy, ...

    sqrt zB ist nichts anderes als eine operation auf einem numerischen objekt. ob das objekt jetzt ein int, double oder MyInteger ist, ist dem interface dabei egal.

    Wenn ich nun eine klasse MyInteger habe, dann wuerde ich gerne diese Klasse sich wie ein int verhalten lassen. Ich will also quasi das Math-Interface implementieren. Deshalb spezialisiere ich (oder ueberlade - schiess mich tot, keine ahnung was von beiden ich da am besten mache (zuviel java in letzter zeit ;))) sqrt fuer MyInteger.

    Oder eben copy, find, etc. fuer container - wenn ich einen eigenen container geschrieben habe, will ich ja dass er auch dieses interface implementiert.

    das mache ich natuerlich nicht dauernd und es waere falsch zu sagen man braucht dieses feature, denn von einem technologischem standpunkt aus tut es ein a.sqrt() genauso - aber es sind eben diese features die den code schoener gestalten.



  • volkard schrieb:

    Ich bin der Meinung, dass wenn Du eigene Typen hast, für die Du eine schon vorhandene Funktion überladen musst, Du auch einen eigenen "Namespace" dafür machen solltest.

    warum?

    Hab ich doch gesagt: Ich halte das nicht für besonders wartbar. Abgesehen davon könnte - sogar bei C++ - die Standardbibliothek auf lange Sicht offiziell erweitert werden. Dann kommt vielleicht eine Funktion mit gleicher Signatur in std rein, weil es sich gezeigt hat, dass sich eben jeder so eine Funktion selbst schreibt, weil sie gebarucht wird. Ok... dann gibt es also 2 Funktionen mit gleicher Signatur. Löscht Du dann Deine Funktion? Dann hat die Funktion aus dem offiziellen std aber vielleicht eine ganz leicht andere Semantik und plötzlich funktioniert Dein Code nicht mehr, ist also buggy. (um mal ein Negativ-Beispiel für eine entsprechende Erweiterung zu konstruieren) ...ok, bei EIGENEN Typen, die irgendwo in der Signatur auftauchen, ist das natürlich äußerst unwahrscheinlich. Aber es geht ja generell um die Erweiterungsfähigkeit des Namespaces.

    Ich bin halt der Meinung, dass std vom Standard her vorgegeben ist. Wenn man da etwas reinbringen will, soll man bei der Standardisierung mitarbeiten. Das ist kein Bereich, den man eigenmächtig anfassen sollte. ...und genau das Gleiche gilt für für jeden anderen Namespace einer externen Bibliothek.

    volkard schrieb:

    Wo kommt man denn da hin, wenn sich jeder seinen eigenen Namespace std "definiert". Ein Wartungsalptraum. Wenn ein anderer Deinen Code liest, wird er ganz schön lange nach der Definition dieser Funktionen suchen.

    ja. rechtsklick auf den funktionsnamen und aussuchen des kontextmenupunks "go to definition". ewig lange zwei sekunden später hat er sie. welche geschwindigkeiten bist du mit eclypse so gewohnt, daß du 2 sekunden (inklusive zweimal klicken) für ganz schön lang halten tust?

    Oh... ich habe noch nie die Zeit gemessen... aber wenn da irgendwo Math.sqrt in einem Javacode steht, dann sehe ich unabhängig von der genutzten IDE oder des genutzten Editors sofort, dass das sqrt aus der Standardbibliothek kommt. Setzen wir also mal 0,1 Sekunden an. ...Faktor 20 schneller und man ist noch nichtmal auf die IDE angewiesen. 🤡 Viel wichtiger halte ich allerdings, dass ich in keinster Weise von meinem Lesefluss gestört werde: Ich muss nicht zur Maus greifen, um zu erkennen, was dort Sache ist.



  • Ist Objektorientierung nicht genau der engegengesetzer Weg ?

    Man definiert ein Interface, aber man erweitert das Interface doch nicht. Wenn du eine neue Funktion im Namespace std für deinen MyInteger definierst, dann erweiterst du das Interface von sqrt.

    Der richtige Weg wäre einen Operator int() in deiner MyInteger-Klasse zu definieren ( oder eben float/double ). So das sqrt() diesen Operator aufrufen kann.

    Wenn ich einen std:sqrt(myinteger) sehe, dann schau ich erstmal stundenlang in die STL-Doku wo denn so ein sqrt definiert ist.

    Du erweiterts doch die Funktion std:sort() doch nicht um deine ShadeListe. Sondern du definierst in der Klasse ShadeListe das Interface iterator.



  • Gregor schrieb:

    konstruieren) ...ok, bei EIGENEN Typen, die irgendwo in der Signatur auftauchen, ist das natürlich äußerst unwahrscheinlich. Aber es geht ja generell um die Erweiterungsfähigkeit des Namespaces.

    nö. es geht ab jetzt um die erweiterung des std-namespaces für eigene typen.

    Ich bin halt der Meinung, dass std vom Standard her vorgegeben ist.

    (hier frage ich vorhin "warum" und du hast leer zurückgeantwortet. das vereinfache ich jetzt mal.)
    quatsch.

    Viel wichtiger halte ich allerdings, dass ich in keinster Weise von meinem Lesefluss gestört werde: Ich muss nicht zur Maus greifen, um zu erkennen, was dort Sache ist.

    ok, du weißt sofort, in welcher datei das steht. aber das istr fürchterlich egal, wo das steht. kannst immer hingehen.
    mir ist wichtig, daß ich die bedeutung von std::sort kenne und wer std::sort überlädt, wird sich dran halten.
    oder für ein einfacheres beispiel, nehmen wir mal std::swap.
    natürlich biete ich std::swap auch für eigene typen an.



  • ok, du weißt sofort, in welcher datei das steht. aber das istr fürchterlich egal, wo das steht. kannst immer hingehen.
    mir ist wichtig, daß ich die bedeutung von std::sort kenne und wer std::sort überlädt, wird sich dran halten.
    oder für ein einfacheres beispiel, nehmen wir mal std::swap.
    natürlich biete ich std::swap auch für eigene typen an.

    Ist das jetzt ein Scherz ? Für jeden Type den du hast erweiterst du die STL ?

    Was ist wenn jetzt 2 (oder besser 10) Leute an einem Projekt arbeiten. Diese 2 erweitern also für jeden Type den sie haben die STL. Das Ergebniss kannste dir doch Vorstellen oder ?
    Am Ende findet dann ein Kampf in einer Arena statt, wenn 2 Programmierer die STL für den gleichen Type erweitern. Naja das sorgt wenigestns für Aufregung und für sportliche Bewegung am Arbeitsplatzt.



  • DEvent schrieb:

    Man definiert ein Interface, aber man erweitert das Interface doch nicht. Wenn du eine neue Funktion im Namespace std für deinen MyInteger definierst, dann erweiterst du das Interface von sqrt.

    nein.

    ein interface ist eine beschreibung der schnittstellen. Wenn wir jetzt sagen ein Objekt implementiert IMath wenn es sqrt() anbietet.

    class MyInt : public IMath {
    public:
      virtual MyInt sqrt();
    };
    

    das wuerdest du vermutlich als OK einstufen, oder?

    aber was ist
    a.sqrt();
    denn anderes als
    sqrt(a);
    ?

    es ist das selbe, nur andere schreibweise.

    In LISP macht man es zB immer so: man definiert eine funktion sqrt() und implementiert die dann fuer alle "Klassen" die es anbieten.

    Der richtige Weg wäre einen Operator int() in deiner MyInteger-Klasse zu definieren ( oder eben float/double ). So das sqrt() diesen Operator aufrufen kann.

    ne, bloss nicht.

    Wenn ich einen std:sqrt(myinteger) sehe, dann schau ich erstmal stundenlang in die STL-Doku wo denn so ein sqrt definiert ist.

    wieso?

    sqrt ist doch perfekt dokumentiert: du schaust in die doku und da steht
    int sqrt(int); //ermittelt die quadratwurzel einer zahl und returned diese

    und dann muss ich nicht nachsehen ob das fuer double anders ist. weil es fuer double definitionsgemaess genau gleich ist.

    oder vielleicht steht da sogar nur
    template<typename T>
    T sqrt(T);

    da das waere ja nice, oder? reine interface definiton 🙂

    Du erweiterts doch die Funktion std:sort() doch nicht um deine ShadeListe. Sondern du definierst in der Klasse ShadeListe das Interface iterator.

    und da ein sort leider ewig dauern wuerde, ich aber mit ein paar tricks das sortieren enorm schnell hinbekomme (weil ich ja die implementationsdetails meiner ShadeList kenne) baue ich mir eine spezialisierung fuer std::sort.

    klar, es ist etwas "out of the box" denken noetig. aber OOP hat echt nichts mit Klassen zu tun. vergiss klassen. funktionen koennen genauso ein interface definieren.



  • DEvent schrieb:

    Ist das jetzt ein Scherz ? Für jeden Type den du hast erweiterst du die STL ?

    nein. wir spezialisieren.

    wenn ich eine Klasse Bitmap habe die intern halt nen char* auf die daten haelt, dann kann ich natuerlich bei einem std::sort() auf ein array von bitmaps jedesmal n copy machen, oder aber std::swap spezialisieren.

    dann spezialisiere ich std::swap so, dass er nur schnell die 2 pointer tauscht.

    Ich habe die STL nicht erweitert, es ist keine funktionalitaet dazu gekommen.

    ich haette genauso

    class Bitmap : public ISwapable {
    public:
      virtual void swap(Bitmap&);
    };
    

    machen koennen. nur dass es mit statischer polymorphie und std::swap einfach soviel besser ist.

    Was ist wenn jetzt 2 (oder besser 10) Leute an einem Projekt arbeiten. Diese 2 erweitern also für jeden Type den sie haben die STL. Das Ergebniss kannste dir doch Vorstellen oder ?

    ja. wunderbarer code.

    Am Ende findet dann ein Kampf in einer Arena statt, wenn 2 Programmierer die STL für den gleichen Type erweitern. Naja das sorgt wenigestns für Aufregung und für sportliche Bewegung am Arbeitsplatzt.

    wieso sollten sie das tun?
    dann kannst du genauso sagen es macht keinen sinn klassen zu verwenden, weil ja jeder eine funktion draw() einbauen koennte und dann hast du 10 draw() funktionen...

    ne, ne, ne. soviel uebersicht muss das team schon haben dass sie jede schnittstelle einer klasse nur einmal implementieren.

    std::swap ist nichts anderes als eine implementierung des interfaces ISwapable. die std::swap spezialisierung findet sich auch direkt in der datei mit der interface definition der klasse. also da kann man nix doppelt implementieren...

    PS:
    es wir keine neue funktion hinzugefuegt. der std namespace bekommt nicht mehr funktionalitaet. es kommt nichts neues dazu.

    ich implementiere lediglich ein interface. mehr nicht.

    nur dass das interface halt statisch ist und nicht dynamisch. (statische polymorphie ist auch polymorphie ;))



  • A* a = new A;
    

    ist auch nicht OO!
    hier wird der KLASSE A die Nachricht "new" geschickt, ein Objekt A zu erstellen. Im OO-System schickt man jedoch Nachrichten an OBJEKTE. Allgemein passen statische Funktionen nicht in das OO-Paradigma... was nun?

    zu namespaces vs. static imports:
    Klar, man hätte in java vielleicht Namespaces einführen können, da Math nicht wirklich eine schöne Klasse darstellt. Aber was ich in c++ doof finde, ist, dass ich bei Namespaces "::" und bei Objekten oder Klassen "." schreiben muss. In Java muss ich in der IDE "." schreiben, und schon listet mir die Code-Completion alles auf. In c++ muss ich vorher genau wissen, ob Math nun eine Klasse oder ein Namespace ist....



  • OOler schrieb:

    In c++ muss ich vorher genau wissen, ob Math nun eine Klasse oder ein Namespace ist....

    du musst wissen ob Math ein objekt ist oder ein namespace/klasse.

    bedeutender unterschied.

    aber wir wollen ja nicht die syntax diskutieren oder? denn die ist grausam, wie man es auch dreht und wendet.



  • Shade Of Mine schrieb:

    OOler schrieb:

    In c++ muss ich vorher genau wissen, ob Math nun eine Klasse oder ein Namespace ist....

    du musst wissen ob Math ein objekt ist oder ein namespace/klasse.

    bedeutender unterschied.

    stimmt :), so bedeutend ist aber dieser unterschied für diesen fall nicht. bei java ist es beim "." egal, ob du nun ein packet, klasse oder objekt "ansprichst". damit abstrahiert bereits die syntax die drei konzepte zur dekomposition deines systems.



  • OOler schrieb:

    damit abstrahiert bereits die syntax die drei konzepte zur dekomposition deines systems.

    ob das im entferntesten sinnvoll sein kann, sei dahingestellt.
    es wäre kein problem, das einer c++-ide auch beizubringen, daß man nur . oder :: tippt und auf beide gleich mit der komplettliste reagiert wird.



  • sqrt ist doch perfekt dokumentiert: du schaust in die doku und da steht
    int sqrt(int); //ermittelt die quadratwurzel einer zahl und returned diese

    und dann muss ich nicht nachsehen ob das fuer double anders ist. weil es fuer double definitionsgemaess genau gleich ist.

    Manche Funktionen sind Typenabhänig.
    Z.B. Die Division. int div(int x, int y) ist was anderes als double div(double x, double y). Auch die Funktion betrag() ist was anderes für normale Zahlen, Vektoren, komplexe Zahlen.
    Muss gestehen das ich mir bei diesem Argument nicht ganz sicher bin. Ist bestimmt ziemlich schwaches Argument.

    Mal das Beispiel div:
    int div(int x, int y).
    Man kann es verschieden Implementieren, man erhält verschiedene Ergebnisse.

    int div(int x, int y)
    {
        double y = (double)x / (double)y;
        return round_abwärst(y);
    }
    
    int div(int x, int y)
    {
        double y = (double)x / (double)y;
        return round_aufwärst(y);
    }
    
    int div(int x, int y)
    {
        double y = (double)x / (double)y;
        return schneide_nachkomme_ab(y);
    }
    
    int div(int x, int y)
    {
        return y = ganzzahlige_division(x, y);
    }
    

    naja usw usf.

    class Bitmap : public ISwapable {
    public:
      virtual void swap(Bitmap&);
    };
    

    Das Beispiel auf Namespaces bezogen würde doch eher so aussehen:

    namespace bitmap
    {
        void swap(Bitmap& b)
        {
            std:swap(b.intern_daten);
        }
    }
    

    Wieso sollte deine spezielle Funktion eigentlich überhaupt im Namespace std liegen? Gibt es dafür einen Grund (ausser den Leser des Codes zu verwirren) ?

    In der Bibliothek STL wird doch nirgendwo deine spezielle Funktion aufgerufen.

    dann kannst du genauso sagen es macht keinen sinn klassen zu verwenden, weil ja jeder eine funktion draw() einbauen koennte und dann hast du 10 draw() funktionen...

    Klar gibt es 10 draw()-Methoden, aber sie liegen in 10 verschiedenen Klassen. Du hast 10 draw()-Funktionen und sie liegen alle im namespace std.

    ne, ne, ne. soviel uebersicht muss das team schon haben dass sie jede schnittstelle einer klasse nur einmal implementieren.

    Eben nicht, die Sprache sollte dies "von Haus aus" absichern. Das war auch der Gedanke bei Namespace in C++, packages bei Java usw.

    Mir fällt auch grade ein, das du das Bitmap-Beispiel falsch hast.
    Das Interface Swapable kann nicht so sein, es müsste:

    public interface Swapable
    {
        void swap(Object a);
    }
    
    public class Bitmap implements Swapable
    {
       void swap(Object a)
       {
           Bitmap b = (Bitmap)a;
           b.spaw_data();
       }
    }
    

    Bei deiner "statischen Polymorphie" erweiterst du das Interface Swapable um eine Methode: void swap(Bitmap b);

    oder vielleicht steht da sogar nur
    template<typename T>
    T sqrt(T);

    Das wäre das optimale. Jetzt kann sqrt() mit allem umgehen, das den Operator double() anbietet. Was spricht den dagegen, das du so dagegen bist?


Anmelden zum Antworten