Komponenten und Frameworks - Fachbereich Mathematik und
Transcription
Komponenten und Frameworks - Fachbereich Mathematik und
Softwareproduktlinien Teil 6: Komponenten und Frameworks Christian Kästner (Universität Marburg) Sven Apel (Universität Passau) Gunter Saake (Universität Magdeburg) 1 Wie Variabilität modular implementieren? Wiederverwendbare Implementierungsartefakte Application Eng. Domain Eng. Feature-Modell Feature-Auswahl 2 Generator Fertiges Program Komponenten 3 Komponenten Abgeschlossene modulare Implementierungseinheit mit Schnittstelle (black box), bietet ein „Dienst“ an Wird zusammen mit anderen Komponenten – auch von anderen Herstellern – „zusammengesetzt“ zu Softwaresystemen (Komposition) Einzeln vermarktbar und lauffähig Kontext (z. B. JavaEE, CORBA, COM+/DCOM, OSGi) und Abhängigkeiten (imports, exports) explizit spezifiziert Klein genug für Erzeugung und Wartung in einem Stück, groß genug um sinnvolle Funktion bieten zu können 4 Komponenten vs. Objekte/Klassen Ähnliche Konzepte: Kapselung, Interfaces, Geheimnisprinzip Objekte strukturieren ein Problem Komponenten bieten anwendbare Teilfunktionalität Objekte sind üblicherweise kleiner als Komponenten: „Komponenten skalieren OOP“ Objekte haben häufig viele Abhängigkeiten zu anderen Objekten; Komponenten haben wenig Abhängigkeiten Interfaces von Objekten sind häufig implementierungsnah; Komponenten abstrahieren mehr 5 Vision: Märkte für Komponenten Komponenten können gekauft und in eigene Produkte integriert werden Best of Breed: Entwickler kann für jedes Teilsystem den besten/billigsten Anbieter auswählen Anbieter können sich auf Kernkompetenz beschränken und diese als Komponente anbieten 6 Komponenten eines Webshops (UML-Notation) Bestellen Kunde anlegen Großhändler Kundenverwaltung Adresse ändern Einkäufe Rechnung drucken Reportgenerator Webshop Katalog verwalten Entnehmen Rechnung schreiben Lagerhaltung Finanzbuchhaltung Einlagern Szenario: Kunde anlegen -> Einkauf -> Rechnung schreiben -> Rechnung drucken 7 Produktlinien aus Komponenten Features werden in verschiedenen Komponenten implementiert z. B. die Komponenten Transaktionsverwaltung, Log/Recovery, Pufferverwaltung, Optimierer Komponenten können ggf. Laufzeitvariabilität enthalten Durch Feature-Auswahl werden Komponenten ausgewählt (mapping) Der Entwickler muss Komponenten verbinden (glue code) 8 Beispiel: Komponente Farbe in Java package modules.colorModule; //public interface public class ColorModule { public Color createColor(r:Int,g:Int,b:Int) { …} public void printColor(color: Color) {colorPrint… } public void mapColor(elem: Object, col: Color) { colorMapping…} public Color getColor(elem: Object) { colorMapping…} //just one module instance public static ColorModule getInstance() { return module; } private static ColorModule module = new ColorModule(); private ColorModule() { super(); } Facade Pattern Versteckt Implementierungsdetails Gemeinsames Interface für viele Klassen Singleton Pattern Nur eine Instanz des Moduls } public interface Color { … } //hidden implementation class ColorPrinter { … } class 9ColorMapping {…} ColorModule.getInstance().createColor(…) Produktlinien aus Komponenten Mapping von Features zu Komponenten Komponentensammlung Feature-Modell Component10 Component6 Component2 Component12 Component5 Component1 Component9 Component3 Component7 Component11 Component4 Component8 Feature-Auswahl als Eingabe Entwickler baut fertiges Programm aus Komponenten Component6 Component2 Component9 Component8 Fertiges Program Feature-Auswahl 10 Komponentenauswahl Menge von Komponenten Wie Komponenten bestimmen? Markt für beliebige Komponenten funktioniert nicht Zu kleine Komponenten hoher Aufwand Zu große Komp. kaum wiederverwendbar Produktlinien liefern nötige Domänenanalyse 11 Welche Teilfunktionalität wird in welcher Granularität wiederverwendet Systematische Wiederverwendung (Tangram) Exkurs 12 Bewertung Produktlinien aus Komponenten In der Industrie üblich (Beispiel Heimelektronik mit Koala-Komponenten von Phillips) Produktlinien zum Domain Engineering, zur Organisation der Entwicklung, … Systematische (geplante) Wiederverwendung von Komponenten Keine vollständige Automatisierung, hoher Entwicklungsaufwand (glue code) im Application Engineering Keine freie Feature-Auswahl 13 Diskussion Modularität Komponenten verstecken interne Implementierung Idealerweise kleine Interfaces Feature-Kohäsion aber … Grobe Granularität Seitenwechselstrategien, Suchalgorithmen, Locking im B-Baum, oder VARCHAR als Komponente? Farben oder gewichtete Kanten im Graph als Komp.? Funktionalität ggf. schwer als Komponente zu kapseln 14 Transaktionsverwaltungskomponente? Services Ähnlich zu Komponenten: kapseln Teilfunktionalität (Dienste) i.d.R. verteiltes Szenario Bus-Kommunikation, Web Services, SOAP, REST… Produktlinien durch Verbinden von Services, i.d.R. mit scripting (glue code) Viele Werkzeuge verfügbar (teils “Managementgerecht”) 15 Frameworks 16 Frameworks Menge abstrakter und konkreter Klassen Abstrakte Struktur, die für einen bestimmten Zweck angepasst/erweitert werden kann vgl. Template Method Pattern und Strategy Pattern Wiederverwendbare Lösung für eine Problemfamilie in einer Domäne Punkte an denen Erweiterungen vorgesehen sind: hot spots (auch variation points, extension points) Umkehrung der Kontrolle, das Framework bestimmt die Ausführungsreihenfolge 17 Hollywood Prinzip: „Don’t call us, we’ll call you.“ Plug-ins Erweiterung eines Frameworks Spezielle Funktionen bei Bedarf hinzufügen Üblicherweise getrennt kompiliert; third-party Beispiele: Emailprogramme, Graphikprogramme, MediaPlayer, Webbrowser 18 Web Portal WebapplikationFrameworks wie Struts, die grundlegende Konzepte vorgeben und vorimplementieren Entwickler konzentrieren sich auf Anwendungslogik statt Navigation zwischen Webseiten 19 <?php class WebPage { function getCSSFiles(); function getModuleTitle(); function hasAccess(User u); function printPage(); } ?> <?php class ConfigPage extends WebPage { function getCSSFiles() {…} function getModuleTitle() { return “Configuration”; } function hasAccess(User u) { return user.isAdmin(); } function printPage() { print “<form><div>…”; } } ?> Eclipse Eclipse als Framework für IDEs 20 Nur sprachspezifische Erweiterungen (Syntax Highlighting, Compiler) müssen implementiert werden Der gemeinsame Teil (Editoren, Menüs, Projekte, Verzeichnisbaum, Copy & Paste & Undo Operationen, CVS, uvm.) ist durch das Framework vorgegeben Framework aus vielen kleineren Frameworks 21 JUnit Refactor Launch Cheat Sheets Search Debug OSGi Rich Client Platform Help Compare Text Editors Forms Editors Console Resources Weitere Erweiterungen… Workbench JFace Core Runtime SWT Prerequisite C. Meta-Dialogs Dialogs Resourcepool Messages Script Table Component Control Items Web WTP Views Update Help J2EE Edit IDE JDT Team Debug Platform Ant Build Eclipse SAPinst Workbench Weitere Framework-Beispiele Frameworks für graphische Benutzeroberflächen, z.B. MacApp, Swing, SWT, MFC Multimedia-Frameworks, z.B. DirectX Instant Messenger-Frameworks, z.B. Miranda, Trillian, ... Compiler-Frameworks, z.B. Polyglot, abc 22 Framework-Implementierung: Mini-Beispiel Familie von Dialogen, bestehend aus Textfeld und Button 90% des Quelltexts sind gleich 23 Main Methode Initialisierung von Fenster, Textfeld und Button Layout Schliessen des Fensters … Taschenrechner public class Calc extends JFrame { private JTextField textfield; public static void main(String[] args) { new Calc().setVisible(true); } public Calc() { init(); } protected void init() { JPanel contentPane = new JPanel(new BorderLayout()); contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); JButton button = new JButton(); button.setText("calculate"); contentPane.add(button, BorderLayout.EAST); textfield = new JTextField(""); textfield.setText("10 / 2 + 6"); textfield.setPreferredSize(new Dimension(200, 20)); contentPane.add(textfield, BorderLayout.WEST); button.addActionListener(/* code zum berechnen */); this.setContentPane(contentPane); this.pack(); this.setLocation(100, 100); this.setTitle("My Great Calculator"); // code zum schliessen des fensters } } 24 Quelltext für alle Varianten fast gleich, nur roter Quelltext unterscheidet sich (hot spots) White-Box Frameworks Erweiterung durch überschreiben und hinzufügen von Methoden (vgl. Template Method Pattern) Interna des Framework müssen verstanden werden -> schwierig zu lernen Flexible Erweiterung Viele Subklassen nötig -> ggf. unübersichtlich Status direkt verfügbar durch Superklasse Keine Plug-ins, nicht getrennt kompilierbar 25 Taschenrechner als Whitebox-Framework public abstract class Application extends JFrame { protected abstract String getApplicationTitle(); //Abstrakte Methoden protected abstract String getButtonText(); protected String getInititalText() {return "";} protected void buttonClicked() { } private JTextField textfield; public Application() { init(); } protected void init() { JPanel contentPane = new JPanel(new BorderLayout()); contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); JButton button = new JButton(); button.setText(getButtonText()); contentPane.add(button, BorderLayout.EAST); textfield = new JTextField(""); textfield.setText(getInititalText()); textfield.setPreferredSize(new Dimension(200, 20)); contentPane.add(textfield, BorderLayout.WEST); button.addActionListener(/* … buttonClicked(); … */); this.setContentPane(contentPane); this.pack(); this.setLocation(100, 100); this.setTitle(getApplicationTitle()); // code zum schliessen des fensters } protected String getInput() { return textfield.getText();} } 26 Taschenrechner als Whitebox-Framework public abstract class Application extends JFrame { protected abstract String getApplicationTitle(); //Abstrakte Methoden protected abstract String getButtonText(); protected String getInititalText() {return "";} protected void buttonClicked() { } private JTextField textfield; public Application() { init(); } protected void init() class { public Calculator extends Application { JPanel contentPane = new JPanel(new BorderLayout()); protected String getButtonText() { return "calculate"; } contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); protected String getInititalText() { return "(10 – 3) * 6"; } JButton protected button = new JButton(); void buttonClicked() { button.setText(getButtonText()); JOptionPane.showMessageDialog(this, "The result of "+getInput()+ contentPane.add(button, " is BorderLayout.EAST); "+calculate(getInput())); } textfield protected = new JTextField(""); String getApplicationTitle() { return "My Great Calculator"; } textfield.setText(getInititalText()); public static void main(String[] args) { textfield.setPreferredSize(new Dimension(200, 20)); new Calculator().setVisible(true); contentPane.add(textfield, BorderLayout.WEST); } } button.addActionListener(/* … buttonClicked(); … */); this.setContentPane(contentPane); this.pack(); this.setLocation(100, 100);Application { public class Ping extends this.setTitle(getApplicationTitle()); protected String getButtonText() { return "ping"; } // code zum schliessen des fensters protected String getInititalText() { return "127.0.0.1"; } } protected void buttonClicked() { /* … */ } protected String getInput() { return textfield.getText();} protected String getApplicationTitle() { return "Ping"; } } public static void main(String[] args) { new Ping().setVisible(true); } 27 } Black-Box Frameworks Einbinden des anwendungsspezifischen Verhalten durch Komponenten mit speziellem Interface (plug-in) Nur das Interface muss verstanden werden vgl. Strategy Pattern, Observer Pattern einfacher zu lernen, aber aufwendiger zu entwerfen Flexibilität durch bereitgestellte Hot Spots festgelegt, häufig Design Pattern Status nur bekannt wenn durch Interface verfügbar Insgesamt besser wiederverwendbar (?) 28 Taschenrechner public class Application extends JFrame { private JTextField textfield; private Plugin plugin; public Application(Plugin p) { this.plugin=p; p.setApplication(this); init(); } protected void init() { JPanel contentPane = new JPanel(new BorderLayout()); contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); JButton button = new JButton(); public interface Plugin { if (plugin != null) String getApplicationTitle(); button.setText(plugin.getButtonText()); String getButtonText(); else String getInititalText(); button.setText("ok"); void buttonClicked() ; contentPane.add(button, BorderLayout.EAST); void setApplication(Application app); textfield = new JTextField(""); } if (plugin != null) textfield.setText(plugin.getInititalText()); textfield.setPreferredSize(new Dimension(200, 20)); contentPane.add(textfield, BorderLayout.WEST); if (plugin != null) button.addActionListener(/* … plugin.buttonClicked();… */); this.setContentPane(contentPane); … } public String getInput() { return textfield.getText();} } 29 Taschenrechner public class Application extends JFrame { private JTextField textfield; private Plugin plugin; public Application(Plugin p) { this.plugin=p; p.setApplication(this); init(); } protected void init() { JPanel contentPane = new JPanel(new BorderLayout()); contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); JButton button = new JButton(); public interface Plugin { if (plugin != null) String getApplicationTitle(); button.setText(plugin.getButtonText()); String getButtonText(); else String getInititalText(); button.setText("ok"); void buttonClicked() ; contentPane.add(button, BorderLayout.EAST); void setApplication(Application app); textfield = new JTextField(""); } if (plugin != null) textfield.setText(plugin.getInititalText()); public class CalcPlugin implements20)); Plugin { textfield.setPreferredSize(new Dimension(200, private BorderLayout.WEST); Application application; contentPane.add(textfield, public void setApplication(Application app) { this.application = app; } if (plugin != null) public String getButtonText() { return "calculate";*/); } button.addActionListener(/* … plugin.buttonClicked();… public String getInititalText() { return "10 / 2 + 6"; } this.setContentPane(contentPane); public void buttonClicked() { … JOptionPane.showMessageDialog(null, "The result of " } + application.getInput() + " is " public String getInput() { return textfield.getText();} + calculate(application.getText())); } } public String getApplicationTitle() { return "My Great Calculator"; } } class30CalcStarter { public static void main(String[] args) { new Application(new CalcPlugin()).setVisible(true); }} Weitere Entkopplung moeglich public class Application extends JFrame implements InputProvider { private JTextField textfield; private Plugin plugin; public Application(Plugin p) { this.plugin=p; p.setApplication(this); init(); } public interface InputProvider { protected void init() { JPanel contentPane = new JPanel(new BorderLayout()); String getInput(); } contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED)); JButton button = new JButton(); public interface Plugin { if (plugin != null) String getApplicationTitle(); button.setText(plugin.getButtonText()); String getButtonText(); else String getInititalText(); button.setText("ok"); void buttonClicked() ; contentPane.add(button, BorderLayout.EAST); void setApplication(InputProvider app); textfield = new JTextField(""); } if (plugin != null) textfield.setText(plugin.getInititalText()); public class CalcPlugin implements20)); Plugin { textfield.setPreferredSize(new Dimension(200, private BorderLayout.WEST); InputProvider application; contentPane.add(textfield, public void setApplication(InputProvider app) { this.application = app; } if (plugin != null) public String getButtonText() { return "calculate";*/); } button.addActionListener(/* … plugin.buttonClicked();… public String getInititalText() { return "10 / 2 + 6"; } this.setContentPane(contentPane); public void buttonClicked() { … JOptionPane.showMessageDialog(null, "The result of " } + application.getInput() + " is " public String getInput() { return textfield.getText();} + calculate(application.getInput())); } } public String getApplicationTitle() { return "My Great Calculator"; } } class31CalcStarter { public static void main(String[] args) { new Application(new CalcPlugin()).setVisible(true); }} Plugins laden Typisch für viele Frameworks: Plugin-Loader … Häufig zusätzlich GUI für Plugin-Konfiguration Beispiele: … sucht in Verzeichnis nach DLL/Jar/XML Dateien … testet ob Datei ein Plugin implementiert … prüft Abhängigkeiten … initialisiert Plugin Eclipse (plugin-Verzeichnis + Jar) Miranda (plugins-Verzeichnis + DLL) Alternativ: Plugins in Konfigurationsdatei festlegen oder StarterProgramm generieren 32 Beispiel Plugin Loader (benutzt Java Reflection) public class Starter { public static void main(String[] args) { if (args.length != 1) System.out.println("Plugin name not specified"); else { String pluginName = args[0]; try { Class<?> pluginClass = Class.forName(pluginName); new Application((Plugin) pluginClass.newInstance()) .setVisible(true); } catch (Exception e) { System.out.println("Cannot load plugin " + pluginName + ", reason: " + e); } } } } 33 Mehrere Plugins vgl. Observer Pattern Mehrere Plugins laden und registrieren Bei Ereigniss alle Plugins informieren Für unterschiedliche Aufgaben: spezifische Plugin-Interfaces 34 public class Application { private List<Plugin> plugins; public Application(List<Plugin> plugins) { this.plugins=plugins; for (Plugin plugin: plugins) plugin.setApplication(this); } public Message processMsg (Message msg) { for (Plugin plugin: plugins) msg = plugin.process(msg); ... return msg; } } Frameworks fuer Produktlinien Mapping von Features zu Plugins Framework + Plugins Application Eng. Domain Eng. Feature-Modell Feature-Auswahl als Eingabe Feature-Auswahl 35 Pluginauswahl (und ggf. Startkonfiguration generieren) Anwendung = Framework mit passenden Plugins Frameworks für Produktlinien – Bewertung Vollautomatisierung möglich Modularität Praxiserprobt Erstellungsaufwand und Laufzeit-Overhead für Framework/Architektur Vorplanung nötig, Frameworkdesign erfordert Erfahrung Schwierige Wartung, Evolution Grobe Granularität oder riesige Interfaces 36 Plugin für Transaktionsverwaltung, VARCHAR oder gewichtete Kanten? Querschneidende Belange 37 Querschneidende Belange Engl. crosscutting concerns Behauptung: Nicht alle Belange (Features) in einem Programm können mittels Objekten (Komponenten, Plugins) modularisiert werden Belange sind semantisch zusammenhängende Einheiten Aber ihre Implementierung ist manchmal verstreut, vermischt und repliziert im Code Weitere Querschneidende Belange class DatabaseApplication //... Datenfelder //... Logging Stream //... Cache Status public void authorizeOrder( Data data, User currentUser, ...){ // prüfe Autorisierung // Objekt sperren für Synchronisation // Aktualität des Puffers prüfen // Start der Operation loggen // eigentliche Operation ausführen // Ende der Operation loggen // Sperre auf Objekt freigeben } public void startShipping( OtherData data, User currentUser, ...){ // prüfe Autorisierung // Objekt sperren für Synchronisation // Aktualität des Puffers prüfen // Start der Operation loggen // eigentliche Operation ausführen // Ende der Operation loggen // Sperre auf Objekt freigeben } } Code für verschiedene Belange vermischt Code repliziert Im Beispiel Operationen modular, aber Sperren, Logging, Puffer und Authentifizierung nicht Scattering und Tangling Verstreuter Code (code scattering) Code, der zu einem Belang gehört, ist nicht modularisiert, sondern im gesamten Programm verteilt Häufig kopierter Code (auch wenn es je nur ein einzelner Methodenaufruf ist) Oder stark verteilte Implementierung von (komplementären) Teilen eines Belangs Vermischter Code (code tangling) Code, der zu verschiedenen Belangen gehört, ist in einem Modul (oder einer Methode) vermischt Eine Frage der Größe Zeitmanagement von Sessions l Beispiel: Example: Session expiration in im Apache Tomcat Server the Apache der Tomcat Server als Bestandteil Session-Verwaltung Probleme querschneidender Belange Belange verschwinden in der Implementierung Schwierige Arbeitsteilung Was gehört alles zu einem Belang? Bei Wartungsaufgaben muss der ganze Quelltext durchsucht werden Für unterschiedliche Belange kann es unterschiedliche Experten geben; alle müssen am gleichen Code-Fragment arbeiten Geringere Produktivität, schwierige Evolution Beim Hinzufügen neuer Funktionalitäten muss sich der Entwickler um diverse andere Belange kümmern, die erstmal nur ablenken (Lesbarkeit, Erfassbarkeit) Alternative Implementierung (Command Pattern) class SecureSystem extends System private User currentUser; public void login() { /* ... */ } public void executeOperation(Operation o) { if (o instanceof AuthorizeOrder) if (!currentUser.isAdmin()) denyAccess(); else o.execute(); if (o instanceof StartShipping) { if (!o.hasWriteAccess()) denyAccess(); else o.execute(); } } Authentifizierung wurde modularisiert Dadurch sind aber die anderen Belange (hier Operationen) nicht mehr modular Weiterer Versuch – Methodenaufrufe Belange wie Sperren, Logging, Puffer und Authentifizierung werden in eigene Module ausgelagert Scattering und Tangling nur noch von Methodenaufrufen Übersichtlicher, aber immer noch explizite Aufrufe im Code -> Viele Extension Points in Framework nötig; große Interfaces für Komponenten class BusinessClass public void importantOperation( Data data, User currentUser, ...){ checkAuth(currentUser); startSynchronization(); checkCache(); logStart(); // eigentliche Operation ausfuehren logEnd(); endSynchronization(); } } Weiterer Versuch – Middleware Middleware kann sich um querschneidende Belange kümmern; Entwickler implementiert nur noch eigentliche Operationen (inversion of control) Beispiel: Enterprise Java Beans stellen zur Verfügung: verteilte Objekte, Persistenz, Transaktionsverwaltung, Authentifizierung und Autorisierung, Synchronisation Aufwendige Architektur Nicht alle Belange von Middleware erfassbar, insbesondere Belange der Businesslogik Tyrannei der Dominanten Dekomposition Viele Belange können modularisiert werden, jedoch nicht immer gleichzeitig Problemstellung nur in einer Richtung modularisierbar Im Graph können Farben modularisiert werden… …dann sind die Datenstrukturen (Node, Edge) verteilt Entwickler wählen eine Dekomposition aus (z.B. Operationen, Authentifizierung, Datenstrukturen), aber einige andere Belange „schneiden quer“ Gleichzeitige Modularisierung entlang verschiedener Dimensionen nicht möglich Exkurs: Expression Problem Frage: Wie weit kann man Datenstrukturen und Methoden abstrahieren, so dass man beide unabhängig erweitern kann… ohne bestehenden Code zu ändern (oder sogar ohne neucompilieren des bestehenden Codes) mehrfach, in beliebiger Reihenfolge und ohne (nicht-triviale) Code-Replikation Ausdrücke (expressions) Aufgabe: Mathematische Ausdrücke werden in einer Baumstruktur gespeichert und können ausgerechnet und ausgegeben werden * 1,73 + 5 ln 50 Implementierung 1: Daten-zentriert Rekursive Klassenstruktur (Composite Pattern) Für jede Operation eine Methode in jeder Klasse definiert «interface» Term +eval() +print() Number -value +eval() +print() Plus -term1 -term2 +eval() +print() Ln Product -term1 -term2 +eval() +print() -term +eval() +print() Problem von Implementierung 1 Ausdrücke sind modular Neue Operationen, z. B. drawTree oder simplify, können nicht einfach hinzugefügt werden Alle bestehenden Klassen müssen angepasst werden! Operationen sind querschneidend zu den Ausdrücken Implementierung 2: Methoden-zentriert Nur eine Methode accept pro Klasse Methoden werden mit dem Visitor-Pattern implementiert «interface» Visitor +visitNumber() +visitSum() +visitProduct() «interface» Term +accept(in Visitor) Sum Product -term1 -term2 +accept(in visitor) -term1 -term2 +accept(in visitor) Number -value +accept(in visitor) (Ln Klasse aus Platzgründen ausgelassen) PrintVisitor EvalVisitor +visitNumber() +visitSum() +visitProduct() +visitNumber() +visitSum() +visitProduct() Codebeispiel Methoden-zentriert interface Term { void accept(Visitor v); } class Number { float value; void accept(Visitor v) { v.visitNumber(this); } } class Sum { Term term1, term2; void accept(Visitor v) { v.visitSum(this); } } class Product { Term term1, term2; void accept(Visitor v) { v.visitProduct(this); } } interface Visitor { void visitNumber(Number n); void visitSum(Sum s); void visitProduct(Product p); } class PrintVisitor { void visitNumber(Number n) { System.out.print(n.value); } void visitSum(Sum s) { System.out.print('('); s.term1.accept(this); System.out.print('+'); s.term2.accept(this); System.out.print(')'); } void visitProduct(Product p) { s.term1.accept(this); System.out.print('*'); s.term2.accept(this); } } // Main: // term.accept(new PrintVisitor()); Problem von Implementierung 2 Operationen sind modular Neue Ausdrücke, z. B. Min oder Power können nicht einfach hinzugefügt werden Für jede neue Klasse müssen alle Visitor-Klassen angepasst werden Ausdrücke sind querschneidend zu den Operationen Expression Problem Nur sehr schwer möglich Ausdrücke und Operationen darauf gleichzeitig zu modularisieren (komplizierte Lösungen mit Java 1.5 Generics existieren) Daten-zentrierter Ansatz Neue Ausdrücke können direkt hinzugefügt werden: modular Neue Operationen müssen in alle Klassen eingefügt werden: nicht modular Methoden-zentrierter Ansatz Neue Ausdrücke können als weiterer Visitor hinzugefügt werden: modular Für neue Klassen müssen alle bestehenden Visitors erweitert werden: nicht modular Expression Problem – Grafisch Daten-zentriert plus Methoden-zentriert (Visitor) plus power eval eval print print (a) plus power power (b) ln plus eval eval print print simplify simplify (c) Ausblick: Neue Sprachansätze power ln (d) Ausblick: Feature-Interaktionen Typische Beispiele für querschneidende Belange Logging: Meldung nach jeder Methode Caching/Pooling: Code bei jedem Erzeugen eines Objektes Synchronisierung/Locking: Erweiterung vieler Methoden mit lock/unlock-Aufrufen Features in Produktlinien! Dilemma Es ist nicht immer möglich alle Belange zu modularisieren (Tyrannei …) Ein Grundmaß an verstreutem und vermischtem Code in OOP-Implementierungen ist normal Einige Belange sind immer „orthogonal“ zu anderen: querschneidende Belange Features in Produktlinien sind häufig betroffen Preplanning Problem 58 Preplanning Problem Erweiterungen sind nicht ad-hoc möglich, sondern müssen voraus geplant werden Es müssen explizit Erweiterungsmöglichkeiten vorgesehen werden Extension Points in Frameworks Interfaces/Parameter in Komponenten Ohne passenden Extension Point keine modulare Erweiterung möglich Preplanning Problem - Beispiel Stack-Methoden sollen synchronisiert werden Modulare Erweiterung mittels Subklasse oder Delegation Basis-Code class Stack { /* ... */ } class Main { public static void main( String[] args) { Stack stack = new Stack(); stack.push('foo'); stack.push('bar'); stack.pop(); } } Spätere ungeplante Erweiterung class LockedStack extends Stack { private void lock() { /* ... /* } private void unlock() { /* ... /* } public void push(Object o) { lock(); super.push(o); unlock(); } public Object pop() { lock(); Object result = super.pop(); unlock(); return result; } } Preplanning Problem - Beispiel II Problem: Instantiierung des Stacks im Basiscode muss angepasst werden Alternative Keine Möglichkeit ohne Änderung des Basiscode (nichtmodular) Design Pattern: Factory statt direkter Instantiierung (erlaubt modulare Erweiterung) Framework mit passendem Extension Point Erweiterungsmöglichkeiten müssen antizipiert werden (preplanning) oder nachträglich dem Basiscode hinzugefügt werden (nicht-modular) Zusammenfassung Modularisierung von Features mit Komponenten und Frameworks Keine Vollautomatisierung, Laufzeitoverhead, grobe Granularität Grenzen bei querschneidenen Belangen und feiner Granularität Modularität erfordert Planung Nicht für alle Produktlinien geeignet (z.b. Graphbibliothek, eingebettete Datenbanken) 62 Ausblick Neue Programmierkonzepte 63 Analyse Objekt-Orientierung und deren Grenzen Feature-Orientierung Aspekt-Orientierung Literatur C. Szyperski: Component Software: Beyond ObjectOriented Programming. Addison-Wesley, 1998 [Standardwerk Komponentenorientierte Softwareentwicklung] R. Johnson and B. Foote, Desiging reusable classes, Journal of Object-Oriented Programming, 1(2):22-35, 1988 [OOP Wiederverwendung, insb. Frameworks] L. Bass, P. Clements, R. Kazman, Software Architecture in Practice, Addison-Wesley, 2003 [Architekturgetriebene Produktlinien, typischerweise Frameworks] 64