Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Chapter 23 COMPOSITE COMPOSITE Shape Example <<interface>> Shape 0,,* <<delegates>> +draw() 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? Sensor Sensor 0..* Command Command Composite Command 0..* Alternative 1: Sensor maintains a list of Command objects. Observation: Always just iterated over list and called do() Alternative 2: No modifications to Sensor or Command classes, just create a CompositeCommand using COMPOSITE pattern. One-to-one relationship is easier to code, understand and maintain than a one-tomany 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 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 Remote Switch Light + turnOn + turnOff Simple Code public class RemoteSwitch { private Light light; private boolean switchedOn = false; public RemoteSwitch() { light = new Light(); } public class Light { public void turnOn() { System.out.println("Light is on"); } 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 void turnOff() { System.out.println("Light is off"); } Naïve Design – Try to Reuse Remote Switch Fan Switch Light + turnOn + turnOff Fan + turnOn + turnOff Can’t just create a subclass of Switch because FanSwitch still inherits dependency upon Light. Try to reuse public class FanSwitch extends RemoteSwitch { private Fan fan; public class Fan { public void turnOn() { System.out.println("Fan is on"); } public FanSwitch() { fan = new Fan(); } public void turnOff() { System.out.println("Fan is off"); } // Hmmm, how do I reuse anything? } } QUICK EX (without looking ahead): What’s a solution? ABSTRACT SERVER design pattern Switch <<interface>> Switchable + turnOn + turnOff Fan + turnOn + turnOff Light + turnOn + turnOff Introduce an interface* in between Switch and Light. Now Switch can control anything that implements that interface. Notice the name is Switchable not ILight. 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 static void main(String[] args) { TheRemote lightRemote = new TheRemote(new Light()); TheRemote fanRemote = new public TheRemote(Switchable switchedObj) TheRemote(new Fan()); { this.switchedObject = switchedObj; lightRemote.pressSwitch(); } fanRemote.pressSwitch(); fanRemote.pressSwitch(); public void pressSwitch() { lightRemote.pressSwitch(); if (switchedOn) { } switchedObject.turnOff(); } switchedOn = false; } else { switchedObject.turnOn(); switchedOn = true; } } public class TheRemote { private Switchable switchedObject; private boolean switchedOn = false; Adapter Pattern 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 Switch Fan + turnOn + turnOff <<interface>> Switchable + turnOn + turnOff CDAdapter CDPlayer cd + turnOn + turnOff Extra overhead instantiating the adapter Time and space for delegation. Use this if needed, prefer ABSTRACT SERVER <<delegates>> CDPlayer + cdOn + cdOff ADAPTER Problem may be that class is not Switchable (even though it has the right functions) Switch Fan + turnOn + turnOff <<interface>> Switchable + turnOn + turnOff TVAdapter TV tv + turnOn + turnOff <<delegates>> TV + turnOn + turnOff Some Code public interface Switchable { public void turnOn(); public void turnOff(); } public class CDPlayer { public void cdOn(){ System.out.println("CD is on"); } public void cdOff(){ System.out.println("CD is off"); } } public class CDAdapter implements Switchable { private CDPlayer cd; public CDAdapter(){ cd = new CDPlayer(); which to use depends on app – if } CD player is created already, use public CDAdapter(CDPlayer cd){ 2nd option this.cd = cd; } public void turnOn(){ cd.cdOn(); } Delegation! public void turnOff(){ cd.cdOff(); } } Add to TheRemote public static void main(String[] args) { // controlling fan and light CDAdapter adapter = new CDAdapter(); TheRemote cdRemote= new TheRemote(adapter); cdRemote.pressSwitch(); cdRemote.pressSwitch(); CDAdapter adapter2 = new CDAdapter(new CDPlayer()); TheRemote cdRemote2= new TheRemote(adapter2); cdRemote2.pressSwitch(); cdRemote2.pressSwitch(); } More Code public interface Switchable { public void turnOn(); public void turnOff(); } public class TV { public void turnOn(){ System.out.println("TV is on"); } public void turnOff(){ System.out.println("TV is off"); } } 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(); } Delegation! public void turnOff(){ tv.turnOff(); } } 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… Example problem Customer - name - address - billingInformation 0..* Order - date - status Simple shopping cart object model 0..* Product - name - price - sku Item - quantity 0..* cusid Customer -cusid - name - address - billingInformation Order - orderId - cusid - date - status Shopping cart relational data model orderID 0..* sku Item - orderId - quantity Product - sku - name - price Code for two examples public void addItem(Product p, int qty) { Item item = new Item(p,qty); itsItems.add(item); } 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 DB Product DB Proxy <<delegates>> Product Implementation 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 getPrice() price DB created by DB retrieveProduct(sku) Product Implementation Product getPrice() price Using a proxy is nontrivial. PROXY shopping cart 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 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); } 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(); This is the method that returns the ProductData } 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; 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); Use database result to create pd.price = rs.getInt(3); ProductData 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; } 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(); Notice pattern: prepare statement, execute it, s.close(); extract results, close ResultSet and return id; PreparedStatement } private static PreparedStatement buildItemsForOrderQueryStatement(int orderId) throws SQLException { PreparedStatement s = con.prepareStatement ("SELECT * FROM Items WHERE orderid = ?;"); s.setInt(1, orderId); return s; 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; } 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. public String getName() throws Exception { ProductData pd = DB.getProductData(itsSku); Remember the database engine return pd.name; also does caching. } public String getSku() throws Exception { return itsSku; } } 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 Application Layer 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. API Application Layer API 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. 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 STAIRWAY TO HEAVEN Achieves same dependency inversion as PROXY Variation on class form of ADAPTER Product only contains business rules, no hint of persistence at all. PersistentObject + write + read Product Persistent Product Assembly Persistent Assembly abstract class, write & read are abstract, includes methods that can be used to implement read&write. uses tools of PersistentObject to implement read & write for Products implements read & write for assembly, inherits read & write for Product fields 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. Product Assembly Database Façade + readProduct + writeProduct + readAssembly + writeAssembly