A Software Engineering Lecture by Steven Choy
Lecture Overview:
In this lecture, you will learn the following commonly-used software design patterns.
Factory Method: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. [
GoF, p107]
Abstract Factory: Provide an interface for creating families of related or dependent objects without specifying their concrete classes. [
GoF, p87]
Adapter: Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. [
GoF, p139]
Bridge: Decouple an abstraction from its implementation so that the two can vary independently. [
GoF, p151]
Facade: Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. [
GoF, p185]
Strategy: "Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it." [Gamma, p315]
Reading: Chapter 8 of the textbook Object Design: Reusing Pattern Solutions
The Strategy Pattern
Business rules change from time to time. How do you handle and prepare your system for this variation?
Coping with Changes
- A mediocre programmer handles changes by:
- Plenty of switch cases
- A lot of if-then-else conditional statements
- Inherit from the base class and provide extra functionalities
- What a great mind does is to use:
Strategy Pattern
- What did GoF say?
- Define a family of algorithms, encapsulate each one, and make them interchangeable. The Strategy pattern lets the algorithm vary independently from clients that use it.
- When is it applicable?
- Allow your software to use different business requirements or algorithms depending on the context they occur
- Strategy pattern emphasizes two fundamental principles of OO design:
- Program to an interface, not implementation
- Encapsulate the concept that varies
- How does it work?
- Separate the selection of business rules/algorithms from the implementation of business rules/algorithms
- Allows for the selection to be made based upon context
- How does it work?
- Separate the selection of business rules/algorithms from the implementation of business rules/algorithms
- Allows for the selection to be made based upon context
Strategy Pattern Example
- Consider you're are the designer of an online shopping portal, any customers can query for shipment rate of its order via the system
- Currently, your company only liaise with one of the logistic companies for courier service and you encapsulate the calculation of shipment rate in RateQuoteClient object:
- One day, your boss asks you to change the system to support query of shipment rate from other logistic companies. How can you handle it?
- Think if you have to support another new logistic provider, what do you need to alter?
Bringing Strategy for Help
Code Sample...
|
abstract class RateQuoteStrategy {
public abstract double quote();
}
|
|
class UpsRateQuoteStrategy extends RateQuoteStrategy {
public double quote() {
System.out.println("[UpsRateQuoteStrategy] Quoting shipment rate...");
// Implementation omitted for demo purpose
// In real situation, implementation varies between different providers
return 100.0;
}
}
|
|
class DhlRateQuoteStrategy extends RateQuoteStrategy {
public double quote() {
System.out.println("[DhlRateQuoteStrategy] Quoting shipment rate...");
// Implementation omitted for demo purpose
// In real situation, implementation varies between different providers
return 99.5;
}
}
|
|
class FedexRateQuoteStrategy extends RateQuoteStrategy {
public double quote() {
System.out.println("[FedexRateQuoteStrategy] Quoting shipment rate...");
// Implementation omitted for demo purpose
// In real situation, implementation varies between different providers
return 110.0;
}
}
|
|
class RateQuoteContext {
private RateQuoteStrategy strategy = null;
public RateQuoteContext(String type) {
if (type.equals("dhl")) {
strategy = new DhlRateQuoteStrategy();
} else if (type.equals("ups")) {
strategy = new UpsRateQuoteStrategy();
} else {
strategy = new FedexRateQuoteStrategy();
}
}
public double quote() {
return strategy.quote();
}
}
|
|
public class RateQuoteClient {
public static void main(String[] args) {
RateQuoteContext context = new RateQuoteContext("dhl");
System.out.println(context.quote());
// Change to other strategy
context = new RateQuoteContext("ups");
System.out.println(context.quote());
// Change to another strategy
context = new RateQuoteContext("fedex");
System.out.println(context.quote());
}
}
|
The Facade pattern
Your system is growing with more subsystems. It's too complex for client to learn how to interact with the subsystems. You decide to simplify the access of the existing subsystems. How do you cope with it?
Facade Pattern
- What did GoF say?
- Provide a unified interface to a set of interfaces in a subsystem
- Facade defines a higher-level interface that makes the subsystem easier to use
- When is it applicable?
- You need to simplify the interface for accessing subsystems
- You need a central point to access the subsystems that aids:
- Centralize security management
- Provide common caching facility for the underlying system
- Centralize transaction control
- Etc...
- How does it work?
- The Facade presents a new interface for the client to access the existing system
The Adapter pattern
You bought a new framework to replace your home-grown one. But you find that some of class interfaces do not compatible with your current one. How do you cope with it?
Adapter Pattern
- What did GoF say?
- Convert the interface of a class into another interface that the clients expect
- When is it applicable?
- You want classes with incompatible interfaces to work together
- Also known as a wrapper
- How does it work?
- Adapter provides a wrapper with the desired interface
- Implementation Issues
- How much adaptation should be done?
- Simply just a method name conversion
- Incompatible method arguments
- Totally different operations
Adapter Pattern Example
- Consider an example of online movie portal for ticket purchase, our system delegates the handling of credit card payment to a payment provider via a set of APIs
- Now your manager decides to switch to another payment provider, which offers lower service charges
- The new provider offers the same features but with APIs of incompatible interfaces with your current system.
How do you adapt to the situation?
Payment Client:
|
public void pay(PaymentProvider pp) {
// Pre-processing operations
pp.submitPayment(cc);
// Post processing operations
}
|
Main Program:
|
public static void main(String[] args) {
PaymentClient client = new PaymentClient();
PaymentProvider pp = new ExistingProvider();
client.pay(pp);
}
|
PaymentProvider Interface:
|
interface PaymentProvider {
public void submitPayment(String cc);
}
|
Existing Payment Provider:
|
public class ExistingProvider implements PaymentProvider {
public void submitPayment(String cc) {
// Implementation
}
}
|
Code Snippet for the existing implementation
- The new payment provider offers a different interface for submitPayment()
- The new method call is sendPayment()
- We need to adapt the old interface to the new one
New Payment Provider:
|
public class NewPaymentProvider {
public void sendPayment(String cc) {
// Implementation
}
}
|
Payment Client:
|
PaymentProvider pp = new PaymentProviderAdapter(new NewPaymentProvider());
public void pay() {
// Pre-processing operations
pp.submitPayment(cc);
// Post processing operations
}
|
Main Program:
|
public static void main(String[] args) {
PaymentClient client = new PaymentClient();
client.pay(pp);
}
|
PaymentProvider Interface:
|
interface PaymentProvider {
public void submitPayment(String cc);
}
|
Adapter:
|
public class PaymentProviderAdapter implements PaymentProvider {
private NewPaymentProvider pp = new NewPaymentProvider();
public void submitPayment(String cc) {
// Implementation
pp.sendPayment(cc);
}
}
|
Adapter Comes To Rescue
Adapter Pattern Solution - Code Sample
- We introduce a PaymentProviderAdapter to translate submitPayment() to the new sendPayment() Adapter:
|
public class PaymentProviderAdapter implements PaymentProvider {
private NewPaymentProvider pp;
public PaymentProviderAdapter(p) {
this.pp = p;
}
public void submitPayment(String cc) {
// Implementation
pp.sendPayment(cc);
}
}
|
Factory Method Pattern
How do you defer instantiation of particular objects to derived classes?
Factory Method Pattern
- What did GoF say?
- Define an interface for creating an object, but let subclasses decide which class to instantiate
- Let a class defer instantiation to subclasses
- When is it applicable?
- You want to make object creation flexible (compared with using the "new" operator)
- Factory method allows a derived class to make the decision on how to do instantiation
- Normally used in defining a framework
- How does it work?
- Client does not use the new operator for direct object creation
- An abstract Creator defines abstract Factory Method for object creation
- A derived class makes decision on which class to instantiate and how to instantiate it
Factory Method Example #1
Code Sample: Abstract Factory
|
/**
* DAO Abstract Factory (Creator)
*/
abstract class DAOFactory {
// Factory method
public abstract CustomerDAO createCustomerDAO();
}
|
Code Sample: Concrete Factory
|
/**
* Concrete factory (Concrete Creator)
*/
class OracleDAOFactory extends DAOFactory {
public CustomerDAO createCustomerDAO() {
System.out.println("[OracleDAOFactory]
Creating Oracle Customer DAO...");
return new OracleCustomerDAO();
}
}
|
Code Sample: Product
|
/**
* Customer DAO interface (Product)
*/
interface CustomerDAO {
public void addCustomer(Customer customer);
public void removeCustomer(String customerId);
public void updateCustomer(Customer customer);
public String getOrder();
}
|
|
/**
* Oracle customer DAO (Concrete Product)
*/
class OracleCustomerDAO implements CustomerDAO {
public void addCustomer(Customer customer) {
System.out.println("[OracleCustomerDAO] Adding customer...");
// The actual implementation is omitted
}
public void removeCustomer(String customerId) {
System.out.println("[OracleCustomerDAO] Removing customer...");
// The actual implementation is omitted
}
public void updateCustomer(Customer customer) {
System.out.println("[OracleCustomerDAO] Updating customer...");
// The actual implementation is omitted
}
public String getOrder() {
System.out.println("[OracleCustomerDAO] Retrieving order...");
// The actual implementation is omitted
return "";
}
}
|
Factory Method Example #2
Factory Method Real-life Application
- Ample of examples for Factory Method Pattern in JDK
- Open the JavaDoc for JDK 1.4/5.0 and search for class name end with "Factory"
Abstract Factory Pattern
You may appreciate Factory Method on the beauty of object creation. But now you want more... Suppose your application need to further support multiple databases depending on client's environment.
How do you handle instantiation of families of objects?
Abstract Factory Pattern
- What did GoF say?
- Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
- One step further than Factory Method to create families of objects
- When is it applicable?
- Think about you have families of Data Access Objects such as OracleDAO, MySqlDAO and SybaseDAO
- But you don't want the client to directly specify the concrete DAO class
- How does it work?
- Client does not use the new operator for direct object creation
- Object creations are coordinated by a AbstractFactory
Abstract Factory Example #1
Abstract Factory Example #2
Bridge Pattern
Bridge Pattern (Overview)
- Intent: De-couple an abstraction from its implementation so that the two can vary independently.
- The Bridge pattern is one of the toughest patterns to understand because it is so powerful and applies to so many situations.
Bridge Pattern Sample
Design Pattern Summary
- Design for extensibility
- Support for new vendor
- Support for new implementation
- Support for new views
- Design for portability
- Support for other platforms
Summary: what you have learned?
- Abstract Factory
- Factory Method
- Adapter
- Strategy
- Bridge
- Facade
- Singleton (covered in previous lecture)
- Observer (covered in previous lecture)
More materials for learning software design patterns
Java Design Patterns Explained with Practical Examples
The following suggests some materials to aid teaching and learning of software design patterns. They are all articles from JavaWorld.com. Each article (around 1,0000 in length) focuses on one design pattern at a time and has example applications with explanation and discussions. They are suitable for students to play with during practical and tutorial sessions.
Thanks for Reading
If you would rather like to have this lecture note in printed format, please click the print action link in the top right corner.
If you find any problem in this lecture note, please feel free to tell Steven by steven@findaway.hk