Wie erweitert man richtig?
Ziemlich schwere Frage, oder? Deswegen reden wir doch erstmal um den heißen Brei herum und erzählen, warum wir uns gerade mit diesem Thema beschäftigen. Es ging mal wieder um die alte „macht man alles protected statt private“ Diskussion, die ihr wahrscheinlich alle nicht mehr hören könnt. Natürlich ist private die bessere Wahl, einfach weil ich es sage. Auf jeden Fall hatte ich vor ein paar Tagen diese Diskussion. Protected bedeutet, dass man ableiten und dann auf interne Methoden zugreifen kann. Wissen wir ja alle. Das macht ein System natürlich irgendwie erweiterbar. Da geb ich jedem Recht. Aber eigentlich ist Ableitung nicht das, was wir wollen, um Applikationen größer zu machen. Da muss es doch also auch einen anderen Weg geben?!
Und schon sind wir mitten im Thema. Ich versuche jetzt mal ein paar Dinge aufzuzählen, die mir so einfallen. Wir machen es dann wie immer, dass ihr den Rest in den Kommentaren verankert.
- Plugins: Wir halten uns an das Eclipse-Paradigma „alles ist ein Plugin“. Wir definieren feste Punkte, an denen Plugins Aktionen ausführen können. So wird zum Beispiel beim initialisieren eines Programmes auch die Initialisierung des Plugins angestoßen. Nach dem eigentlichen Lauf dann eine weitere festdefinierte Methode. Dies bekommt man einfach über ein Plugin-Interface hin, dass jede Erweiterung implementieren muss und schon kann es losgehen. Da zeige ich euch am Montag ein Beispiel zu.
- Events: Eigentlich genau das gleiche, wie bei den Plugins, nur noch ein wenig flexibler. Statt zu sagen, an welchen n Stellen ich nun etwas einhängen kann/muss, kann sich hier jedes Plugin aussuchen worauf es reagiert. Natürlich muss die Applikation hierfür auch nach außen mitgeben, dass etwas passiert ist. Vielleicht sollte man sich hierzu mal den Event Dispatcher der Symfony Components anschauen. Aber eigentlich ist das ganze Eventgeraffel nichts anderes als das Observer-Pattern.
- Klassen nutzen: Oft ist es so, dass Leute ableiten, um Funktionalitäten der Elternklasse nutzen zu können. Aber eigentlich ist das nicht die Idee hinter Ableitung. Vielmehr geht es darum, dass sich zwei Klassen nach außen hin gleich verhalten (Liskovsches Substitutionsprinzip). Da können wir auch mal genauer drauf eingehen. Irgendwann. Auf jeden Fall ist das dann oft ein Zeichen, dass man nicht so wirklich auf Seperation of Concerns geachtet hat und man die zu nutzende Funktionalität lieber in eine separate „offene“ Klasse packt. Diese kann dann jeder nutzen, wie er gerade Lust hat.
- Interfaces: Alter Hut, aber eigentlich immer noch ein stiefmütterlich behandeltes Thema in PHP. Wenn man gegen konkrete Implementationen programmiert, so ist das Programm starr. Ich kann nichts austauschen. Ich stand zum Beispiel gerade vor dem Problem, dass ich mit der Zend_Http_Response gearbeitet habe. Nettes Teil. Kann nur leider nicht das was ich will. Trotzdem nettes Teil. Würde ich jetzt überall gegen genau diese Klasse programmiert hätte, hätte ich keine Chance selber zu erweitern. Ich kann ja schließlich nicht einfach eine neue Methode in die Zend-Bibliothek einbauen, ohne damit irgendwann in Probleme zu kommen. Also nimmt man sich ein Response-Interface und programmiert dagegen. Jetzt kann ich meine eigene Implementierung reingeben. Die ist am Anfang vielleicht noch die Zend Klasse, aber wenn ich mehr brauch, dann mach ich halt mehr dazu. Meine Applikation wird trotzdem weiter funktionieren. (Den Abschnitt werde ich übrigens bald noch mal neu formulieren, habe da grad ein paar Ideen wie man es besser veranschaulicht)
- Business-Value: Jetzt kommt der Knauser wieder durch. Vielleicht fragen wir uns auch mal, ob wir überhaupt erweitern müssen. Wenn wir eine Bibliothek haben, die schon sehr viel kann, vielleicht wollen wir einfach zu viel. Manchmal lohnt der Mehraufwand nämlich nicht. Also zumindest vorher nachdenken, wahrscheinlich machen wir es dann eh. aber wenigstens haben wir dann was fürs Gewissen gemacht.
So das war’s erstmal. Gibt bestimmt noch viele weitere Wege. Sicherlich hat jeder dieser Punkte auch noch mal das Potential genauer unter die Lupe genommen zu werden, aber vielleicht reicht es schon mal, um zu zeigen, dass es auch andere Wege als die Ableitung gibt.
Interessanter Artikel, der zur Hochzeit von Modulen und Apps einen sehr wichtigen Aspekt anspricht.
Ah… Aspekt! AOP (Aspektorientierte Programmierung) trifft doch hier auch sehr gut das Thema.
Ich selbst habe mir auch schon viele Gedanken zu den von dir angesprochenen Lösungswegen gemacht, bin aber zu dem Entschluss gekommen, dass es meist keine Patentlösung gibt und jede der Lösungswege irgendwelche blöden Nachteile hat… das Thema könnte man sich aber durchaus auch einmal in einer Artikelreihe genauer ansehen!
also ich frühe viel lieber die diskusion warum man überhauot sachen private macht^^
Die führen viele gerne, aber ich hab’s aufgegeben. Ich zieh mein Ding einfach mit private durch 😉
Nils, und wie benutzt du dann dein Objekt?
@test: Ich sage ja nur, dass ich meine Methoden so lange private lasse, bis ich einen Grund habe sie weiter zu öffnen.
Bei „echter“ Vererbung hast Du dann doch aber die A-Karte und musst gemeinsame Funktionalität per Copy&Paste implementieren… ?! Decorators & Co fallen auch aus. Und IMHO bekommst Du auch dann Probleme, wenn Du Interfaces verwendest, weil eine nachträgliche Öffnung der Sichtbarkeit die Signatur der Methode verändert. Ich weiß, viele argumentieren, in Interfaces gehören nur public Methoden, in Hinblick auf Patterns wie Strategie bin ich allerdings anderer Meinung.
Die Diskussion ist wirklich schon überbeansprucht. Methoden dürfen doch gerne protected sein, jedoch sollten die Properties private sein. Dadurch ist jede Klasse in sich geschlossen und gut ist.
Was die veränderte Signatur betrifft, ist das doch sauber, solange erweitert und nicht eingeschränkt wird.
@nik: Decorators & Co kommen eh nur an public Methoden oder Properties dran und sind eh u.U. recht wackelig.
@Daniel: Wie gesagt, soll jeder machen wie er will. Ich hab meinen Weg gefunden.
Der ist ja auch ok, besser als alles public zu setzen.
Guter Artikel nebenbei, ist ein interessantes Thema. Wenn alles richtig aufgebaut ist, kann man eine Menge damit anstellen.
Nils macht es völlig richtig.
@Daniel:
Keine Diskussion ist jemals überansprucht, es kommen immer wieder Myriaden an neuen Entwicklern hinzu.
Genauso wie wir alle mal lernen mussten und „dumme“ Fragen gestellt haben,
als es die alten Hasen schon gab und alle Fragen beantwortet schienen.
Das ist der Lauf der Dinge und die Verantwortung der erfahreneren Entwickler, darauf einzugehen.
@test, @Wasrun:
Um es kurz zu machen, sehen ideale Klassen so aus:
1) Sie machen nur eine Sache, die aber richtig.
2) Sie haben ein klares und feingranulares Interface.
3) Sie haben nur eine public Methode.
4) Alles Sonstige ist für die Außenwelt unsichtbar.
Vor allem die wichtigsten Prinzipen wie SoC, SRP, OCP, Information Hiding und Kapselung u.v.a.m. werden damit bedient. Ausnahmen gibt es, wie im richtigen Leben, hier natürlich auch.
Google mal nach „clean code“, „solid oop“ und guckst du auch bei Onkel Bob:
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Everything depends on something, so don’t tell me „it depends“…
@Don Zampano
Ich wollte gerade die Frage stellen wann „protected“ Methoden denn Sinnvoll sind. Das ganze wollte ich mit dem Beispiel „Ich habe eine Klasse die eine Public-Methode beinhaltet. Diese Klasse benutzt 3 Methoden zur Berechnung von Algorithmen. Jetzt möchte ich diese Klasse ableiten …“ unterlegen. Beim schreiben viel mir aber auf das ich ja für jeden Algorithmus eine separate Klasse anlegen kann. SoC zielt ja genau darauf ab. Jetzt ergibt sich aber wieder eine neu Frage. Sollte ich denn versuchen sobald ich eine Methode als „protected“ deklariere, den Code(im Sinne von SoC) in einer separaten Klasse zu kapseln. Deine 4 Punkte Aufzählung zielt darauf ab. In einer in sich geschlossenen Anwendung mag das ja auch alles funktionieren. Was ist aber wenn ich eine Bibliothek entwickele. Dann weiß man ja nicht wie der Benutzer sich verhält. Das heißt ob er eine Klasse ableitet um zusätzliche Funktionalität bereit zu stellen. Soll man hier den Benutzer dazu zwingen diese in der Klasse bereits implementierte Funktionalität selbst noch mal zu implementieren?
@Christian
„Sollte ich denn versuchen sobald ich eine Methode als „protected“ deklariere, den Code(im Sinne von SoC) in einer separaten Klasse zu kapseln.“
Verstehe ich nicht ganz.
Generell entscheidet nicht das Ändern der Sichtbarkeit einer Methode darüber, ob man es in einer eigenen Klasse kapselt.
Die Frage ist auch eher, warum du die Methode als protected deklarierst.
Das wird so leider nicht ganz klar.
„Was ist aber wenn ich eine Bibliothek entwickele.
Dann weiß man ja nicht wie der Benutzer sich verhält.“
Das muss eine Bibliothek auch nicht wissen, wo kämen wir hin, wenn man
Bibliotheken auf alle Eventualitäten seiner Benutzer hin gestalten würde?
Es ist eher anders herum. Als Benutzer sollte ich wissen, welche Möglichkeiten mir die Bibliothek anbietet.
Meist kommt nun quasi das Open-Closed-Prinzip ins Spiel: als Entwickler
der Bibliothek biete ich verschiedene Mechanismen an, um die Funktionalität
nutzen und erweitern zu können. Das macht ZF extensiv oder Doctrine, letztere sehr viel mit Template Methods (preInsert, postInsert, preUpdate,…).
Ich verwende, bis auf wenige Ausnahmen, stets ein „Gateway“ zu Drittanbietercode oder Bibliotheken hin. Mich interessiert primär, was ich haben will und wie ich mir die ideale Schnittstelle zu so einer Blackbox (was sie sein sollte!) vorstelle.
Eben wie bei TDD, wo man zuerst seine idealen Schnittstellen designed.
Was man dann macht, kann vielfältig sein. Entweder bietet mir die Bibliothek Möglichkeiten des Subclassings, Template Methods oder Decoratoren an oder ich erweitere es eben mit meinen eigenen Mitteln – oder nehme eine andere.
Aber das alles hat nicht wirklich etwas damit zu tun, ob ich meine Methoden protected oder private setze.
Man sollte es eher aus dieser Sicht sehen:
Alle Artefakte (Bibliotheken, Module und Klassen) sollten quasi als Blackbox gesehen werden, mit einem klar definiertem Interface und klaren Aufgaben.
Alles was die Außenwelt machen können soll, tun sie, alles andere verbergen sie.
Das lässt sich von kleinster Codeebene bis hin zur Architektur durchziehen.
Von daher gibt es, wenn überhaupt, nur innerhalb dieser *Konzepte* so etwas
wie protected.
Argumente, dass man protected/private nicht testen könnte, sind ja schon vielfach diskutiert und widerlegt worden (u.a. auch hier http://www.phphatesme.com/blog/softwaretechnik/protected-vs-private/)
und schlichtweg technischer Unsinn.
Apropos: fast alle diese Diskussionen zielen auf technische Probleme und technische Lösungen aus.
Dabei ist es fast immer nur ein Nach- oder Umdenken, was einem auch einfachere und sinnvollere Möglichkeiten im Rahmen der Sprache gibt.
OOP ist vor allem Kommunikation, Zuhören und kritisches Denken (zumindest was Businessanwendungen angeht).
Gefällt den meisten nicht.
Die anderen entdecken die Möglichkeiten 🙂
@Don
>> Sollte ich denn versuchen sobald ich eine Methode
>> als “protected” deklariere, den Code(im Sinne von SoC)
>> in einer separaten Klasse zu kapseln.
> Verstehe ich nicht ganz.
Ich glaube ich hatte mich da gedanklich etwas verrannt.
Danke erstmal für die ausführlichen Informationen. Ich konnte auf jeden Fall ein paar neue Denkanstöße mitnehmen und werde diese hoffentlich beim nächsten Klassenentwurf berücksichtigen.
Ich würde mich mal freuen ein paar Praxisbeispiele zu sehen.
Ich benutze in meinen Projekten meistens eine Kombination aus Plugins und Events. Wenn von Anfang an auch konsequent Events definiert und eingebaut werden, damit die Plugins nicht in ihrem Funktionsumfang beschränkt sind, klappt das (meiner Meinung nach) am besten.