Contents






Affiliated sites






Other sites










         

Business best practice - design patterns and OOD


Following GoF (Gang-of-Four) patterns in the now legendary book "Design Patterns - Elements of Reusable Object-Oriented Software" by Eric Gamme, Richard Helm, Ralph Johnson, John Vlissides, 1994 - additional proposals for supplementary patterns were put forward by people. The book "Patterns in Java" vol 1+2 by Mark Grand offered insight into other commenly used patterns.
Benefits of Design Patterns
  • Teaches good design
  • Proven best practice
  • Define set of concepts used in communication between designers, architects and developers
  • Systematicly captures reusable experience, solving re-occurring types of problem
GoF Business Design Patterns
  1. Fundamental
    1. Delegation - or when not to use inheritance
    2. Interface
    3. Immutable
    4. Marker Interface
    5. Proxy
  2. Creational
    1. Object Pool
  3. Partitioning
    1. Layered Initialization
    2. Filter
  4. Structural
    1. Dynamic Linkage
    2. Virtual Proxy
    3. Cache Management
  5. Behavioral
    1. Little Language
    2. Snapshot
    3. Null Object
  6. Concurrency
    1. Single Threaded Execution
    2. Guarded Suspension
    3. Balking
    4. Schedular
    5. Read/Write lock
    6. Producer-Consumer
    7. Two Phase Termination
1. Fundamental Patterns
Fundamental: Involved with the basic handling of objects.
1.1 Delegation (When not using inheritance) [Grand98]

Defines how to differentiates between use of inheritance/delegation. Inheritance defines a new class, which use the interface of a parent class while adding extra, more problem-specific methods. Delegation is a way of reusing and extending the behavior of a class by writing a new class that incorporates the functionality of the original class by using an instance of the original class and calling its methods. No. 1 issue in OO is if a class A should inherit from B or A should use B.

Inheritance is a common way of extending and reusing the functionality of a class. However, inheritance is inappropriate for many situations:

  • Inheritance is useful for capturing is-a-kind-of relationships which are rather static in nature.
  • is-a-role-played-by relationships are awkward to model by inheritance, where delegation could be a better choice. Using instances of a class to play multiple roles.

Places where not to use inheritance (but rather delegation):
  • Don't use inheritance where roles interchange. For example, consider the example of an airline reservation system (SCEA-2 assignment). A reservation system may include such roles as passenger, ticket selling agent and flight crew. A class called Person may use subclasses corresponding to each of these roles. Problem is that the same person can fill more than one of these roles. A person who is normally part of a flight crew can also be a passenger. Etc. This way, the number of subclasses would increase exponentially.
  • Don't use inheritance where there will arise a need for an object to be a different subclass of a class at different points in time, then it should not be a subclass of that class in the first place. Like in the reservation situation, pilot may be derived from Person - but when the pilot sells tickets, he ought to have been derived from ticket seller...
  • Don't use inheritance if you end up in a situation where a class is trying to hide a method or variable inherited from a superclass from other classes, then that class should not inherit from that subclass.
  • Don't use inheritance of a utility class, where you're not in control of the parent class and it may change scope later (inheriting java.util.Vector is a very, very bad idea since sun may later declare methods depricated). It's always easier to replace changing a class you just use - than one you inherit from.
  • Don't use inheritance from a class, which is written very specificly to a narrow problem - because that will mait it more difficult to inherit from another class later. Client classes that use the problem domain class may be written in a way that assumes the problem domain class is a subclass of the utility class. If the implementation of the problem domain changes in a way that results in its having a different superclass, those client classes that rely on its having its original superclass will break. An even more serious problem is that client classes can call the public methods of the utility superclass, which defeats its encapsulation.



Benefits:

  • Advantage of delegation is that it easy to compose behaviour at runtime.
  • Advantage of delegation is that modification of delegation class interface has little ripple effects on the interface of other classes.
  • Advantage of delegation is more general purpose than inheriatance and thus any extension to a class accomplished by inheritance may equally well be accomplished by delegation - but not necessarily the other way around.
  • Advantage of inheritance is transparent relationships which is clear already at compile time compile-time.

Disadvantage:

  • Primary disadvantage of delegation is that it is less structured than inheritance. Simply, relationships between classes built using delegation are less obvious than those built using inheritance.

 

When to use:

Strategies for improving the clarity of delegation based relationships:
  • Use in context with other well-known design and coding patterns. A person reading code that uses delegation will be more likely to understand the role that the objects play if the roles are part of a well know pattern or a pattern the recurs frequently in your program.
  • Use in context with consistent naming schemes to refer to objects in a particular role. For example if multiple classes delegate the creation of widget objects, the role of the delegatee object becomes more obvious if all of the classes that delegate that operation refer to delegatee objects through a variable called widgetFactory.
  • Use in context where purpose of a delegation is clarified by writing comments.
  • Note that it is possible and advantageous to use all three of these strategies at the same time.
  • By determining its superclass, a class' declaration determines the behavior that a class inherits from its superclass. Inheritance is not useful when the behavior that a class should build on is determined at run time.
1.2 Interface / class decoupling [Grand98]

Defines use of interfaces - the process of having a class accessing data and services provided by other classes independent of their implementation - by usage of interfaces. To avoid the coupling of classes because they share a uses/used-by relationship, make the usage indirect through an interface.

The Client class other class implementing the IndirectionIF interface. IndirectionIF interface provides the indirection that keeps the Client class independent of the class that is playing the Service role

 

Benefits:

  • Allows development at parallel levels, where top-level implementation does not necessarily depend on completed implementationat lower levels (behind the interface) until execution of code at runtime
  • Postphones actual implementation of lower-level functionality to later time. Not being forced to implement lower level functionality first may allow more planning - possibly resulting in better technical solutions.
  • Functionality behind the interface may be substituted at a later point in time. As long as the interface is not compromized, the other code running at other levels should remain unaffected.

 

Disadvantage:

  • Like any other indirection, the Class Decoupling pattern can make a program more difficult to understand.

 

When to use:

  • In practical life, interface is used in a situation where an instances of a class use other objects and all they require of those objects is implementation of certain methods. Then the class dependency may be limited to just this requirement by defining interface including exclusively those methods - and letting the class use that interface.
  • In large projects it can be beneficial to define programmable interfaces between various participants in projects to divide up areas of responsability (divide and conquer).
  • When actual implementation has not been completely decided on a project, hiding actual programming logic behind an interface may allow subsequent changes of low-level implementation without affecting higher prgramming layers.
1.3 Immutable [Grand98]

Defines a class that never changes after contruction - used in contexts with an object shared by multiple other objects and where state is fetched more often than changed. Immutable class pattern increases the robustness of objects that share references to the same object and reduces the overhead of concurrent access to an object. The Immutable Object pattern avoids need of synchronization between multiple executable threads when sharing an object.

Main advantage is simplicity. Once the object never changes, no code is necessary to manage changes. While an immutable object is static in nature - a read-only-object is a class, which presents a read-only interface - but actually can still change internal state (maybe acting on timeticks). So the two things are not the same. An immutable class is read-only - but not the other way around.

 

Benefits:

  • Since the state of immutable objects never changes, there is no need to write code to manage such changes. Also, there is no need to synchronize threads that access immutable objects. Amounts to simpler programming logic.
  • Your program uses instances of a class that is passive in nature. The instances do not ever need to change their own state. The instances of that class are used by multiple other objects. Amounts to simple exchange of information.
  • If access to a shared object's state information involves multiple threads and modification of its state information, then the threads that access the state information must be synchronized in order to ensure consistency.

 

Disadvantage:

  • Operations that would otherwise have changed the state of an object must create a new object. This is an overhead that mutable object do not incur.

 

When to use:

  • When having a situation where no method, other than a constructor, should modify the values of a class' instance variables.
  • Or when any method that computes new state information must store that information in a new instance of the same class, rather than modifying the existing object's state.
1.4 Marker / Semantic Interface [Grand98]

Defines procedure of hiding implementation of a class - by using interfaces that declare no methods or variables to indicate semantic attributes of a class. Mostly used for utility classes, which must determine something about objects without assuming they are an instance of any particular class.

Main advantage is providing interface while hiding implementation. Interface makes it possible to determine how use an object without relying on the object being an instance of any particular class.

 

Applicability:

1.5 Proxy [originally GOF pattern]

Defines central object acting as a surrogate for another object, by receiving all method calls for that other object and subsequently delegating them to the other object. But in fact this is a pattern used in various forms, in many other patterns. Proxy objects are declared as classes in a way that minimizes client object's awareness that they are actually dealing with a proxy.

Put to use in a number of contexts:

  • Used in "The Asynchronous Invocation pattern", the proxy pattern is used in asynchronous communication to provide quick return from a method, which in fact takes a long time to complete.
  • Used in "The Remote Proxy pattern", the proxy pattern us used to give illusion that an object on another physical machine appears to exists on the local machine.
  • Used in "The Access Proxy pattern" or "DAO pattern", the proxy pattern controls access to a service or data, possibly improving performance by caching identical, frequent requests.
  • Used in "The Virtual Proxy pattern", the proxy pattern gives the illusion that an object exists before it actually does.

Benefits:

  • Provides a stable interface for a service, which might not in real life be contiously able to offer the services at all times or places (fx. the actual service may actually only be available within a specified opening hours).
  • Hiding implementation and complexity of the real service provider behind the proxy.
  • Access to a service prover should be controlled without adding complexity or putting restriction on implementation by coupling the service to the access control policy.
  • Transparent access to the service provider will reduce complexity on the client side.

When to use:

  • Applicable in numerous SOA contexts.
  • Used where there is a need for an object to receive method calls on behalf of another object. Client objects call the proxy object's method. The proxy object's methods do not directly provide the service that its clients expect. Instead, the proxy object's methods call the methods of the object that provides the actual service.
2. Creational Patterns
Creational: Related to the object instantiation process.
2.1 Object Pool [Grand98]

Defines a performance efficient way to re-use objects, which might be expensive to create - by recycling a limited number of used objects through a pool.

Benefits:

  • Simulate infinite system resources where the a process may require an unlimited number of objects - provided(!) only a finite number is used simultaneously
  • Overall reduced need for system ressources - from basic fact that objects are reused.
  • Scalable usage of system resources - since pool can take various size - and may be restrained by runtime limitations for the system.
  • Improved speed efficiency - since occasional time-consuming creation of objects can be avoided.
  • Less need for garbage handling since objects are not disposed of so frequently.

When to use:

  • used in a context where a process has a need for a large number of identical objects with changing contents. Could fx. be a database connection with need to map database objects - or a data compression program with need to maintain a finite windows into backlog data.
3. Partitioning Patterns
Partitioning: organizing complex actors and concepts into multiple classes
3.1 Layered Initialization [Grand98]

Defines encapsulation of common and specialized logic in separate objects. Addresses problem where implementation requires common logic to be used when deciding, which specialized subclass to create. In such a situation, it is not always optimal to just define a class that encapsulates common logic and then define subclasses that contain the different forms of specialized logic - because that does not allow the common logic to be used at implementation level to actually pick out the most appropriate specialized logic.

Benefits:

  • Processing of complex data is done by a specialized class Transparent encapsulation of the logic to choose a specialized class to process complex data.
  • To maintain low coupling, only one of the objects that participate in the Layered Initialization pattern should be visible to the object that provides the complex data.
  • Encapsulating the logic deciding which class to instantiate into a separate class reduces the effort required to maintain the other classes. If a database migrates to a different type of engine or a new class becomes available that provides better access to it (JDBC driver), then the corresponding change in the program is limited to the class that decides what class to instantiate (which JDBC driver to use).

When to use:

  • use in situations where preprocessing must be done on selection data before deciding which specialized class to instantiate.
3.2 Filter [BMRSS96]

Defines how to obtain complex transformations of data by combining simple filters with compatible interfaces. By using output from one filter as input to another filter, the filters can work together to create different computations and transformations on streams of data. Each filter may concentrate on simple and understandable computations. Working together, the filters can provide transformations on much higher level of complexity. Major force with this patteren is substitutable and transparent transformation of date, which is usefull during data analysis.

Source Filter... Sink filter... The differeence is who take initiative to move the data. Whether pushing or pulling data through the filter. Similar to the difference between polling and interrupts (polling when you ask for data... interrupt when waiting to receive data):

  • Source Filter... data flows as a result of a data sink object calling a method in a data source object (data are actively moved)
  • Sink filter... data flows when a data source object passes data to a method of a data sink object (data are received).
In a situation with multiple filters, they should implement a a common superclass for all classes so an instance of one can use an instance of another without have to care which class the object instantiates. Diagram for the version of Filter, where data sink objects get data by calling methods in data sources

Diagram for the version of Filter where data source objects pass data to methods of data sink objects:

Benefits:

  • offers abstraction,where an overall complex operation (permutation) is broken down into simple operations
  • reusability of the filters, when applied in changed order in another context - or atleast reducing the scope of changes when it is possible to restrain modifications to individual filters
  • speed effiency, where each filter may be implemented in a in the most efficient way. Fx. the permutation in DES encryption is done much more efficient as a hash-table lookup at first - than being done as a difficult understandable side-activity during the general encryption process.
  • filters are runtime interchangeable - since they does not maintain internal state (and perhaps even may implement the same interface).

When to use:

  • where data is to undergo several transformations in changing order. After completing each transformation, the process is left stateless. Only the order of transformation may change. Fx. different layers of encryption.
4. Structural Patterns
Structural: organizing object to work with each other.
4.1 Dynamic Linkage (aka Dynamic class-loading) [Grand98]

Defines procedure of arbitrary loading and usage of classes at runtime - with only requirement for the classes, that they implement an interface, known at compile-time. Dynamic linkage simply reduces compile-time restrictions on behalf of more run-time freedom regarding class implementation. The procedure allows change in implementation of classes at run-time, without neither re-compiling nor stopping the program.

Benefits:

  • to postphone design decision as far as possible - possibly to the runtime level
  • at runtime, the process will be able to load and use arbitrary classes that it has no prior knowledge of - classes which may be implemented at a point in time after the final completed implementation of the main process.

When to use:

  • When design decisions are either not firm - or may require flexibility to undergo changes - at runtime level
  • as presented, it is required that the environment knows about the AbstractLoadableClass class and that the loaded class knows about the EnvorinmentInterface. In cases which requires less structure, other mechanisms for interoperation are possible - such as fx. reflection in Java, where one object may ask another about which methods it implements - and consequely call a method it had not prior interface information about.
4.2 Virtual Proxy [Larman98]

Defines procedure of using objects before they're actually created. This is possibly by using a proxy interface, behind which the objects are created at a later date. Reasons for doing that may be that objects are expensive to create and later there is a possibility that the objects will not required anyway... In such a scenario, it may be beneficial to postpone instantiation until the existance of objet is absolutely required. It goes without saying, that the proxy needs to implement the same interface as the object and the proxy need to be able to evaluate, when creation of the object is required.

Benefits:

  • avoiding time overhead while constructing classes (and perhaps populating with all required data). In the end it might turn out to be completely unnecessary to instantiate it at all.
  • delaying instantiation of classes until actual use will allow a more continous workload distribution - avoiding processing bottlenecks in multithreaded systems, both in regard to the instantiation process and probably also the subsequent ressource deallocation (garbage handling).
  • clients (classes) using the proxy need not be aware if the service class is loaded or not, an instance of it exists or that the class even exists

When to use:

  • where there is a significant cost overhead while creating classes - or it is impractical for arbitrary reasons
  • where the functionality of the service is not necessarily required right after instantiation - but may actually be used a little later on. Fx. even though it is usual to declare variables in a common block in a program flow, it does not necessarily mean they are all immdiately used - some of them may actually turn out never to be used at all.
4.3 Cache Management [Grand98]

Defines strategy of keeping a local copy of objects fetched from outside of a program, such as a remote server or database. Reason for doing that is normally performance improvements in order to save the relatively high expense of fetching such objects. "Cache management" defines the strategy of managing the cache. Fx. how many objects to keep in cache, which to keep, how long to keep them, etc. In data compression, managing the cache of historical data decides not only speed - but also prediction probablity for processing new data - and thus influences the overall compression ratio.

Link: http://www.developer.com/java/other/article.php/630481

Benefits:

  • performance - retrieving objects from external sources can take thousands or even millions of times longer that accessing an object that is already cached in internal memory

Drawbacks:

  • determining the size of a cache is usually a runtime issue and can be a very challenging (let's be honest... difficult) task. It depends on the amount of available system ressources as well as the user pattern. This runtime paramenter may require runtime interaction. But however difficult to estimate, it is essential to the performance of the system.
  • an appropriate scheeme is required for updating and managing cache. Really there are two issues: which items to keep and which to dispose. Assuming newest items are always kept, the challenge is usually selecting which object to discard. This vital process directly affects the cache hit rate. If the discarded object is always the next one requested then the hit rate will be 0%. But if the object discarded will not be requested before any of the other object in the cache, then discarding will have least negative impact on hit rate. Making a good choice of which object to discard requires a scheme for forecasting future object requests... This is a runtime issue, which can be very difficult to deal with a compile time.
  • read consistency is required - meaning that the cache always must reflect updates to information in the original object source. If the objects being cached are database entries, then the data in the object source can change while the data in the cache will no longer be current. To achieve absolute read consistency while caching objects in a cache, the object source must notify the program of updates to cached objects. This may be accomplished using the Publish-Subscribe pattern.
  • write consistency is required - meaning that updates to the the original object source always reflects updates to the cache.
  • get the design right (!) to incorporate use of cache - fx. when encapsulating a loop construction within another loop - and letting the inner loop process a sequence of objects, beginning with those which just tipped out of the cache - might not make the best use of the cache (assuming the sequence of objects exceeds cache size).

When to use:

  • cache works best in an environment with plenty reads and few modifications - otherwise cache management processes will be fulltime occupied by synchronizing cache objects with the actual data they represent.
  • cache works specificly well if a limited number of objects are used frequently - otherwise managing a cache introduced an unnecessary work overhead instead.
  • the size of the cache should correspond to easy accessible system resources. If storing cache entries in a not-so-easy-accessible way, retrieving cache objects will end up taking the same time as retrieving the original items
  • systems often needs to be designed with the cache system in mind !!! Using the cache right is essential to performance. Fx. when encapsulating one loop operation within another, it is important to access elements close to each other at all times - in order to be sure to operate on elements in the cache. Fx. failing to get loop constructions right will defeat the whole purpose of using a cache.
  • secondary cache is a concept introduced where it is realized that is not possible to achieve a high cache hit rate with available memory by fetching objects from the original data source. Then it may be considered to use a secondary cache - which is typically a disk file, used as a cache. The secondary cache may takes longer to access than the primary cache that is in memory. But as long as it takes sufficiently less time to fetch objects out of a local disk file than fetching them from the original object source, it can be advantageous to use this secondary cache.
  • 5. Behavioral Patterns
    Behavioral: the process of organizing, managing and combining responsabilities between objects.
    5.1 Little Language [Grand98]

    Defines an approach where you define a meta language to express solutions to problems using the same common break-down solution elements. Goes without saying, this only works in a scenario where solutions to problems are confined to various combinations of a small number of elements. Then a little language may be defined to express the solutions. This pattern is basicly a modification of the Interpreter pattern [GoF95] and a note by Jon Bentley[Bentley] about little languages.

    With a language follows definition of syntax and semantics. Syntax defines the "grammer" fx. what words and symbols make up the language and how they may be combined. Semantics defines the meaning of the words, symbols and their combinations that make up the language.

    Explanations of the class roles in the Little Language pattern:
    • Client is an instance of a class running a little language program, feeding it whatever data it needs and using the results that the program produces. It creates an instance of the Parser class to parse programs that it supplies through InputStream objects. The Parser object's parse method returns an instance of the AbstractNonterminal class to the Client object. That object is the root of a parse tree. The Client object calls the AbstractNonterminal object's execute method to run the program.
    • Lexical Analyzer is used when a Parser object's parse method is called. It creates a LexicalAnalyzer object to read characters from the same InputStream object that was passed to it. The LexicalAnalyzer object reads characters from the InputStream object, recognizes terminal tokens it finds using the lexical rules and return those tokens to the Parser class when it calls the LexicalAnalyzer object's nextToken method. The nextToken method returns the next terminal token that it finds in the input.
    • Parser gets its parse method called from the client in order to parse input from InputStream objects by matching the tokens in the input against the productions of the grammar. The parse method builds a parse tree as it matches the productions and returns a reference to the parse tree's root to the Client object.
    • AbstractNonterminal is the abstract superclass of all of the classes whose instances can be parse tree nodes. A Client object calls its abstract execute method to execute the program.
    • ConcreteNonterminal1, ConcreteNonterminal2 are used a parse tree nodes.
    • TerminalToken defines no variables or methods. Its subclasses correspond to the terminal tokens that the LexicalAnalyzer class recognizes.
    • InputStream is used to read a stream of characters from the data source.

    Benefits:

    • reduces the requirement for different implementations while introducing an abstraction level to deal with multiple problems, which are fundamentally just different combinations of a limited number of elements or operations
    • parser for little languages is usually small and simple enough for it to be understood in its entirety. Spreading the parsing logic over multiple classes makes understanding much more difficult.

    When to use:

    • when dealing with multiple problems, which are fundamentally just different combinations of a limited number of elements or operations
    5.2 Snapshot [Grand98]

    Defines generalization of the Memento Pattern [GoF95]. Defines procedure to take a "snapshot" of an objects state.

    Snapshot pattern using memento objects:

    Explanations of the class roles in the variation of the Snapshot pattern that uses Memento objects:
    • Originator is a class whose instance's state information is to be saved and restored. When its createMemento method is called, it creates a Memento object that contains a copy of the Originator object's state information. Later, you can restore the Originator object's state by p assing a Memento object to its setMemento method.
    • Memento is a private static class of the Originator class that implements the MementoIF interface. Its purpose is to encapsulate snapshots of an Originator object's state. Because it is a private member of the Originator class, only the Originator class is able to access it. Other classes must access instances of the Memento class either as instances of Object or through the MementoIF interface.
    • MementoInterface is the interface used by classes other than the Originator class to access Memento objects. Interfaces in this role may declare no methods. If they do declare any methods, the methods should not allow the encapsulated state to be changed. That ensures the consistency and integrity of the state information.
    • Caretaker maintains a collection of Memento objects. After a Memento object is created, it is usually added to a Caretaker object's collection. When an undo operation is to be performed, a Caretaker object typically collaborates with another object to select a Memento object. After the Memento object is selected, it is typically the Caretaker object that calls the Originator object's setMemento method to restore its state.

    Snapshot pattern serialization:

    Explanations of the class roles in the variation of the Snapshot pattern that uses serialization:
    • Target is the passive receiver of objects containing the state to preserve. In any case, either the ObjectOutputStream object or ObjectInputStream object do all of the work. ObjectOutputStream object converts the state of instances of classes in this role to a byte stream. ObjectInputStream object restores the state of instances of classes in this role from a byte stream.
    • ObjectOutputStream is usually the destination data stream, which accepts serializable objects (in J2EE it would be java.io.ObjectOutputStream). It discovers and accesses a Target object's state information and writes it to byte stream with additional information that allows an ObjectInputSteam object to restore the state information.
    • OutputStream An object in this role is an instance of a subclass of the standard Java class java.io.OutputStream. If the state information needs to be saved indefinitely, then the OutputStream object may be a FileOutputStream. If the state information needs to be saved no longer than the duration of a program run, then the OutputStream object may be a ByteArrayOutputStream.
    • ObjectInputStream is the source data output stream, which delivers deserialized objects (in J2EE usually it would be java.io.ObjectInputStream or a subclass of it). Instances of these classes read serialized state information from a byte stream and restore it. If you do not override the default behavior, an ObjectInputStream object puts the original Target object's state information in a new instance of the Target object's class. Using techniques described under the "Implementation" heading, you can arrange for ObjectInputStream objects to restore the saved state to an existing instance of the Target class.

    Benefits:

    • Snapshot pattern reduces complexity of saving and restoring an object's state - seperating the issue from its class

    Drawbacks:

    • not very suitable for undoing a fine grained sequence of commands. Making many snapshots of an object can consume a prohibitive amount of storage. Capturing the changes to an object's state (the Command pattern) may be more efficient

    When to use:

    • where it is necessary to make a snapshot of an objects state (by pattern definition)
    5.3 Null object [Woolf97]

    Defines usage of a simple object (the Null Object) used to initialize variables not used. Replaces commenly known practice of initilizing variables using "null" (nil) pointer to indicate the absence of an object to delegate an operation to. Using null (nil) to indicate the absence of such an object requires a test for null (nil) before each call to the other object's methods. Instead of using null (nil), the Null Object pattern uses a reference to an object that doesn't do anything (empty methods).

    Benefits:

    • simpler code may result from remvoing need to check if objects exists before being put into use. Calling the empty methods on the Null Object will just result in no operations - just as a check for null would have caused the object to be skipped.

    Drawbacks:

    • it is usually more performance efficient to do a single check for null and skipping further work on an object than calling numerous empty methods - which may subsequently call a cascade of other empty methods in a possible pyramid-like pattern. Even the simplest operations can create performance overhead when performed enough times.
    • the pattern may increases the number of classes in the program, where there is not already a class or interface in the AbsractOperation role. Thus the Null Object pattern may introduce more complexity through the introduction of additional classes than it removes by the simplification of code.

    When to use:

    • where requirements to the sourcecode dictates no checks for existance of objects (more or less by pattern definition)
    6. Concurrency Patterns
    Concurrency: coordinating concurrent operations with regard to shared ressources and the sequence of operations.
    6.1 Single Threaded Execution [Grand98]

    Defines procedure of scheduling single-threaded execution of operations, which are not suitable for concurrent execution because of shared ressources or data.

    This pattern is not necessarily an excuse for bad design, since at the most basic level in a program, there will often be operations, which are not threadsafe. Fx. ensuring file pointer is not moved by others during disk I/O, writing uncut messages to the console, etc. The whole idea of good concurrency design is to make these occasions as few and isolated as possible. A lot of these non-threadsafe operations are dictated by the operating system (OS) and maybe not possible to correct. Fx. in some OS' the number of file pointers has been limited and using one for each execution thread has simply not been realistic. In that case, good design is about dealing effectively with these restraints - not just passing on problems at another level.

    The class in the diagram has two kinds of methods:

    • unguarded methods: safeOp1, safeOp2, which can be safely called concurrently by different threads
    • synchronized methods: unsafeOp1, unsafeOp2, which cannot be safely called concurrently by different threads
    When different threads call the guarded methods of a Resource object at the same time, only one tread at a time is allowed to execute the method. The rest of the threads wait for that thread to finish

    Benefits:

    • ensures proper execution of non-threadsafe operations - which could be a poor excuse for bad design or simply the consequences of circumstances, which are not possible to be changed.

    When to use:

    • when there are non-threadsafe operations, which can not be executed otherwise. Going to the lowest level of operations, there will quite usually be operations (fx. disk I/O), which will require protection by semafores or other means (fx. 'synchronized' in J2EE) to ensure singlethreaded execution.
    6.2 Guarded Suspension [Lea97]

    Defines the suspended execution of a method call until a precondition is satisfied.

    Waiting for non-threadsafe ressourcess is unavoidable where multiple threads request simultaneous access. Here access is restrained to only one thread - which must be allowed to execute and leave the guarded state in order for another to get in and do its task.

    Benefits:

    • using java.lang.Object.wait() and java.lang.Object.notify() it is possible to efficiently restrain access to non-threadsafe operations without the need of complex semafore semantics.

    When to use:

    • (as the definition states) where execution of a method must be restrained from execution until a precondition is satisfied.
    6.3 Balking [Lea97]

    Defines procedure of returning from a method call without doing anything if the object is in an invalid state.

    This procedure should be considered as just one out of several (others) alternatives to handle commen error situations:

    • as in design pattern "Guarded Suspension" delaying completion of the call until the object is back in a valid state
    • throwing exception to be handled by the caller
    • return an error code (assuming the caller is ready to check for one)

    Benefits:

    • safe handling of calls to objects, which are in an inappropriate to execute a method call

    When to use:

    • when an object is in an invalid state, where method call can not return
    • and it is inapppropriate to postphone execution until state becomes valid
    • and suspending execution can be done safely, where the object is in an invalid state
    6.4 Schedular [Lea97]

    Defines manager for the execution order of threads (the schedular), which executes specified threads at an appropriate time and in a defined context.

    Schedular pattern suggests definition of a scheduling policy without restricting itself by any implementation limitations. Scheduling is fx. relevant when managing non threadsafe code, where execution order may be significant.

    Explanations of the class roles in the Scheduler pattern:
    • SchedulerRequest must implement the interface in the ScheduleOrdering role. SchedulerRequest objects encapsulate a request for a SchedularProcessor object to do something.
    • SchedulerProcessor must perform computation described by a SchedulerRequest object. May face multiple SchedulerRequest objects to process, but can only process each one sequentially after each other. A SchedulerProcessor object delegates to a Scheduler object the responsibility of scheduling ScheduleRequest object for execution in sequential order.
    • Scheduler manages SchedularRequest objects for processing by a SchedulerProcessor object. To decrease restrints and increase reusability, a Scheduler class have no knowledge of the Request class that it schedules. Instead, it accesses SchedulerRequest objects through the ScheduleOrdering interface that they implement.
    • ScheduleOrdering is an interface implemented by SchedulerRequest objects. Interfaces in this role serve the purposes: 1) By referring to a ScheduleOrdering interface, Processor classes avoid a dependency on a SchedulerRequest class. 2) By calling methods defined by the ScheduleOrdering interface, SchedulerProcessor classes are able to delegate the decision of which SchedulerRequest object to schedule for processing next, which increases the reusability of SchedulerProcessor classes. The above class diagram indicates one such method named scheduleBefore.

    Benefits:

    • provides explicitly control over thread execution
    • scheduling policy is encapsulated in its own class and is possibly reusable - atleast on the abstraction level

    Drawbacks:

    • since Scheduler pattern allows increased flexibility beyond just serializing execution of threads one after the other in a random sequence (as by the synchronized method), it may cause significant processing overhead relative to applying simple synchronization.

    When to use:

    • when it is required to execute threads in a specific order, at specific time or in a specific context (assuming dependencies on conditions). Schedular is a concept incorporated into some application servers (fx. BEA weblogic) where execution of processes may be configured to occur under a defined set of circumstances (fx. point in time).
    6.5 Read/write Lock [Lea97]

    Defines a context with allowed concurrent read access to an object but required exclusive access for write operations

    Explanations of the class roles in the Scheduler pattern:
    • Data has methods to get/set its instance information. Any number of threads are allowed to concurrently get a Data object's instance information. No two threads are allowed to simultaneously set its instance information. Its set operations must occur seperately under conditions 1) set done one at a time, 2)no concurrent get operations being executed. Data objects must coordinate their set and get operations so that they obey those restrictions.
    • ReadWriteLock offers methods to synchronize safe access to data while honoring the two conditions above. Before the get method in Data retrieves anything, it calls the associated ReadWriteLock object's readLock method, which issues a read lock to the current thread. During the time the thread possesses a read lock, the get method can be sure that it is safe for it to get data from the object. Since while there are any outstanding read locks, the ReadWriteLock object will not issue any write locks. In case of outstanding write locks (when the ReadWriteLock object's readLock method is called), it does not return until all the outstanding write locks have been relinquished by calls to the ReadWriteLock object's done method. Otherwise, calls to the ReadWriteLock object's readLock method return immediately.

    Benefits:

    • transparently coordinates concurrent calls to a Data object's get and set methods so that calls to the object's set methods do not interfere with each other or calls to the object's get methods
    • in a context with substantially more calls to get than set - the Read/Write Lock pattern may offer superiour performance compared to the approach where just using single-thread-execution (synchronizing) for all get/set calls to the Data object - since the Read/Write Lock pattern actually allows concurrent calls to (frequently occurring) get.

    Drawback:

    • in a context with substantially less calls to get than set - the Read/Write Lock pattern may offer inferiour performance compared to the approach where just using single-thread-execution (synchronizing) for all get/set calls to the Data object - since the time overhead used to coordinate calls to get/set will not be justified by a sufficient number of concurrent calls to get.

    When to use:

    • in situations, which require control of read/write access to a data object - and where there may be a significant number of attempts to retrieve data compared to the number of times data is stored
    6.6 Producer-Consumer [-]

    Defines procedure for asynchronous production and consumption of information or objects

    Explanations of the class roles in the Producer-Consumer Pattern
    • Producer supply objects that are used by Consumer objects. Instances of Producer classes produce objects asynchronously of the threads that consume them. That means that sometimes a Producer object will produce an object when all of the Consumer objects are busy processing other Consumer objects. Rather than wait for a Consumer object to become available, instances of Producer classes put the objects that they produce in a queue and then continue with whatever they do.
    • Queue act as a buffer for objects produced by instances of Producer classes. Instances of Producer classes place the objects that they produce in an instance of a Queue class. The objects remain there until a Consumer object pulls them out of the Queue object.
    • Consumer use the objects produced by Producer objects. They get the objects that they use from a Queue object. If the Queue object is empty, a Producer object that wants to get an object from it must wait until a consumer object puts an object in the Queue object.

    Benefits:

    • reduced dependencies as objects are produced or received asynchronously of their use or consumption

    When to use:

    • when decoupling of sending/receiving data is required
    6.6 Two Phase Termination [Grand98]

    Defines procedure for orderly shutdown of a thread or process through the setting of a latch. The thread or process checks the value of the latch at strategic points in its execution.

    Explanations of the class roles in the Scheduler pattern:
    • Client is the central class initiating the concurrent threads or processes and also subsequently initiating a coordinated shutdown of them all
    • Thread1, Thread1, .. Thread(N) are the threads, which are required to call the isShutdownRequested method in the Terminator class at strategic points in their execution - where they will be informed if they're requested to shut down
    • Terminator is used by the main process (Client) to initiate a shutdown (by calling doShutDown()). Each of the process' threads will call the isShutdownRequested method at strategic points in their execution. If it returns true, the thread cleans up and shuts down. When all the application threads have died, the process exits.

    Benefits:

    • should provide better conditions for a thread or process to clean up after itself after forcibly being shut down

    Drawbacks:

    • Two Phase Termination pattern can delay the termination of a process or thread for an unpredictable amount of time - assuming there is no timeout after which all threads or processes will be forced to shutdown. Delay is possible because nirmally: 1) it is not guaranteed that the threads in finite time will actually pick up a shutdown signal from the Teminator class' method isShutdownRequested() - and 2) having requested the shutdown request, they're not guaranteed to actually shutting down within a finite time period

    When to use:

    • where a controlled shutdown is required, which leaves data processed bt threads or processes in a consistant state

    J2EE Patterns

    There are a number of patterns used in developing J2EE applications. They may be found on some of the pages referred to in the links section below.

    Links

    1. Mark Grand's collection of design pattens which goes somewhat further then the GOF set of patterns. Some of the patterns are variants of GOF - some are much older - but in general Mark Grand put up a good piece of work trying to categorize an additional set of patterns.
    2. Leo Crawford's SCEA ressource collection which has been source for inspiration during my SCEA studies
    3. Web Cornucopia is a very extensive source of design patterns and general information on component design. It Covers Creational, Structural and Behavioural Patterns.
    4. Design Patterns In Java A very nice presentation. It has the bullets for all the design patterns plus UML diagrams for all the patterns. This is a big plus for this resource. You do need some fundamental understanding of UML before you can use this ppt. A very helpful resource.
    5. University of Maryland course notes
    6. The Design Patterns Java Companion
    7. The serverside.com has a good collection of the design pattern usage in J2EE.
    8. Sun Microsystem released the first public release of 15 patterns used in J2EE.
    9. Ian's Patterns mock test