1 Dept. of Computer Science & Engineering, York University, Toronto CSE3311 Software Design State Pattern
2 Problem You are developing a program to keep track of movies for a video store. There are three movie types: regular, children’s, and new releases. The charge for a movie will depend on the length of the rental and its type. Also, customers receive a number of frequent renter points that depend on the type of the movie rented. We need the ability to calculate the price and frequent renter points that accrue to a customer renting a movie. Develop a BON diagram demonstrating the classes you might include in this system along with their relationships. Do not be concerned with user interface classes.
3 First Design class MOVIE1 create make feature {NONE} – Initialization make(n,t: STRING) is do name := n type := t end feature name: STRING type: STRING
4 First Design price(days_rented: INTEGER): REAL is require positive_days: days_rented >= 0 do if type.is_equal("REG") then Result := 2 if days_rented > 2 then Result := Result + (days_rented - 2) * 1.5 end elseif type.is_equal("NEW") then Result := days_rented * 3 elseif type.is_equal("CHI") then Result := 1.5 if days_rented > 3 then Result := Result + (days_rented - 3) * 1.5 end
5 First Design points: INTEGER -- frequent renter points do if type.is_equal("REG") then Result := 100 elseif type.is_equal("NEW") then Result := 200 elseif type.is_equal("CHI") then Result := 150 end set_type(t: STRING) do type := t.twin end end -- class MOVIE1 How will we change the state from NEW to REG?
6 First Design test class make local m1, m2, m3: MOVIE1 do -- Design 1: It works but changes are very cumbersome print("Design1%N") create m1.make("Casablanca", "REG") create m2.make("Bambi", "CHI") create m3.make("The Lord of the Rings", "NEW") print (m1.price(2).out + "%N") print (m2.price(1).out + "%N") print (m3.price(5).out + "%N") print (m3.points.out + " points%N") m3.set_type ("REG") print("The new price for The Lord of the Rings is: ") print (m3.points.out + " points%N%N") end
7 Critique? Suppose we need to add a new state ADVENTURE_MOVIE? price is calculated differently frequent renter points is calculated different Will need to change multiple places (price and points routine logic) there may be many more type dependent routines such as special_discount etc. Single Choice Principle Violated
8 Design Two price* points* Change state (e.g.state ”reg”, “chi”) from class STRING to a class MOVIE2 Use polymorphism and dynamic binding to delegate calculation of price and points to MOVIE2 Adding a new state is easy Removes if … elseif … elseif … end statements in class MOVIE1
9 Second Design test class make is -- Creation procedure. local m1, m2, m3: MOVIE1 m4, m5, m6: MOVIE2 do -- Design 1: It works but changes are very cumbersome -- Design2: Takes advantage of polymorphism print("Design2%N") create {REGULAR} m4.make("Casablanca") create {CHILDRENS} m5.make("Bambi") create {NEW} m6.make("The Lord of the Rings") print (m4.price(2).out + "%N") print (m5.price(1).out + "%N") print (m6.price(5).out + "%N") print (m6.points.out + " points%N") print("How to change 'Lord of Rings' to regular???%N%N") end Position of Single Choice Delegate price & points to MOVIE2 (polymorphism + dynamic binding)
10 Final Design! price* points* price points set_type Delegate price & points to MOVIE_STATE Dynamic change of the state Single choice happens here
11 Final Design class MOVIE3 create make feature {NONE} – Initialization make(n,t: STRING) is do name := n; set_type(t) end feature name: STRING type: MOVIE_STATE set_type (t: STRING) do if t.is_equal("NEW") then create {NEW_STATE} type elseif t.is_equal("REG") then create {REGULAR_STATE} type elseif t.is_equal("CHI") then create {CHILDRENS_STATE} type end price (days_rented: INTEGER): REAL is do Result := type.price(days_rented) end points: INTEGER do Result := type.points end end Single Choice Delegate price & points to MOVIE_STATE
12 Final Design test class Design 1: It works but changes are very cumbersome Design2: Takes advantage of polymorphism, but what if "The Lord of the Rings" is no longer a NEW movie? Design 3: Polymorphism is now delegated to the state object. Adding a new state is trivial make local m7, m8, m9: MOVIE3 do create m7.make("Casablanca", "REG") create m8.make("Bambi", "CHI") create m9.make("The Lord of the Rings", "NEW") print (m7.price(2).out + "%N") print (m8.price(1).out + "%N") print (m9.price(5).out + "%N") print (m9.points.out + " points%N") m9.set_type ("REG") print("The new price for The Lord of the Rings is: ") print (m9.price(5).out + "%N") print (m9.points.out + " points%N") end
13 State Pattern: allows an object to alter its behaviour when its internal state changes. The object will appear to change its class By encapulating each state we localize any changes that will need to be made (e.g. price and points) to that state (behaviour)
14
15
16 State vs. Strategy The class diagrams are similar but they differ in intent State Behaviours are constantly changing over time and the client (context) knows very little about how those different behaviours work Encapsulate behaviours in state objects and set change in the context Alternative to putting a lot of conditionals in the context Strategy Client knows quite a lot about what behaviour (state) is most appropriate e.g. we know that a mallard duck has typical flying behaviour and a decoy duck never flies Change in state less usual Flexible alternative to subclassing
17
18
19 adding new states
20
21