meeting -*- Outline -*- For next year, the stuff about the persistence framework seemed to bore students... Either omit it, or cover it faster using lots of handouts. * Template Method (design patterns book, p. 325 ff., Larman section 34.12) This is a basic way to use polymorphism in object-oriented languages. The idea is to define the outline of an algorithm, leaving some steps to subclasses to implement. It's a key way to abstract common code from subclasses, or to design superclasses for frameworks. ** motivation Our airline reservation system requires interfacing with a persistent storage system, such as a relational database. Were going to use this example, which also will introduce frameworks, but you shouldn't take this as the best approach to designing a persistence framework *** frameworks (34.3) ------------------------------------------ FRAMEWORKS (34.3) Definition: a *framework* is an extendable set of classes with related functions. It has: - a cohesive set of interfaces and classes - many abstract classes to be subclassed - relies on the *Hollywood principle* "don't call us, we'll call you" E.g., Java AWT, Java Swing, C++ Interviews ------------------------------------------ *** requirements for persistence service framework (34.4) ------------------------------------------ REQUIREMENTS (34.4) Provide functions such as: - store and retrieve objects in a persistent storage mechanism - commit and rollback transactions Support different storage mechanisms and formats (RDBMS, XML, ...) CONCEPTS Transactions: preserving consistency, by making sure each operation happens completely or not all Object identity: objects have a unique identity that must be preserved ------------------------------------------ *** key implementation ideas (34.5) ------------------------------------------ KEY IMPLEMENTATION IDEAS (34.5) materialization persistent ----------------> internal storage <---------------- objects dematerialization Caches: avoid I/O if possible ------------------------------------------ will use virtual proxies to achieve lazy materialization *** representing objects as tables (34.6) One way to represent objects persistently as to define a table for each persistent object class. This is the "representing objects as tables" pattern... ------------------------------------------ REPRESENTING OBJECTS AS TABLES (34.6) public class City { private String name; } --> CITY TABLE |-------------- | name | |-------------- | "Mumbai" | | "San Ramon" | | ... | |-------------- ------------------------------------------ Q: What kinds of problems arise from this type of modeling? If the objects have mutable, i.e., time-varying, state, then there can be two different manufacturer objects with the same current state Relational databases can't represent non-atomic fields (First Normal Form) We might waste storage if we materialize the same object twice This leads to... *** pattern: object identifier (34.8) ------------------------------------------ OBJECT IDENTIFIERS (34.8) public class City { private String name; private OID oid; City(OID oid, String name) { this.oid = oid; this.name = name; } } --> CITY TABLE |----------------------- | OID | name | |----------------------- | manf34 | "Mumbai" | | manf35 | "San Ramon" | | ... | ... | |----------------------- ------------------------------------------ The object identity forms the primary key of the table, and each object has object of the inside of it. The OIDs are instances of some interface or class encapsulates the actual value of object identities. In the DBMS, these may be stored as strings. *** accessing the persistence service within a facade (34.9) The first step in the design of the subsystem is to define a facade for its services. import java.util.Hashtable; public class PersistenceFacade { public Object get(OID oid, Class c) { IMapper mapper = (IMapper) mappers.get(c); return mapper.get(oid); } public void put(OID oid, Object o) { /* ... */ } private Hashtable mappers; { mappers = new Hashtable(); mappers.put(City.class, new CityRDBMapper()); } // this is a singleton protected PersistenceFacade() { } private static PersistenceFacade instance; public static PersistenceFacade getInstance() { if (instance == null) { instance = new PersistenceFacade(); } return instance; } } Show the class diagram for this, see fig 34.4 *** Mapping Objects: Database Mapper (Broker) pattern (34.10) The facade doesn't do the work itself, but delegates to other classes. In the Database Mapper pattern, have one map object for each class of persistent object, find them via a hash table... public Object get(OID oid, Class c) { IMapper mapper = (IMapper) mappers.get(c); return mapper.get(oid); } [[[put figure 34.5 here, and explain IMapper]]] see fig 34.5, but use CityRDBMapper, etc. public interface IMapper { Object get(OID oid); void put(OID oid, Object o); } Note that there will be some common code between all of the get methods... Better is to have mapping based on metadata (schema read from file...) ** Materialization with the Template method Pattern (34.12) ------------------------------------------ BASIC DESIGN FOR MATERIALIZATION if (object not in cache) { create from external rep save in cache } return object in cache What parts change among different kinds of persistent storage mechanisms? ------------------------------------------ ... the way it is created from external rep This is the basic idea of the template method pattern... ------------------------------------------ TEMPLATE METHOD import java.util.Hashtable; public abstract class AbstractMapper implements IMapper { public final Object get(OID oid) { Object obj = cachedObjects.get(oid); if (obj == null) { // call hook method obj = getFromStorage(oid); cachedObjects.put(oid, obj); } return obj; } // hook method protected abstract Object getFromStorage(OID oid); private Hashtable cachedObjects = new Hashtable(); public final void put(OID oid, Object o) { putToStorage(oid, o); } protected abstract void putToStorage(OID oid, Object o); } ------------------------------------------ show figure 34.7 ------------------------------------------ CONCRETE SUBCLASS EXAMPLE import java.sql.*; public class CityRDBMapper extends AbstractMapper implements DBAccountConsts { protected Object getFromStorage(OID oid) { try { String key = oid.toString(); String url = "jdbc:odbc:reservsys"; Connection con = DriverManager.getConnection( url, USERNAME, PASSWORD); Statement stmt = con.createStatement(); ResultSet dbRec = stmt.executeQuery( "Select * from FLIGHT where key =" + key); String name = dbRec.getString("NAME"); City c = new City(oid, name); return c; } catch (SQLException se) { return null; } } protected void putToStorage( OID oid, Object o) { // ... } } ------------------------------------------ extend the class diagram as in figure 34.8 Q: Would any of this could be common between different subclasses of AbstractMapper? yes, so could apply the template method pattern again, making another abstract class, AbstractRDBMapper, with a template method getFromStorage, which calls a hook method, getFromRecord, see figure 34.9 and files AbstractRDBMapper, CityRDBMapperBetter.java in UML, use {abstract} for it abstract methods, and # with {leaf} for protected final methods *** discussion the template method pattern is basic for avoiding code duplication: 1. identify commonalities and differences in existing code of subclasses 2. make (a superclass and) a template method in the superclass to hold the common code 3. declare the hook method(s) in the superclass, and move the different code into the hook method(s) Q: In what way that the template method pattern limit how subclasses behave? this can be a good thing Q: In what way does the template method pattern satisfy the "Hollywood principle"? Q: Why should the hook method be declared protected in the superclass? Q: Can a superclass provide default behavior for a hook method? to be effective, subclasses have to know which methods are: - hooks (may be overridden) - abstract (must be overridden) ------------------------------------------ WHY NOT DO IT THIS WAY? public class Parent { public void operation() { // ... } } public class Child extends Parent { public void operation() { // extensions to behavior ... super.operation(); // possibly other extensions ... } } ------------------------------------------ more code for the subclass to write (so better for frameworks), it's easy to forget the call the inherited operation *** other examples template method is often used in user interfaces void View::Display() { SetFocus(); DoDisplay(); // hook ResetFocus(); } void View::DoDisplay() { } void MyView::DoDisplay() { // ... render ... } template method is so fundamental, it's used in almost every abstract class *** related patterns Factory methods are often called by template methods Strategies use delegation to vary an entire algorithm, as opposed to template methods, which only vary part of an algorithm ** more about persistence *** synchronized or guarded methods The get method in the abstract persistence manager is not thread safe, one has to make sure that the cache is properly maintained. Use synchronized in Java, {guard} in the UML *** creating the mappers (34.13) Q: How would you create all of these mapper classes? Use a factory class, however... Q: How would you protect against variation in the number of classes to be mapped to the persistent storage? instead of having a factory method for each class, have a single factory method which returns a hash table from classes to the mapper for that class *** cache management (34.14) it's desirable to cache objects locally to: - improve performance - make sure that there's only one copy of each object locally