# Elementfunktionen vs. Nichtelementfunktionen in Java



## temi (4. Jun 2020)

JustNobody hat gesagt.:


> Die Kernproblematik ist aus meiner Sicht: In Java geht es um eine Objektorientierte Entwicklung. Man soll also mit Objekten arbeiten.
> 
> Das static geht aber komplett an diesem Konzept vorbei.


Hallo zusammen,
aufgrund des obigen Zitates habe ich mich an eine Passage aus "Effective C++" von Scott Meyers erinnert, die da lautet:


> Ziehen Sie nicht befreundete Nichtelementfunktionen den Elementfunktionen vor



Die Erläuterung dazu war (gekürzt) folgende:

```
class Browser {
    public clearCache();
    public clearHistory();
    public clearCookies();
}
```

Sollte man eine Funktion, die alles mit einem Aufruf löscht als Elementfunktion von Browser implementieren oder als "freie" Funktion?

```
class Browser {
    public clearCache();
    public clearHistory();
    public clearCookies();
    public clearAll(); // Möglichkeit 1
}

// oder

void clearAll(Browser br); // Möglichkeit 2, so in Java nicht möglich!
```

Sein Tipp ist es, eine Nichtelementfunktion zu implementieren, weil diese die Kapselung erhöht.

Die Argumentation dazu:
Kapselung bedeutet, etwas zu verbergen. Je stärker die Kapselung, desto weniger kann man sehen und je weniger man sehen kann, desto flexibler ist man als Programmierer etwas zu ändern. Das gilt für die mit dem Objekt verknüpften Daten insofern, je weniger Code die Daten sehen kann, desto stärker die Daten gekapselt sind. Deshalb ist eine Nichtelementfunktion vorzuziehen, weil sie nicht die Anzahl der Funktionen erhöht, die Zugriff auf die Daten haben.

Im Gegensatz zu C++ sind in Java ja freie Methoden nicht möglich. Die einzige Möglichkeit wäre es, diese als statische Methoden einer Klasse bereitzustellen. Wo wir wieder hier angekommen sind:


JustNobody hat gesagt.:


> Die Kernproblematik ist aus meiner Sicht: In Java geht es um eine Objektorientierte Entwicklung. Man soll also mit Objekten arbeiten.
> 
> Das *static* geht aber komplett an diesem Konzept vorbei.



Was haltet ihr von dem Gedanken?


----------



## LimDul (4. Jun 2020)

Ich halte davon gar nichts.  Das Buch Effective C++ ist von 1992 - also quasi aus der Steinzeit. Ich behaupte das zum einen da vieles überholt ist.

Auf Java bedeutet das Konzept meines Erachtens auch nicht statisch verwenden sondern sagt nur "Methoden, die nicht auf den Daten einer Klasse A arbeiten habe nichts in Klasse A verloren". Und das würde ich auch unterschreiben - das heißt aber nicht, dass die Methode  statisch werden soll, sondern das sie Klasse A ggf. nichts verloren hat.


----------



## kneitzel (4. Jun 2020)

Also ich kann die Gedanken vom Effective C++ nachvollziehen, aber wirklich zustimmen tue ich dem nicht. Aber das kann an meiner eingeschränkten C++ Sicht liegen. Also selbst in C++ würde ich dem derzeit so nicht folgen wollen.

Mir gefällt schon der Aufruf clearAll(browser) nicht. Das schränkt mich doch schon ein.... Ich spinne mir einfach mal etwas zusammen:
Ich habe einen SuperBrowser. Der hat halt einen Browser erweitert. Egal mit was. Der hat jetzt noch ABC und XYZ. Und ein clearAll() hat jetzt natürlich das übergeordnete clearAll() als auch das clearABC() und clearXYZ().
Wie Überschreibe ich das jetzt? Einfach nur per Polymorphie? Kann ich denn dann jetzt gezielt aus meinen clearAll(SuperBrowser) das clearAll(Browser) aufrufen? Im Augenblick fällt mir da dann pauschal Namespaces an. Also ich rufe BrowserNamsespace::clearAll(..) auf...

Aber evtl. gibt es da noch Möglichkeiten, die ich nicht (mehr) kenne.

Daher würde ich aus Designgründen und damit meine Klasse erweiterbar ist, das in der Klasse halten. Denn da gehört diese Methode aus meiner Sicht hin.


----------



## temi (4. Jun 2020)

LimDul hat gesagt.:


> Das Buch Effective C++ ist von 1992


3. Auflage 2011. Und ja, manches könnte überholt sein, aber das Beschriebene ist meines Erachtens nicht ausschließlich auf C++ beziehbar (bis auf die befreundeten und nicht befreundeten Nichtelementmethoden) sondern generell auf die objektorientierte Programmierung übertragbar.


LimDul hat gesagt.:


> das heißt aber nicht, dass die Methode statisch werden soll, sondern das sie Klasse A ggf. nichts verloren hat


Ja, damit hast du natürlich recht. Es muss nicht zwangsläufig auf statische Methoden hinauslaufen.


----------



## thecain (4. Jun 2020)

temi hat gesagt.:


> Was haltet ihr von dem Gedanken?


Die Idee an sich ist ja nicht doof, diese Funktionen zusammenzufassen, dann muss aber ganz klar definiert sein, was die Schnittstelle dieser Klasse ist, sonst läuft man in Probleme die schon @JustNobody erwähnt hat.

Static per se finde ich nicht doof, in dem Fall jedoch nicht sinnvoll. Für mich sollte ein static Methode möglichst keinen State ändern(auch kein Objekt) sondern dann ein neues zurück geben.

C++ ist auch eine Sprache die das OOP Paradigma noch weniger konsequent anwendet als Java. Von daher würde ich jetzt C++ Best Practices, wenn es denn welche sind, nicht unbedingt auf Java anwenden.


----------



## LimDul (4. Jun 2020)

Das von @JustNobody und @thecain würde ich unterschreiben. Ich halte die Sicht (ob die jetzt C++ oder der Tatsache das die Grundlagen älter sind geschuldet sind) für zu eingeschränkt.

Es gibt eine Schnittstelle die eine Klasse anbietet. Und diese Methoden gehören als Klassen-Methoden da rein! Ob die Methoden auf die Daten der Klasse zugreifen oder nicht, ist eigentlich vielleicht irrelevant. Daten sind Implementierungsdetails. Wenn ich in dem Beispeil die clearAll Methode aus der Klasse raus ziehe gebe ich das Implementierungsdetail - das clearAll genau die 3 Methoden aufrufen soll - nach außen. Das heißt, die Funktionalität clearAll ist *keine Funktionalität* der Klasse Browser. Zu welcher Fachlichen Entität gehört sie dann? Wenn man diese Frage sinnvoll beantworten kann, ist klar wo die Methode hingehört - und das in dem Beispiel höchstwahrscheinlich doch die Klasse Browser.

Aus Sicht von außen hat eine Klasse keine Daten - sondern nur Methoden. Und ob und auf welchen Daten die arbeiten ist vollkommen egal -relevant ist, dass der Nutzer ein in sich konsistentes API hat.


----------



## kneitzel (4. Jun 2020)

Statische Methoden, die neue Objekte erzeugen, sind natürlich auch ein Beispiel für statische Methoden, die ich vergessen habe, aufzulisten. So eine parse Methode habe ich in der Praxis durchaus öfters...


----------



## LimDul (4. Jun 2020)

Mir ist noch was eingefallen - Nehmen wir mal Test Driven Development. Wie entwickele ich da?

Ich fange an, in dem ich einen Test schreibe, Das heißt, ich erwarte, dass (um mal beim obigen Beispiel zu bleiben), dass ich eine Klasse Browser habe, mit der ich Dinge tun kann. Und das kann z. B. sein, dass ich TestDriven anfange "Ich kann alle Temporären Daten, History, Cookies, Cache" löschen. Das heißt, ich fange an der Stelle mit der Methode clearAll() an. Wenn die fertig ist, schreibe ich den nächsten Test - ich möchte nur Cookies löschen können - die Methode clearCookies() entsteht. Im Zuge des Refaktorings delegiert clearAll() dann an clearCookies() (da man doppelten Code vermeiden will). Am Ende sieht dann die Methode clearAll() so aus, dass sie nur noch eine Delegation ist. Es gibt aber jetzt keinen Grund im Refaktoring diese Methode rauszuwerfen oder zu verlagern.


----------



## kneitzel (4. Jun 2020)

Also das sehe ich anders. Du fängst ja klein an. Du willst einen Browser haben können und so ....
Dann hat er z.B. eine History.
Dann soll die gelöscht werden können => clearHistory
Dann kommen Cookies
Diese sollen gelöscht werden können also kommt clearCookies

Die Reihenfolge hier kann sich unterscheiden, aber ein clearAll macht erst Sinn, wenn es mind. 2 Dinge gibt, die gelöscht werden können ... die muss es also gegeben haben ...


----------



## LimDul (4. Jun 2020)

Wobei es am Ende aufs gleiche rauskommt - du erwartest durc Tests definiert eine Methode clearAll am Browser-Objekt und ich sehe keine Refaktoring Ansatz der sagt "Ziehe es raus".


----------



## kneitzel (4. Jun 2020)

LimDul hat gesagt.:


> Wobei es am Ende aufs gleiche rauskommt - du erwartest durc Tests definiert eine Methode clearAll am Browser-Objekt und ich sehe keine Refaktoring Ansatz der sagt "Ziehe es raus".


Das ist natürlich richtig. Wobei bei der Implementation natürlich von Anfang an das entsprechende OO Design die Grundlage war. Daher ist das relativ logisch und erläutert auch, wieso ich beim rausziehen Bauschmerzen mit dem Design bekomme


----------



## temi (4. Jun 2020)

Ist nicht die Klasse Collections genau das, was oben beschrieben wird?

Es gibt noch weitere Beispiele in der Java-Klassenbibliothek...


----------



## mihe7 (4. Jun 2020)

Möglichkeit 3:

```
class Browser {
    public:
        virtual void clearCache() = 0;
        virtual void clearHistory() = 0;
        virtual void clearCookies() = 0;
        void clearAll();
};

void Browser::clearAll() {
    this->clearCache();
    this->clearHistory();
    this->clearCookies();
}
```


----------



## LimDul (4. Jun 2020)

temi hat gesagt.:


> Ist nicht die Klasse Collections genau das, was oben beschrieben wird?
> 
> Es gibt noch weitere Beispiele in der Java-Klassenbibliothek...


Das ist eine spannende Frage - Die Abgrenzung ist an der Stelle auch nicht trivial.

Die Klasse Collections bietet Methoden an um mit Collections zu arbeiten. Da kann man an vielen Stellen drüber diskutieren - ist es sinnvoll das in eine Extra Klassse zu packen oder wäre es nicht sinnvoller direkt in Collection aufgehoben. 

Hier kommt ein "Problem" in Jave zu tragen. Java kann (aus gutem Grund keine Mehrfachvererbung). Und Collection ist ein Interface. Wenn nun z.B. jede Implementierung von Collection eine sort Methode implementieren muss ist das ziemlicher Bullshit - es sollte dafür eine Standard-Implementierung geben. Andererseits will man aber nicht eine Abstrakte Standard-Oberklasse Collection haben - weil damit schränkt man die Möglichkeiten zur Vererbung ein. Das heißt hier muss man einen Tod sterben:

a) Packe ich Kram in eine abstrakte Oberklasse, schränke ich die Möglichkeiten zur Vererbung ein
b) Definiere ich die Methoden nur im Interface, muss ich Implementireungen doppelt schreiben
c) Packe es in eine "Hilfsklasse" Collections hab ich Code, der fachlich eher Bestandteil der Collection-Api sein sollte in einer weiteren Klasse.


----------



## thecain (4. Jun 2020)

LimDul hat gesagt.:


> b) Definiere ich die Methoden nur im Interface, muss ich Implementireungen doppelt schreiben


Oder seit Java 8 ich mache eine Default Methode im Interface... Wenn es das damals schon gegeben hätte, hätte man bei der Java Entwicklung vielleicht/hoffentlich auch nicht den "Collections" Weg gewählt.


----------



## LimDul (4. Jun 2020)

thecain hat gesagt.:


> Oder seit Java 8 ich mache eine Default Methode im Interface... Wenn es das damals schon gegeben hätte, hätte man bei der Java Entwicklung vielleicht/hoffentlich auch nicht den "Collections" Weg gewählt.


Stimmt, das wäre mittlerweile seit Java 8 ein gangbarer Weg.


----------



## mihe7 (4. Jun 2020)

LimDul hat gesagt.:


> (aus gutem Grund keine Mehrfachvererbung)


Eigentlich ist der Grund nicht gut; genau genommen gibt es keinen


----------



## sascha-sphw (4. Jun 2020)

mihe7 hat gesagt.:


> Eigentlich ist der Grund nicht gut; genau genommen gibt es keinen


Ich bin bis jetzt aber auch bestens ohne ausgekommen.


----------



## mihe7 (4. Jun 2020)

sascha-sphw hat gesagt.:


> Ich bin bis jetzt aber auch bestens ohne ausgekommen.


Wer ist das nicht?   

Der Punkt ist doch, dass Mehrfachvererbung mit den default-Methoden Einzug gehalten hat und da stellt sich natürlich die Frage: warum nur bei Interfaces und nicht bei Klassen?!?


----------



## LimDul (4. Jun 2020)

mihe7 hat gesagt.:


> Wer ist das nicht?
> 
> Der Punkt ist doch, dass Mehrfachvererbung mit den default-Methoden Einzug gehalten hat und da stellt sich natürlich die Frage: warum nur bei Interfaces und nicht bei Klassen?!?


Du kannst in Interfaces keinen Zustand halten in Form von Variablen. An der Stelle fängt es - zumindest in meinem Kopf - schnell an mit Mehrfachvererbung komplex und unüberschaubar zu werden. Ich kann in Interfaces auch nur public API definieren - während ich bei Klassenvererbung auch interne (protected) Methoden überschreibe. 

Ich habe das Gefühl mit Mehrfachvererbung auf Klassenebene löse ich noch einige Spezialfälle mehr als mit Interfaces, schaffe auch mehr Stolpersteine und nicht mehr nachvollziehbares Verhalten.

Die Probleme gibt es auch jetzt schon mit den Default-Methoden in Interfaces. Da haben wir auch schon Probleme in Projekten gehabt. 

Kontext: Wir haben in Firma eine Standard-Software, die wir dann bei Kundenprojekten in Form von Maven-Modulen nutzen und darauf aufbauend anpassen (Durch Vererbung, Komposition etc.). Da kam dann auch in einem neuen Release in einem Interface eine neue Methode mit Default-Implementierung hinzu. Problem: Im Kundenprojekt waren die anderen Methoden des Interfaces schon durch Kundenspezifisches Verhalten implementiert. Die neue Methode musste nun auch durch Kundenspezifisches Verhalten implementiert werden - aber dadurch das die ja eine Default-Implementierung hat, ist das erst relativ spät im Test aufgefallen. Hätte es keine Default-Implementierung gegeben, hätten wir eine Compile Error gehabt und es direkt angepasst. 

Bei Merhfachvererbung sehe ich noch mehr die Gefahr, dass irgendwo oben in der Ableitungshierachie sich was ändert und auf einmal tickt die Klasse unten anders als vorher. Insbesondere wenn man die Klassen in der Ableitungshierachie nicht unter eigener Kontrolle hat. 

Ob das wirklich so schlimm wäre weiß ich nicht - aber ich weiß auch nicht, ob der Benefit so groß wäre.


----------



## mihe7 (4. Jun 2020)

LimDul hat gesagt.:


> Du kannst in Interfaces keinen Zustand halten in Form von Variablen. An der Stelle fängt es - zumindest in meinem Kopf - schnell an mit Mehrfachvererbung komplex und unüberschaubar zu werden. Ich kann in Interfaces auch nur public API definieren - während ich bei Klassenvererbung auch interne (protected) Methoden überschreibe.


Das wäre alles kein Problem, ist jetzt ja auch nicht anders:

```
class Base {
    protected int value = 5;
}

public class Extension extends Base {
    protected int value = 10;
}
```
In einem Extension-Objekt gilt nun this.value == 10 und super.value == 5.

Natürlich wird Mehrfachvererbung komplex; das ist ja genau der Punkt: ohne default-Methoden gab es keine Mehrfachvererbung von Implementierungen. Mit den default-Methoden hat man sich nun die Komplexität sowieso schon ins Boot geholt, dann hätte man es gleich "richtig" machen können.

Interessant wäre das doch vor allem, wenn es sich um wirklich unterschiedliche Klassen handelt. Dann gibt es das Problem von Konflikten sowieso nicht (bzw. wenn, dann nur zufällig).

NB: das soll kein "pro Mehrfachvererbung" werden.


----------

