Hönnunarmunstur eru aðferðir við að leysa algeng vandamál. En notkun þeirra felst í að takast á við verkefni sem þarf að leysa. Fyrst koma verkefnin og svo finnum við hvaða munstur hentar miðað við þær forsendur sem við höfum. Oft þegar verið er að vinna að lausnum þá er alls ekki hægt að gera allan hugbúnaðinn í einu, heldur þarf að "hakka" suma hluti meðan unnið er í öðru. Til þess að lenda ekki í technical dept þarf að vera með stöðugt refactoring.
Í þessum fyrirlestri skoðum við vandamál og rifjum upp grunnmunstrin (base patterns) sem við kynntumst í L05 Design Patterns. Þá skoðum við hvernig við leysum tengingu við póstþjón.
2. Agenda
Base Patterns
– Gateway, Mapper, Layerd Supertype, Separated
Interface, Registry, Value Object, Plugin, Service
Stub, Record Set
From Problem to Patterns
– Using design patterns
3. Using Design Patterns
Normally we don’t start with patterns
– We start with problems to solve
– From Problem to Pattern
Must have clear objectives for the design
– The patterns will come as they are needed
Establish Design Principles
– This applies to your application
Remember the separation of concern
4. From Problem to Pattern
How do I reuse common functionality of my
objects?
– Layer Supertype
5. Layer Supertype
A type that acts as the supertype
for all types in its layer
Super class that contains common functionality
in a layer
How it works
– Use this pattern when you
have common features
from all objects in a layer
6. Layer Supertype
When to use it
– When you have common features from all objects in a
layer
Example
– Domain objects can
have a common
superclass for
ID handling
class DomainObject...
private Long ID;
public Long getID()
{
return ID;
}
public void setID(Long ID)
{
this.ID = ID;
}
public DomainObject(Long ID)
{
this.ID = ID;
}
7. From Problem to Pattern
How do I access an external service without
becoming too dependant on it?
– Gateway
8. Gateway
An object that encapsulates access to an external
system or resource
Wrap external APIs into an interface
– API is usually for accessing some external resource
• Examples: JDBC, JDom, financial software
9. Gateway
How It Works
– Create a simple API and use it access the external
API through a Gateway
– All access is easily defined
– Change in the resource does not require changes in
the client software
– Gateways should be simple – complex logic should
not be in the clients of the Gateway
– Gateways can be generated
10. Gateway
When to Use It
– Gateway is useful when accessing external service
– Can be applied with Service Stub (504)
– Clear benefit is that is makes it easy to swap out one
kind of resource for another
11. From Problem to Pattern
How do I avoid creating unwanted
dependencies?
–Separated Interface
12. Separated Interface
Defines an interface in a separate
package from its implementation
Decouples parts of a system
– Controls the dependencies between packages
– Implementation can easily be changed
How it works
– Interface and implementation is placed in separate
packages
– Client uses the interface
– Implementation can be determined at configuration
time
13. Separated Interface
Layered System
– Domain layer depends on Data Source layer
– Data Source layer cannot access Domain layer
Domain Layer
Data Source Layer
JDBC
Code
Concreate class
RowCallBackHandler
processRow(ResultSet rs)
Interface
RowCallBackHandler
processRow(ResultSet rs)
implements
Code reading SQL
Execution calls
Separated interface
14. Separated Interface
Instantiating the implementation
– User of the interface should not know the
implementation
– User of the interface creates the interface
• The Arrray.sort developer creates Comparable
• The Person developer implements Comparable
Solutions
– Use a Factory and Plugin method
– Use Dependency Injection
15. From Problem to Pattern
How do I test my client code using a service that
I don’t have access to?
– Service Stub
16. Service Stub
Removes dependence upon problematic
services during testing
Enterprise systems often need to access
external system
– Can be out of developers control
17. Service Stub
Service stub provides implementation for
development and testing purposes
– Runs locally and in-memory
– Implements the same interface of the gateway used
to access the real service
When to Use It
– Service stub is useful when dependence on a
particular service is hindering development or testing
– Called “Mock Object” in the extreme programming
world
18. From Problem to Pattern
How do I link to my implementation class using
configuration
– Plugin
19. Plugin
Links classes during configuration
rather than compilation
Use plugin to provide specific implantation
– Plugins implement specific interface use by the client
application code
– Decision at configuration time or run time
– Use factory to load in the plugin
– For example: on plugin for test, another for production
20. Plugin
A caller obtains a Plugin implementation of a
caller a plugin factory a plugin configuration
getPlugin
lookupPluginByType
new
a plugin
separated interface
When to Use It
– Use plugin when you have behavior that requires
different implementations based on runtime
environment
21. From Problem to Pattern
How can I keep common object available within
the application
– Registry
22. Registry
A well-known object that other objects can use
to find common objects and services
A registry is a global object
How It Works
– Object that can easily be accessed at any time
– Only one object available at any time
– Provides services or information
– Can have different scopes
– Usually not mutable data
– Example: System Settings, Loggers
23. Registry
In this implementation, only one instance running
public class Registry
{
private static Registry soleInstance = new Registry();
public static Registry getInstance()
{
return soleInstance;
When to Use It
– As a last resort
}
private Registry()
{
}
...
}
Registry registry = Registry.getInstance();
//registry = new Registry (); Does not work
24. From Problem to Pattern
I need to set up communication between two
independent objects
–Mapper
25. Mapper
An object that sets up communiction between
two independent objects
Create communication between two systems but
you still need to make them independent
26. Mapper
How it Works
– A Mapper is an insulating layer between subsystems
– It controls the details of communication between them
without either subsystem being aware of it
– Mappers are fairly easy as they are well-defined
– The tricky part is what system invokes them – third
party system or make the Mapper an Observer
When to Use it
– When you want to decouple different parts of a
system
27. From Problem to Pattern
I need to define a simple immutable object that
does not have any identity
–Value Object
28. Value Object
A small simple object, like money or date
range, whose equality isn’t based on identity
Small and easily created objects that hold and
represent some data
How it works
– Not based on identity
– Equality is based on comparing values of the object
– Can be immutable (example is the Date class)
When to use it
– When you’re basing equality on something other than
identify
29. Value Object
Examples
– Date, Money
class Money...
private long amount;
private Currency currency;
public Money(double amount, Currency currency)
{
this.currency = currency;
this.amount = Math.round(amount * centFactor());
}
...
31. Mail Service
We are building an web application
– One important service is sending messages in email
– We need to access the e-mail service
32. Mail Service
During development we cannot have all features
fully developed
– We program in steps
– Code, test, fix, code…
– This also applies over releases
– Maybe we have to use hacks in Version 1.0
Lifestyle: REFACTOR
33. Refactoring
Design, redesign, refactor
– Make the design as complete as possible
– But be prepared to change design as you code
– Unit tests become very important
Code Smell
– Think of your code as a baby:
“If it smells, change it!”
34. Refactoring
Refactoring is the process of improving design in
little steps at a time
– Minimizes risks – calculated
– Changes are controlled
– Code can improve
– Less likely to smell
35. The Danger
Technical Dept
“I’ll fix it later”
The four most dangerous and expensive words in
programming
36. Conway’s Second Law
Technical Dept
“There’s never enough time to do something right, but
there’s always enough time to do it over”
37. Mail Service
We are building an web application
– One important service is sending messages in email
– We need to access the e-mail service
38. Mail Service
We decide to use JavaMail API
– Problem is that this API is pretty low-level and
complicated
– Lots of “noise” – not good to have the domain
developers worry about that
What Design Pattern can we use here?
39. Mail Service Gateway
We build a simple gateway to handle mail
– Domain developers don’t worry about the service
– We can easily change to a different mail API
40. Gateway
An object that encapsulates access to an external
system or resource
Wrap external APIs into an interface
– API is usually for accessing some external resource
• Examples: JDBC, JDom, financial software
41. MailSender (1/2)
Class that sends the mail
– Method send that takes care of sending the mail
public class MailSender
{
public void send(String from, String to,
String subject, String body)
{
String smtpServer = "mail.ru.is";
try
{
43. MailSender
Name the problem with his class
public class MailSender {
public void send(String from, String to, String subject, String body) {
String smtpServer = "mail.ru.is";
try {
Properties props = System.getProperties();
props.put("mail.smtp.host", smtpServer);
Session session = Session.getDefaultInstance(props, null);
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.TO,InternetAddress.parse(to, false));
msg.setSubject(subject);
msg.setText(body);
msg.setSentDate(new Date());
Transport.send(msg);
} catch (Exception ex) { ex.printStackTrace(); }
}
}
44. MailSender
Problem
– Many parameters instead of an object
– Mail server is hard-coded
– Exception handling is bad
– Programming to implementation
public void send(String from, String to,
String subject, String body)
{
String smtpServer = ”smtp.ru.is";
try
{ ... }
catch (Exception ex)
{ ex.printStackTrace();}}}
45. MailService
Interface for the domain developers
– Program-to-interfaces Principle
public interface MailService
{
public void send(String from, String to,
String subject, String body);
}
– So let MailSender implement this interface
public class MailSender implements MailService
{
public void send(String from, String to,
String subject, String body)
{ ...
46. Testing
Testing MailService and MainSender is easy
public class TestMail
{
public static void main(String[] args)
{
MailService mail = new MailSender();
mail.send("andri@ru.is", // from
"andri@ru.is", // to
"Hallo", // subject
"So does this stuff work"); // body
}
}
48. Testing
What is the problem with clients like this?
public class TestMail
{
public static void main(String[] args)
{
MailService mail = new MailSender();
mail.send("andri@ru.is", // from
"andri@ru.is", // to
"Hallo", // subject
"So does this stuff work"); // body
}
}
49. Improvements
Problem
– MailSender implementation class is exposed to the
domain layer
Solution
– Use the Plugin Pattern
– Create a factory that will read a configuration file
and load the mail implementation class
– Client will use the MailService interface
50. Plugin
Links classes during configuration
rather than compilation
Use plugin to provide specific implantation
– Plugins implement specific interface use by the client
application code
– Decision at configuration time or run time
– Use factory to load in the plugin
• For example: one plugin for test, another for production
51. Factory with a Plugin
Create a MailFactory class
– Loads mail.properties file
– Creates the class specified in the properties file and
returns interface MailService
– Clients use MailService and are not exposed to
particular implementation
– It’s easy to change the properties file
52. Improvements
Problem
– Can we make the loading of properties and class more
generic?
– Other factories might need this functionality also
Solution:
– Create a Layer Supertype
– MailFactory extends Factory
53. Layer Supertype
A type that acts as the supertype
for all types in its layer
Super class that contains common functionality
in a layer
How it works
– Use this pattern when you
have common features
from all objects in a layer
54. Layer Supertype
A type that acts as the supertype
for all types in its layer
Super class that contains common functionality
in a layer
How it works
– Use this pattern when you
have common features
from all objects in a layer
56. Factory
Layer Supertype
Has two methods
– loadPropertie and loadClass
Exception handling
– Create a new exception class that we will use
– FactoyException
– Log the error
57. FactoryException
Extends Exception
– Checked exception
– Callers must catch this exception or explicitly throw it
public class FactoryException extends Exception
{
public FactoryException(String message)
{
super(message);
}
public FactoryException(String message, Throwable cause)
{
super(message, cause);
}
}
59. Testing Fails
Exception is thrown and message is logged
2.9.2007 16:49:34 is.ru.honn.rubook.factory.Factory loadClass
SEVERE: Factoy: Class 'is.ru.honn.rubook.mail.MailServiceStubx' not found.
60. Testing Fails
Problem
– MailService implementation classes have to
handle FactoryException or pass it on
– Do we want clients to worry about some factory?
Solution
– One solution is to catch FactoryException and
throw unchecked MailService exception
61. MailFactory
public class MailFactory extends Factory
{
public MailService getMailService()
{
MailService service;
try
{
service = (MailService)loadClass(
loadProperties("mail.properties").
getProperty("mail.service.class"));
}
catch(FactoryException fex)
{
throw new MailServiceException ("Unable to send e-mail", fex);
}
return service;
}
}
62. MailServiceException
Extends RuntimeException
– Unchecked exception
– Callers decide if they want to catch it
public class MailServiceException extends RuntimeException
{
public MailServiceException(String message)
{
super(message);
}
public MailServiceException(String message, Throwable cause)
{
super(message, cause);
}
}
63. Testing
Using the MailFactory class
– We can catch the MailServiceException or ignore it
– Notice we have not only abstracted the Mail API but
also the exception handling
public class TestMail
{
public static void main(String[] args)
{
MailFactory mf = new MailFactory();
MailService mail = mf.getMailService();
mail.send("andri@ru.is", "andri@ru.is", "Hello", "Hello");
}
}
64. Improvements
Problem
– Exception handling in our original MailSender is bad
Solution
– Use the MailServiceException
public void send(MailMessage message)
{
try { ... }
catch (Exception ex)
{
String msg = "Sending mail failed: " + ex.getMessage();
logger.severe(msg);
throw new MailServiceException(msg, ex);
}
SEVERE: Sending mail failed: Unknown SMTP host: mail.ru.is
65. Improvements
Problem
– What if we don’t have access to the SMTP server
at this time?
Solution
– Use a Service Stub
– Create the class MailServiceStub that will simply log out
the mail sent
– Could also write in file
66. Service Stub
Removes dependence upon problematic
services during testing
Enterprise systems often need to access
external system
– Can be out of developers control
67. MailServiceStub
public class MailServiceStub implements MailService
{
Logger logger = Logger.getLogger(LogInfo.LOG_NAME);
public void send(String from, String to,
String subject, String body)
{
logger.info("Sending mail from '" + from + "' to '" + to +
"' Subject: '" + subject);
}
}
2.9.2007 16:36:08 is.ru.honn.rubook.mail.MailServiceStub send
INFO: Sending mail from 'andri@ru.is' to 'andri@ru.is' Subject: 'Hello
mail.properties
mail.service.class=is.ru.honn.rubook.mail.MailServiceStub
68. Improvements
Problem
– What if we need to add new parameter?
public interface MailService {
public void send(String from, String to,
Solution
String subject, String body);
– Use an object to group parameters
– Easy to change without changing the interface
}
public interface MailService
{
public void send(MailMessage message);
}
69. MailMessage
Typical Data Transfer Object
public class MailMessage {
private String from;
private String to;
private String subject;
private String body;
public MailMessage(String from, String to,
String subject, String body)
{
this.from = from;
this.to = to;
this.subject = subject;
this.body = body;
}
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
...
70. Improvements
Problem
– The mail server in MailSender is still hardcoded
Solution
– Place in the configuration file
– Let the factory inject the name into the
Mail Service
public interface MailService
{
public void setMailServer(String mailServer);
public void send(MailMessage message);
}
72. New MailFactoy
getMailService injects the name into the service
public class MailFactory extends Factory
{
public MailService getMailService()
{
...
loadProperties("mail.properties");
service = (MailService)loadClass(getProperties().
getProperty("mail.service.class"));
service.setMailServer(getProperties().
getProperty("mail.server")); // injection
return service;
}
}
mail.service.class=is.ru.honn.rubook.mail.MailSender
mail.server=mail.ru.is
73. Improvements
Problem
– loadProperties loads the file each time used
Solution
– Load once then use
public class Factory
{
private Properties properties = new Properties();
protected Properties loadProperties(String filename) throws FactoryException
{ ...
return properties;
}
public Properties getProperties()
{
return properties;
} ...
74. Improvements
Problem
– All mail server implementations must store server name
and set function
– Common functionality in multiple classes
Solution
– Create a Layered Supertype
– Take care of the common functionality
– Make the send method abstract
75. AbstractMailService
Implements MailService
– Provides handling of the mail server property
public abstract class AbstractMailService implements MailService
{
protected String mailServer;
// this is used by the factory to inject
public void setMailServer(String mailServer)
{
this.mailServer = mailServer;
}
public String getMailServer()
{
return mailServer;
}
}
76. MailSender
Extends AbstractMailService
– Does not have to implement the MailServer interface
– Can use the getMailServer method
public class MailSender extends AbstractMailService
{
public void send(MailMessage message)
{
try
{
Properties props = System.getProperties();
props.put("mail.smtp.host", getMailServer());
...
77. Summary
Base Patterns
• Gateway, Mapper, Layerd Supertype, Separated Interface, Registry,
Value Object, Plugin, Service Stub, Record Set
We start with problems to solve
– Then we find the patterns to use
– Must have clear objectives for the design
Beware of code smell
Refactoring is the process of improving design in
little steps at a time
Example case study
– Mail service with a configurable factory