450 likes | 583 Views
Chapter 23. COMPOSITE. COMPOSITE Shape Example. <<interface>> Shape +draw(). 0,,*. <<delegates>>. Circle. Square. Composite Shape +add() +draw(). keeps a list of many Shape instances when draw() is called, it delegates to all Shape instances in the list
E N D
Chapter 23 COMPOSITE
COMPOSITE Shape Example <<interface>> Shape +draw() 0,,* <<delegates>> Circle Square Composite Shape +add() +draw() • keeps a list of many Shape instances • when draw() is called, it delegates to all Shape instances in the list • appears to system to be a single Shape, can be passed to any function or object that takes a Shape • really a proxy for a group of Shapes
COMPOSITE Shape Example Code public interface Shape { public void draw(); } public class CompositeShape implements Shape { private Vector itsShapes = new Vector(); public void add(Shape s) { itsShapes.add(s); } public void draw(){ for (int i = 0; i < itsShapes.size(); i++) { Shape shape = (Shape) itsShapes.elementAt(i); shape.draw(); } } }
COMPOSITE Shape Example Test Code public void testCompositeWithNShapes() throws Exception { CompositeShape s = new CompositeShape(); for (int i = 0; i < 100; i++) { s.add(new TestShape()); } s.draw(); assertEquals(100, drawCount); } Tests include Null and TestOneShape, not shown Notice the call to s.draw(), same as if s were a Shape object
COMPOSITE Sensor/Command Example • Previous example: Sensor objects respond to event (relay, clutch, etc.), must execute some Command • Used a Command structure with a do() method • What if a Sensor needs to execute multiple commands? Alternative 1: Sensor maintains a list of Command objects. Observation: Always just iterated over list and called do() 0..* Command Sensor 0..* Alternative 2: No modifications to Sensor or Command classes, just create a CompositeCommand using COMPOSITE pattern. Command Sensor Composite Command One-to-one relationship is easier to code, understand and maintain than a one-to-many relationship. NOTE: Only use COMPOSITE when every object in list is treated identically (e.g., just call do(), just call draw(), etc.)
Chapter 24 Observer – Backing into a Pattern
To Do: • This chapter gives a demonstration of how the design of a program can evolve to use a pattern. • Read the chapter and answer the chapter questions
Chapter 25 ABSTRACT SERVER and Adapter Bridge not covered
Motivating Example • Design software that runs inside a simple table lamp. Table lamp has a remote switch and light. You can ask switch if it is on or off, and you can tell the light to turn on or off. • Various proposed solutions: switch object + lamp object, lamp object that contains switch + light, electricity as object, power-cord as object??
Naïve first attempt Remote Switch Light + turnOn + turnOff Violates 2 principles: Open-Closed Principle (OCP) because requires Light everywhere we need a Switch. Not easy to extend Switch to control other types of objects Dependency-Inversion Principle (DIP) because Switch is dependent on concrete class, prefer to depend on abstract class
Simple Code public class RemoteSwitch { private Light light; private boolean switchedOn = false; public RemoteSwitch() { light = new Light(); } public void pressSwitch() { if (switchedOn) { light.turnOff(); switchedOn = false; } else { light.turnOn(); switchedOn = true; } } public static void main(String[] args){ RemoteSwitch remote = new RemoteSwitch(); remote.pressSwitch(); remote.pressSwitch(); } } public class Light { public void turnOn() { System.out.println("Light is on"); } public void turnOff() { System.out.println("Light is off"); } }
Naïve Design – Try to Reuse Can’t just create a subclass of Switch because FanSwitch still inherits dependency upon Light. Remote Switch Light + turnOn + turnOff Fan + turnOn + turnOff Fan Switch
Try to reuse public class FanSwitch extends RemoteSwitch { private Fan fan; public FanSwitch() { fan = new Fan(); } // Hmmm, how do I reuse anything? } public class Fan { public void turnOn() { System.out.println("Fan is on"); } public void turnOff() { System.out.println("Fan is off"); } }
ABSTRACT SERVER design pattern Introduce an interface* in between Switch and Light. Now Switch can control anything that implements that interface. Notice the name is Switchable not ILight. <<interface>> Switchable + turnOn + turnOff Switch Fan + turnOn + turnOff Light + turnOn + turnOff AbstractServer is a pattern, DIP and OCP are principles * Remember that an interface can have abstract functions (function signatures) and constants, but no instance variables. Use the keyword implements.
Some code public interface Switchable { public void turnOn(); public void turnOff(); } public class Light implements Switchable { public void turnOn(){ System.out.println("Light is on"); } public void turnOff(){ System.out.println("Light is off"); } } public class Fan implements Switchable { public void turnOn(){ System.out.println("Fan is on"); } public void turnOff(){ System.out.println("Fan is off"); } }
And the RemoteSwitch: public class TheRemote { private Switchable switchedObject; private boolean switchedOn = false; public TheRemote(Switchable switchedObj) { this.switchedObject = switchedObj; } public void pressSwitch() { if (switchedOn) { switchedObject.turnOff(); switchedOn = false; } else { switchedObject.turnOn(); switchedOn = true; } } public static void main(String[] args) { TheRemote lightRemote = new TheRemote(new Light()); TheRemote fanRemote = new TheRemote(new Fan()); lightRemote.pressSwitch(); fanRemote.pressSwitch(); fanRemote.pressSwitch(); lightRemote.pressSwitch(); } }
ADAPTER • No access to source code • Assume have CDPlayer class which has required functionality (e.g., cdOn, cdOff) but function names don’t match interface • Can add “adapter” class which delegates to CDPlayer <<interface>> Switchable + turnOn + turnOff Extra overhead instantiating the adapter Time and space for delegation. Use this if needed, prefer ABSTRACT SERVER Switch Fan + turnOn + turnOff CDPlayer + cdOn + cdOff CDAdapter CDPlayer cd + turnOn + turnOff <<delegates>>
ADAPTER • Problem may be that class is not Switchable (even though it has the right functions) <<interface>> Switchable + turnOn + turnOff Switch Fan + turnOn + turnOff TV + turnOn + turnOff TVAdapter TV tv + turnOn + turnOff <<delegates>>
Some Code public class CDPlayer { public void cdOn(){ System.out.println("CD is on"); } public void cdOff(){ System.out.println("CD is off"); } } public interface Switchable { public void turnOn(); public void turnOff(); } public class CDAdapter implements Switchable { private CDPlayer cd; public CDAdapter(){ cd = new CDPlayer(); } public CDAdapter(CDPlayer cd){ this.cd = cd; } public void turnOn(){ cd.cdOn(); } public void turnOff(){ cd.cdOff(); } } which to use depends on app – if CD player is created already, use 2nd option Delegation!
More Code public class TV { public void turnOn(){ System.out.println("TV is on"); } public void turnOff(){ System.out.println("TV is off"); } } public interface Switchable { public void turnOn(); public void turnOff(); } public class TVAdapter implements Switchable { private TV tv; public TVAdapter(){ tv = new TV(); } public TVAdapter(TV tv){ this.tv = tv; } public void turnOn(){ tv.turnOn(); } public void turnOff(){ tv.turnOff(); } } which to use depends on app – if CD player is created already, use 2nd option Delegation!
More Code (similar to TheRemote) public class RemoteSwitch { private Switchable switched; private boolean isOn; public RemoteSwitch(Switchable switched){ this.switched = switched; isOn = false; } public void pressSwitch(){ if (isOn) switched.turnOff(); else switched.turnOn(); isOn = !isOn; } public static void main(String[] args) { CDAdapter adapter = new CDAdapter(); RemoteSwitch mySwitch = new RemoteSwitch(adapter); mySwitch.pressSwitch(); mySwitch.pressSwitch();} }
Chapter 26 PROXY and STAIRWAY TO HEAVEN: Managing Third Party APIs
Barriers to cross • Move data from program to database, cross the database barrier • Send message from one computer to another, cross the network barrier Database Can be complex, use patterns to help us cross these barriers, keep program centered on more interesting issues…
Customer - name - address - billingInformation Customer -cusid - name - address - billingInformation Order - orderId - cusid - date - status Order - date - status Product - name - price - sku Product - sku - name - price Item - quantity Item - orderId - quantity Example problem 0..* Simple shopping cart object model 0..* 0..* Shopping cart relational data model cusid orderID 0..* sku
public void addItem(Product p, int qty) { Item item = new Item(p,qty); itsItems.add(item); } Code for two examples public void addItem(Product p, String sku, int qty) { Statement s = itsConnection.CreateStatement(); s.executeUpdate(“insert into items values( “ + orderId + “,” + sku + “,” + qty + “)”); } Same logical function, first ignores database, second glorifies it!
PROXY pattern • Test program to interact with database: public void testOrderPrice() throws Exception { OrderImp o = new OrderImp("Bob"); Product toothpaste = new ProductImp("sku1", "Toothpaste", 129); o.addItem(toothpaste, 1); assertEquals(129, o.total()); Product mouthwash = new ProductImp("sku2", "Mouthwash", 342); o.addItem(mouthwash, 2); assertEquals(813, o.total()); } Uses object model, does not assume anything about database. Source for Order, Product, Item in textbook.
PROXY static model <<interface>> Product Product DB Proxy Product Implementation <<delegates>> DB Proxied objects are split into 3 parts: • Interface with all methods clients need to invoke • Class that implements those methods • Proxy that knows about the database ProductImplementation implements Product interface (set/get price etc) ProductDBProxy implements methods to fetch product from database, create an instance of ProductImplementation, delegate messages to it. Neither client nor ProductImplementation need to know about proxy.
PROXY dynamic model Product DB Proxy DB created by DB getPrice() retrieveProduct(sku) Product Implementation price Product getPrice() price Using a proxy is nontrivial.
PROXY shopping cart public class DBTest extends TestCase // some methods not shown { public void setUp() throws Exception { DB.init(); DB.clear(); } public void tearDown() throws Exception { DB.close(); } public void testStoreProduct() throws Exception { ProductData storedProduct = new ProductData("MyProduct", 1234, "999"); DB.store(storedProduct); ProductData retrievedProduct = DB.getProductData("999"); assertEquals(storedProduct, retrievedProduct); } • Will use proxy for Product class • Implemented as a simple dictionary – no table manipulation (simple example) • Require database utility to store/retrieve product data test shows that DB works, at least minimally
PROXY shopping cart code public class ProductData { public String name; public int price; public String sku; public ProductData() { } public ProductData(String name, int price, String sku) { this.name = name; this.price = price; this.sku = sku; } public boolean equals(Object o) { ProductData pd = (ProductData)o; return name.equals(pd.name) && sku.equals(pd.sku) && price==pd.price; } public String toString() { return ("ProductData("+sku+","+name+","+price+")"); } }
shopping cart code, continued public class DB { private static Connection con; public static void init() throws Exception { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // load driver con = DriverManager.getConnection("jdbc:odbc:PPP Shopping Cart"); } public static void store(ProductData pd) throws Exception { PreparedStatement s = buildProductInsertionStatement(pd); executeStatement(s); } private static PreparedStatement buildProductInsertionStatement(ProductData pd) throws SQLException { PreparedStatement s = con.prepareStatement ("INSERT into Products VALUES (?, ?, ?)"); s.setString(1, pd.sku); s.setString(2, pd.name); s.setInt(3, pd.price); return s; }
shopping cart code, continued public static ProductData getProductData(String sku) throws Exception { PreparedStatement s = buildProductQueryStatement(sku); ResultSet rs = s.executeQuery(); ProductData pd = null; if (rs.next()) { pd = extractProductDataFromResultSet(rs); rs.close(); } s.close(); return pd; } private static PreparedStatement buildProductQueryStatement(String sku) throws SQLException { PreparedStatement s = con.prepareStatement ("SELECT * FROM Products WHERE sku = ?;"); s.setString(1, sku); return s; } This is the method that returns the ProductData Query by sku, probably reuse
shopping cart code, continued private static ProductData extractProductDataFromResultSet(ResultSet rs) throws SQLException { ProductData pd = new ProductData(); pd.sku = rs.getString(1); pd.name = rs.getString(2); pd.price = rs.getInt(3); return pd; } public static void store(ItemData id) throws Exception { PreparedStatement s = buildItemInsersionStatement(id); executeStatement(s); } private static PreparedStatement buildItemInsersionStatement(ItemData id) throws SQLException { PreparedStatement s = con.prepareStatement ("Insert into Items(orderId,quantity,sku) VALUES (?, ?, ?);"); s.setInt(1,id.orderId); s.setInt(2,id.qty); s.setString(3, id.sku); return s; } Use database result to create ProductData
shopping cart code, continued public static ItemData[] getItemsForOrder(int orderId) throws Exception { PreparedStatement s = buildItemsForOrderQueryStatement(orderId); ResultSet rs = s.executeQuery(); ItemData[] id = extractItemDataFromResultSet(rs); rs.close(); s.close(); return id; } private static PreparedStatement buildItemsForOrderQueryStatement(int orderId) throws SQLException { PreparedStatement s = con.prepareStatement ("SELECT * FROM Items WHERE orderid = ?;"); s.setInt(1, orderId); return s; } Notice pattern: prepare statement, execute it, extract results, close ResultSet and PreparedStatement May also query database by orderid
shopping cart code, continued private static ItemData[] extractItemDataFromResultSet(ResultSet rs) throws SQLException { LinkedList l = new LinkedList(); for (int row = 0; rs.next(); row++) { ItemData id = new ItemData(); id.orderId = rs.getInt("orderid"); id.qty = rs.getInt("quantity"); id.sku = rs.getString("sku"); l.add(id); } return (ItemData[]) l.toArray(new ItemData[l.size()]); }
shopping cart code, continued private static void executeStatement(PreparedStatement s) throws SQLException { s.execute(); s.close(); } public static void close() throws Exception { con.close(); } public static void clear() throws Exception { Statement s = con.createStatement(); s.execute("delete * from orders;"); s.execute("delete * from items;"); s.execute("delete * from products;"); s.close(); } } // MORE METHODS IN TEXTBOOK
shopping cart code, continued public interface Product { public int getPrice() throws Exception; public String getName() throws Exception; public String getSku() throws Exception; } public class ProductImp implements Product { private int itsPrice; private String itsName; private String itsSku; public ProductImp(String sku, String name, int price) { itsPrice = price; itsName = name; itsSku = sku; } public int getPrice() { return itsPrice; } // getName and getSku also in textbook }
shopping cart code, continued public class ProductProxy implements Product { private String itsSku; public ProductProxy(String sku) { itsSku = sku; } public int getPrice() throws Exception { ProductData pd = DB.getProductData(itsSku); return pd.price; } public String getName() throws Exception { ProductData pd = DB.getProductData(itsSku); return pd.name; } public String getSku() throws Exception { return itsSku; } } Are accesses to DB going to be a performance problem? Could change so cache info, but reasonable to wait til you know if it’s an issue. Remember the database engine also does caching.
Different from canonical pattern • Pattern would have ProductProx create a ProductImp in every method. Wasted effort in this situation. public int getPrice() throws Exception { ProductData pd = DB.getProductData(itsSku); ProductImp p = new ProductImp(pd.sku, pd.name, pd.price); return p.getPrice(); }
Exercise • With a partner, create a proxy for Order. • Must pass ProxyTest • Turn in your code + comparison of your code to book solution/analysis of your effort (was it easy, anything you missed, etc). • Explain what the author did to remove Exceptions. public void testOrderProxyTotal() throws Exception { DB.store(new ProductData("Wheaties", 349, "wheaties")); DB.store(new ProductData("Crest", 258, "crest")); ProductProxy wheaties = new ProductProxy("wheaties"); ProductProxy crest = new ProductProxy("crest"); OrderData od = DB.newOrder("testOrderProxy"); OrderProxy order = new OrderProxy(od.orderId); order.addItem(crest, 1); order.addItem(wheaties, 2); assertEquals(956, order.total()); }
Summary of PROXY Application Problem to be solved: application becomes polluted with calls to API, SQL statements, etc. API Common to add a layer between application and API, but this arrangement has an issue: transitive dependence from application to API. This indirect independence may be enough to cause problems. Application Layer API Application The PROXY pattern inverts this. The Application does not depend on proxies at all. Proxies depend on the application and the API. Result: proxies are nightmares. If API changes, proxy must change. If application changes, proxy must change. BUT at least change is not spread throughout application code. Layer API
Summary of PROXY, continued • Proxies are a heavyweight solution • Best to avoid if not needed! • Could be useful in systems with frequent schema or API thrashing • Also used in systems that can ride on top of many different database engines or middleware engines
PersistentObject + write + read STAIRWAY TO HEAVEN • Achieves same dependency inversion as PROXY • Variation on class form of ADAPTER abstract class, write & read are abstract, includes methods that can be used to implement read&write. Product only contains business rules, no hint of persistence at all. uses tools of PersistentObject to implement read & write for Products Persistent Product Product implements read & write for assembly, inherits read & write for Product fields Persistent Assembly Assembly Pattern requires multiple inheritance – virtual inheritance to deal with “deadly diamond” issues. Example code in textbook.
Other patterns used with databases • Extension object – extension object knows how to write object • Visitor – the visitor hierarchy knows how to write visited object • Decorator – decorate a business object with read and write methods OR decorate a data object than can read and write with business rules • Façade – Good starting point! But it couples business-rule objects with the database. Database Façade + readProduct + writeProduct + readAssembly + writeAssembly Product Assembly