Introduction to Object-Oriented Design Patterns jonas.kvarnstrom@liu.se – 2016 2 First: An illustrating problem Suppose you need to sort some strings Implement sorting algorithms public class StringSorter { public void quicksort(String[] strings) public void mergesort(String[] strings) } Use the algorithms String[] strings = new String[numberOfStrings]; … StringSorter mySorter = new StringSorter(); mySorter.quicksort(strings); {…} {…} jonkv@ida Sorting 1: String Sorting A sorting algorithm must be able to compare two elements Obvious: Comparison inside the sort method public class StringSorter { public void quicksort(String[] array) { … // Should element #i be before element #j? if (array[i].compareTo(array[j]) < 0) { … } } } String has a built-in compareTo method Returns -1, 0 or 1: str1 should be before, equal to, or after str2 3 jonkv@ida Sorting 2: String Comparisons And sometimes you need an inverse sort order Obvious solution: Add a flag public class StringSorter { private boolean inverseOrder; public StringSorter(boolean inverse) { this.inverseOrder = inverse; } public void quicksort(String[] strings) { … // Should element #i be before element #j? if ( !inverseOrder && array[i].compareTo(array[j]) < 0 || inverseOrder && array[i].compareTo(array[j]) > 0) { … } } Different comparisons } depending on standard or inverse order 4 jonkv@ida Sorting 3: Different Sort Orders You sometimes use different locales Danish: …xyzäöå Swedish: …xyzåäö English: aåäbcde… Add a locale specifier to the StringSorter Someone wants to sort strings representing dates In day/month/year format Add a new flag indicating date sorting should be used … 5 jonkv@ida Sorting 4: Additional Sort Orders So what’s the problem? Every time we want a new sorting criterion, the sort method must be changed can't be in a class library Having many sorting criteria leads to long and complex conditional tests A pure sorting algorithm should not have knowledge about the type of data being sorted Inflexible! 6 jonkv@ida Sorting 5: Lack of Flexibility 8 One general approach: Higher-order functions Python example from TDDD73 def combine(func,seq): result = seq[0] for element in seq[1:]: result = func(result,element) return result Takes another function as parameter String sorting example void quicksort(String[] strings, function compare) { … // Should element #i be before element #j? if (compare(strings[i], strings[j]) < 0) { … } } Takes a comparison function as a parameter Call this callback function to determine element order! Many OO languages do not support this! How do we find a pure OO solution? jonkv@ida Callbacks using Higher-Order Functions 9 OO approach: Let an object compare the elements for us public interface StringComparator { int compare(String str1, String str2); } public class StringSorter { private final StringComparator comp; public StringSorter(StringComparator comp) { this.comp = comp; } public void quicksort(String[] strings) { … // Should element #i be before element #j? if (comp.compare(strings[i], strings[j]) < 0) { … } } } Call the specified comparator's method Returns -1, 0 or 1: str1 should be before, equal to, or after str2 Specify a comparator when the sorter is created This is an object-oriented way of implementing callbacks! jonkv@ida Callbacks using Objects 10 Now you can implement as many comparators as needed! No need to change the StringSorter – reuse it for arbitrary sort orders public class LengthComparator implements StringComparator { public int compare(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length(); if (len1 > len2) return 1; else if (len1 < len2) return -1; else return 0; } } jonkv@ida Implementing Comparators 11 StringComparator lengthcomp= new LengthComparator(); StringSorter sorter = new StringSorter(lengthcomp); sorter.quicksort(strings); sorter The sorter is told which comparator to use … comp.compare() … Object header: comp … The comparator may have no fields / data, but it has methods that the sorter can call StringSorter class sort() [code] … [code] … [code] (data) … Object header: (data) LengthComparator class compare() [code] … [code] … [code] jonkv@ida Using Comparators 12 The Collections Framework has a generic comparator interface public interface Comparator<T> { int compare(T obj1, T obj2); } public class Sorter<T> { private final Comparator<T> comp; public Sorter(Comparator<T> comp) { this.comp = comp; } public void quicksort(List<T> list) { // ... // Should element #i be before element #j? if (comp.compare(list.get(i), list.get(j)) < 0) { /* ... */} } Also, built-in sort method: } Collections.sort(students, new NameLengthComparator()); jonkv@ida Java Collections Framework: Comparator Similar problems and solutions arise in many places Any JComponent in the Java GUI can have a border ▪ ▪ ▪ ▪ Not a built-in repertoire of borders Instead, callback objects implementing the Border interface Change border: component.setBorder(Border b) Can easily define a new Border, with arbitrary design 14 jonkv@ida Generalization 1: Similar Problems Any Container in the Java GUI can lay out its subcomponents ▪ Set a layout callback object using component.setLayout() ▪ Uses a LayoutManager interface with concrete managers FlowLayout, BorderLayout, … ▪ Can easily define a new LayoutManager A text component might need input validation ▪ JTextField supports an InputVerifier interface ▪ You can provide Numeric, AlphaNumeric, TelNumber verifier classes … 15 jonkv@ida Generalization 2: Similar Problems 16 There is a certain pattern in the design of these classes! Class that needs to be parameterized General interface for parametrization StringSorter – which sort order? JComponent – which border? Container – which layout? JTextField – which input pattern? StringSorter – StringComparator JComponent – Border Container – LayoutManager JTextField – InputVerifier This parametrization requires code, not just values! Concrete implementation Concrete Concrete implementation Concrete implementation implementation Strategy pattern: A comparator object specifies which comparison strategy to use, a border object specifies which border painting strategy to use, … jonkv@ida Generalization 3: A Pattern 17 StringSorter + Comparator shows one way of using a Strategy StringSorter StringComparator <<interface>> comp: Comparator quicksort(String[] array) mergesort(String[] array) DefaultOrder compare(String first, String second) compare(String first, String second) InverseOrder compare(String first, String second) ”Arrow with diamond” means aggregation: Any StringSorter has a Comparator DateOrder compare(String first, String second) Separation of concerns: StringSorter knows nothing about specific sort orders jonkv@ida One Use of Strategy: String Sorting 18 JComponent + Border shows another way of using a Strategy JComponent Border <<interface>> bord: Border paint() paintBorder(Graphics g) LineBorder paintBorder(Graphics g) EtchedBorder paintBorder(Graphics g) ThickBorder paintBorder(Graphics g) Separation of concerns: JComponent knows nothing about specific border types jonkv@ida One Use of Strategy: Border Drawing 19 We can lift out a general design pattern that can be reused! Strategy<<interface>> Context ContextInterface() AlgorithmInterface() Standardized terminology for this pattern type: Context, Strategy, ConcreteStrategyX ConcreteStrategyA AlgorithmInterface() ConcreteStrategyB AlgorithmInterface() jonkv@ida The General Strategy Pattern 21 "Pattern" has many meanings Some relevant in this context: ▪ 5. a combination of qualities, acts, tendencies, etc., forming a consistent or characteristic arrangement – Dictionary.com ▪ 10: a discernible coherent system based on the intended interrelationship of component parts <foreign policy patterns> – Merriam-Webster jonkv@ida Patterns – in general Originates in architecture! Christopher Alexander et al. (1977) state that a pattern: ▪ describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice. 22 jonkv@ida Design Patterns Object-oriented design patterns: How do we arrange our classes to solve a particular type of problem? ▪ ▪ ▪ ▪ ▪ Which interfaces and classes to create? Who knows about whom? Who inherits from whom? How do they call each other? … Creational Abstract factory Builder Factory method Prototype Singleton Structural Adapter Bridge Composite Decorator Facade Flyweight Proxy Behavioral Chain of responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Visitor 23 jonkv@ida Design Patterns and Classes 24 A typical object-oriented design pattern: Describes a general way of organizing your classes, objects, methods to solve a kind of problem Strategy<<interface>> Context ContextInterface() AlgorithmInterface() ConcreteStrategyA AlgorithmInterface() ConcreteStrategyB AlgorithmInterface() Can be quite different from how the problem would be solved in a non-OO language jonkv@ida Object-Oriented Design Patterns 25 A typical object-oriented design pattern: Can be quite different from how the problem would be solved in a non-OO language void quicksort(String[] strings, function compare) { … // Should element #i be before element #j? if (compare(strings[i], strings[j]) < 0) { … } } Gives you a repertoire of techniques that you might not have thought of on your own Has been refined to provide flexibility that the first solution you think of might lack – take advantage of others' experience! jonkv@ida Object-Oriented Design Patterns (2) 26 A typical object-oriented design pattern: Provides a common vocabulary to talk about solutions Strategy<<interface>> Context ContextInterface() AlgorithmInterface() ConcreteStrategyA AlgorithmInterface() ConcreteStrategyB AlgorithmInterface() Makes it easier to read existing code, because you know the pattern Generally can’t be completely implemented in a class library ▪ Different methods needed, different parameters, … – no single solution! jonkv@ida Object-Oriented Design Patterns (3) Object-oriented patterns are ”mined”! Only recognized as patterns when we see similar solutions reused again and again Many useful patterns have been found Should be aware of them Helps you find solutions to complex problems Helps you avoid solutions that are inflexible, difficult to maintain But don’t use them blindly! Often introduce some additional complexity: New classes, new levels of indirection (method calls to Comparator), … Sometimes worth the complexity, sometimes not! 27 jonkv@ida Object-Oriented Design Patterns (4) 29 Strategy builds on delegation Passing part of your responsibilities to someone else StringSorter StringComparator <<interface>> comp: Comparator quicksort(String[] array) mergesort(String[] array) I need to check the order between two strings… compare(String first, String second) Let some Comparator do it! public void quicksort(String[] array) { … // Should element #i be before element #j? // Let’s delegate the question! if (comp.compare(array[i], array[j]) < 0) { … } … } jonkv@ida Delegation Some consider delegation to be a design pattern But it is very abstract The original book says: It is a technique with which many patterns are implemented 30 jonkv@ida Delegation (2) Consider a spreadsheet Edit a value, press enter ▪ Call commitValue(Cell c, Value v) ▪ Must recalculate dependent values Initial solution: Just let commitValue() call recalculate() 32 jonkv@ida Spreadsheet Problem 1 Weaknesses in the initial solution: You have to update the screen as well ▪ Hard-code another call A graph may be dependent on this value ▪ Let commitValue() know about graphs 33 Why should commitValue explicitly know about these functions? Violates the Single Responsibility Principle! For every new functionality interested in value changes you will have to change commitValue()! jonkv@ida Spreadsheet Problem 2 34 Solution, once again: Callback objects public interface ChangeObserver { void notifyCellChanged(Cell c); } public class Recalculator implements ChangeObserver { public void notifyCellChanged(Cell c) { // … recalculate dependent values … } } Interface for anyone interested in changes public class ScreenUpdater implements ChangeObserver { public void notifyCellChanged(Cell c) { // … update the screen … } } jonkv@ida Spreadsheet Problem 3 public interface ChangeObserver { void notifyCellChanged(Cell c); } 35 Has a list of interested ChangeObservers Has no idea there are Recalculators, ScreenUpdaters, GraphUpdaters, … Fewer dependencies, more modular! public class SpreadsheetPage { private List<ChangeObserver> observers; public void addChangeObserver(ChangeObserver obs) { observers.add(obs); } public void commitValue(Cell c, Value v) { // … make all internal changes … notifyAllObservers(c); } Makes a change, then calls notification public void notifyAllObservers(Cell c) { for (ChangeObserver obs : observers) { obs.notifyCellChanged(c); } } } jonkv@ida Spreadsheet Problem 4 To use: SpreadsheetPage page = new SpreadsheetPage(); page.addChangeObserver(new Recalculator()); page.addChangeObserver(new ScreenUpdater()); Similar situations: Word processor ▪ Modified: Text document ▪ Interested: Spell checker + word counter + text window Web browser ▪ Modified: ▪ Interested: Bookmark list Multiple windows, each showing bookmarks 36 jonkv@ida Spreadsheet Problem 5 37 Is there a pattern? Observable Observer Object whose state changes: Spreadsheet, Document, Bookmark list Objects interested in the change: Screen updater, Spell checker, Word counter Each observable keeps track of a set of interested classes: List<ChangeObserver>, addChangeObserver(), … All interested classes implement an interface: ChangeObserver, … When there is a change, call a method in every registered observer The source of the event does not have to know who will react or how! jonkv@ida Extracting a Pattern Observer pattern 38 SpreadsheetPage Called Subject or Observable ChangeObserver +notifyCellChanged Recalculator +notifyCellChanged GraphUpdater +notifyCellChanged The Subject class only knows about the general Observer! These classes are not known to Subject looser coupling jonkv@ida Observer Pattern 39 Again: Similar class diagram Again: Different usage Call methods in a class where the caller is only aware of the interface Call methods in any number of observers Not delegation: The observers are not helping the observable achieve its task but are interested for their own sake jonkv@ida Observer Pattern 2 Example: Game with multiple "modes" per player public enum PlayerMode { NORMAL, // Standard mode STRONGER, // Stronger and less vulnerable FASTER, // Runs faster GHOST, … // Can walk, run or jump through solid matter } Many methods affected public class Player { by each power-up private PlayerMode mode; public void jump() { if (mode == PlayerMode.NORMAL) { Long conditional statements // Ordinary jump } else if (mode == PlayerMode.STRONGER) { // Jump a bit higher New powerups? Many methods } else if (mode == PlayerMode.GHOST) { to change – in Player! // Can jump through platforms from // below, land on platforms from above Not flexible + }… not good separation of concerns } 41 jonkv@ida Game Problem 1 42 Use plain inheritance? NormalPlayer GhostPlayer StrongPlayer General player code in NormalPlayer FastPlayer Inherit standard behavior, override Must replace the player (object) every time he gets a powerup! We want one player, whose behavior changes! jonkv@ida Game Problem 2 43 Plug in a behavior mode? public interface PowerupState { public void jump(Player p); } Contains what differs between powerup modes public class NormalState implements PowerupState { public void jump(Player p) { // Make p jump a short distance } } public class GhostState implements PowerupState { public void jump(Player p) { // Significant difference in code: Jump through other objects } } Also: FastState, GhostState, … jonkv@ida Game Problem 3 44 Plug in a behavior mode: public class Player { private PowerupState pstate; public Player() { this.pstate = new NormalState(); } public void jump() { … // Ask the state object to do the actual jump – delegate! pstate.jump(this); If you get a new powerup: … this.pstate = new FasterState(); } } jonkv@ida Game Problem 4 45 Expressed in UML notation: Player PowerupState<<interface>> pstate: PowerupState jump() run() … jump() run() … NormalState jump() run() … GhostState jump() run() … FastState jump() run() … jonkv@ida Game Problem 5 46 Generalization to a State Pattern Context + request() ConcreteStateA + doSomething() State <<interface>> + doSomething() ConcreteStateB + doSomething() ConcreteStateC + doSomething() jonkv@ida State Pattern 1 If you only want to represent "which state I'm in": No need for State pattern – just use an enum public enum PlayerMode { NORMAL, // Standard mode STRONGER, // Stronger and less vulnerable FASTER, // Runs faster GHOST, … // Can walk, run or jump through solid matter } To implement different complex behaviors for each state: Could use the state pattern public interface PowerupState { public void jump(); } public class NormalState implements PowerupState { public void jump() { … } } 47 jonkv@ida State Pattern 2 48 Looks similar to Strategy… Similar Class diagrams – because we use delegation But we use the classes differently! State: Strategy: There is an object whose behavior There is an object whose behavior You want to separate code for the You want to separate code for the Solution: The State pattern, Solution: The Strategy pattern, can be switched dynamically during the object’s lifetime ▪ Player receives / loses powerups different behaviors where the state is dynamically changed by the object itself can be configured when the object is created ▪ StringSorter needs a sort order different behaviors where the strategy is usually specified once, generally by the caller jonkv@ida State vs. Strategy 49 A class can use several state objects Player sstate: StrengthState vstate: VulnerabilityState StrengthState<<interface>> jump() run() … Strength determines what happens when you jump, run, … VulnerabilityState<<int…>> absorbAttack() … Vulnerability determines what happens when you are attacked Avoids the need to create one state for each combination of strength / vulnerability jonkv@ida Several States 50 Inheritance can still be used to avoid code duplication But between States, not between Players PowerupState AbstractState NormalState StrongState General behaviors inherited by default by subclasses FastState jonkv@ida State and Inheritance 51 Use the State pattern only when its flexibility is needed! public class Player { private boolean jumpsHigh; public void jump() { if (jumpsHigh) { // Jump a bit higher } else { // Ordinary jump } } } Only two options: High or not high Localized effects (here: a single method) Stick with a simple flag; State pattern too complex! jonkv@ida Appropriate Use 1 52 Use the State pattern only to modify behaviors (code)! public class Player { private int leapLength; public void leapForward() { this.x += leapLength; } } Only a value differs Stick with a simple field; State pattern too complex! class DefaultState implements StrengthState { public void leapForward() { player.x += 10; } class SuperState implements StrengthState { } public void leapForward() { player.x += 20; } } jonkv@ida Appropriate Use 2 54 User interfaces need to: Text field Bar chart Dropdown Store String Column names, data points List of items Visualize Rasterize, display Paint bars, grid lines, … Show list Manipulate Accept text input Drag bars up/down Could create a single class per component Storage, visualization, manipulation Activate, deactivate, select item jonkv@ida MVC 1: Intro 55 How to visualize the same data differently? Reimplement data storage for each visualization? How to visualize the same data in many locations? Store data in both the table and the graph? Difficult to synchronize! jonkv@ida MVC 2: Problems 56 Step 1: Extract a model class Stores all relevant information May support advanced operations on the information TextModel class TextModel { String text; int cursorPosition; void insertAtCursor(String str) { … } … } PlayerModel class PlayerModel { int xpos, ypos; Orientation leftright; List<Package> carrying; … } jonkv@ida MVC 3: Model 57 Could then use a combined view/controller TextModel class TextModel { String text; int cursorPosition; void insertAtCursor(String str) { … } … } TextComponent class TextComponent { TextModel contents; // + Visualization // + Keyboard/mouse input } Separation of concerns: Models know nothing about visualization, and vice versa jonkv@ida MVC 4: View/Controller For any Swing component: One or more Models define its state ▪ JButton ButtonModel ▪ JList ListModel and ListSelectionModel A combined View/Controller defines the graphical interface ▪ How the information in the model is shown ▪ How the information in the model can be manipulated Several Views/Controllers can show the same Model ▪ Synchronize a toolbar button and a checkbox menu item ▪ Table data shown in a table view and a graph view ▪ final PlainDocument doc = new PlainDocument(); pane.add("West", new JTextArea(doc, null, 10, 30)); pane.add("East", new JTextArea(doc, null, 10, 30)); 58 jonkv@ida MVC 5: Model + View/Controller in Swing 59 Pure MVC: Separate three distinct responsibilities! A model stores all relevant information Zero or more views display this information Zero or more controllers can alter the information For example, a Player: Model class/object: ▪ Coordinates? ▪ Facing left or right? ▪ Carrying a package? ▪ … View class/object: ▪ When requested, reads the model and displays it on screen Controller class/object: ▪ One accepts keyboard input; asks the model to move left/right, etc. ▪ One accepts joystick input Header x y orient jonkv@ida MVC 6: Further Separation 60 It is useful for the views to be notified about model changes This can be done using the Observer pattern (Keyboard) Controller (Mouse) Controller Due to keyboard input, cell [5,20] should change Dragged a bar in a chart cell [9,13] should change commitValue() Model commitValue() changes the cell, notifies all observers Views do not have to be observers: Can use other techniques (polling, direct calls) MVC and Observer are separate patterns that can be combined if you want notifyCellChanged() View Recalculator Repaints relevant parts of the screen Recalculates part of the spreadsheet Some observers are not views! jonkv@ida MVC 7: Notification In general: A factory creates things (objects) for you A factory method is a method that creates things (objects) for you ▪ public class Integer { public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } } In design patterns: The factory method pattern is a specific pattern using factory methods in a particular way Not all factory methods follow the factory method pattern! 62 jonkv@ida Terminology A game creating enemy objects: public class Game { public void playGame() { // ... Enemy enemy = new SimpleEnemy(); // ... } } 63 The concrete enemy class SimpleEnemy is hardcoded (fixed) at this point Now we want different game variations Represented as subclasses of Game Each creates a different enemy type Other parts are similar jonkv@ida Factory Method Pattern 1 64 A "game skeleton" creating enemy objects: public abstract class Game { public void playGame() { // ... Call a factory method in the same class Enemy enemy = createEnemy(); Implement this in subclasses // ... } abstract protected Enemy createEnemy(); } public class SimpleGame extends Game { protected Enemy createEnemy() { return new SimpleEnemy(); } } public class AIGame extends Game { protected Enemy createEnemy() { return new AIEnemy(); } Subclasses specify the objects (types) that are used by the superclass jonkv@ida Factory Method Pattern 2 65 AbstractProduct <<interf>> Creator operation() factoryMethod() We want the ability to change which type of object is created Solution: each subclass has its own implementation of a factory method Factory method pattern ConcreteCreator1 <<creates>> ConcreteProduct1 factoryMethod() ConcreteCreator2 <<creates>> ConcreteProduct2 factoryMethod() ConcreteCreator3 factoryMethod() (Many concrete game variations may use the same enemy class) jonkv@ida Factory Method Pattern 3 Not a design pattern: ▪ public class Integer { public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } } ▪ A factory method, but not a factory method pattern in class organization! 66 jonkv@ida Not a Pattern Correct use: Let the needs of the software drive you Be aware of patterns When implementing, consider whether a pattern is relevant If ▪ ▪ ▪ so, decide whether it is truly useful For you In this piece of software For this particular purpose Use it – or don’t! Don’t try to squeeze in as many patterns as possible ▪ Class library analogy: ”Oh no! I haven’t used LinkedList! Maybe I could change the design so that I need linked lists somewhere!” 68 jonkv@ida Conclusions Det vi lärde oss mest under projektet har nog varit objektorientering och hur ett program blir märkbart mer modulärt genom designmönster som verkligen underlättar för en, speciellt i slutskedet av kodningsprocessen när man ska minnas hur alla klasser och metoder hänger ihop, vad de innehåller och hur de kommunicerar med varandra. 69 jonkv@ida Conclusions 2 Som tips till framtida elever så kan vi rekommendera att verkligen tänka på att strukturera upp sin kod och följa designmönster. Till en början kan det kännas lättare att göra lite som man vill men det blir ganska snart ohållbart när man har flera hundra rader av kod som man ska sätta sig in i efter att man kanske inte har arbetat med koden på ett tag, eller om någon utomstående ska förstå den.