Design Patterns
Software Design Patterns
A design pattern is a general reusable solution to a commonly occurring problem within software design. It is a description or template for how to solve a problem that can be used in many different situations.
-
Helps developers reuse successful designs by basing new designs on prior experience.
-
Improves understandability for developers familiar with the pattern.
-
Identifies participating classes and instances, their roles and collaborators, and the distribution of responsibilities. This helps ensure adherence to Design Principles.
Software Design Patterns
Creational
- Factory
- Builder
- Singleton
Structural
- Adapter
- Decorator
- Facade
Behavioral
- Command
- Observer
- Iterator
Creation of objects for you rather than instantiating objects directly
Composition of classes and objects to obtain new functionality
Communication and interaction between objects
Factory Pattern
Encapsulates object creation logic by delegating to implementations of a common Factory interface without specifying the exact class of object to create.
When to use?
- When deciding which object to create depends on state that is not the responsibility of the created class or the creating class.
- When the created object is not available at compile time.
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
There are lots of of different types of loggers and which one to use is decided at runtime.
So how does the application determine which logger to use? The separate logger classes shouldn't nor should the classes using the logger.
Here the factory maintains a separation of concerns by isolating the loggers and classes using the loggers from application state.
CreditCardAccountFactory accountFactory = new CreditCardAccountFactory();
foreach (Transaction transaction : customer.getTransactionHistory())
{
AccountType accountType = transaction.getAccountType();
CreditCardAccount account = accountFactory.AccountFactory(accountType);
totalRewards += account.calculateRewards();
}
- The types of card do not need to be known at compile time, they can be loaded in via plugins.
- Only accounts that are needed are created and can be cached or leverage object pools.
Factories are useful for creating curtains where systems need to be plug and play or details may not be known at compile time.
Real World Examples
the JDBC
The JDBC allows you to interact with various RDBMS without knowing about the specifics of each connector.
At runtime the GetConnection iterates though all of the driver loaded into the JVM (information not know at compile time) and returns one is able to create a connection using the provided URL.
Connection conn = DriverManager.getConnection(URL, USER, PASS);
Builder Pattern
Solves the "telescoping constructor" problem by encapsulating individual construction steps that can be invoked individually.
When to use?
- When constructors have too many parameters or multiple optional parameters.
- When constructors are doing real work.
- Object needs to be immutable after creation.
http
.authorizeRequests()
.antMatchers("/login.jsp").permitAll()
.anyRequest().hasRole("USER")
.and()
.exceptionHandling()
.accessDeniedPage("/login.jsp?authorization_error=true")
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.logoutUrl("/logout")
.logoutSuccessUrl("/login.jsp")
.and()
.formLogin()
.loginProcessingUrl("/login")
.failureUrl("/login.jsp?authentication_error=true")
.loginPage("/login.jsp");
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
public class Account
{
//Required
private final String accountNumber;
private final String type;
private final String bankDescription;
//Optional
private final String accountStatus;
private final String currencyCode;
private final String customerRole;
private final String currentBalance;
private final String availableBalance;
private final String accountUseType;
public Person(final String accountNumber, final String type,
final String bankDescription, final String accountStatus,
final String currencyCode, final String customerRole,
final String currentBalance, final String availableBalance,
final String accountUseType)
{
...
}
}
public class AccountBuilder
{
//Required
private final String accountNumber;
private final String type;
private final String bankDescription;
//Optional
private final String accountStatus;
...
public AccountBuilder(final String accountNumber, final String type,
final String bankDescription)
{
this.accountNumber = accountNumber;
...
}
public AccountBuilder accountStatus(String accountStatus)
{
this.accountStatus = accountStatus;
return this;
}
...
public createAccount()
{
return new Person(accountNumber, type, bankDescription,
accountStatus, currencyCode, customerRole, currentBalance,
availableBalance, accountUseType);
}
}
final Account account = new Account.AccountBuilder(
"1234123412341234", "Savings", "360")
//The rest are options but simply chain together
.accountStatus("Open")
.createAccount();
- Much cleaner initialization code.
- Fewer parameters
- No NULLs
- The Account object is immutable.
The results
Singletons
Restricts the number of instantiations of a class
to one single instance.
When to use?
- Almost Never!
- To store immutable readonly state.
When not to use?
- When you want to be able to unit test your code.
- When you can use DI or a Factory instead.
- When it results in your API being a liar.
What's wrong with Singletons?
You should be able to understand how to use an API without memorizing all of the source code.
testDebitAccount()
{
Account c = new Account("1234123412341234", "360", 5000);
c.debit(200);
assert(c.availableBalance == 4800);
}
In the background there is some stuff going on.
- There is an AccountTransactionProcessor.
- There is an AccountTransactionQueue.
But you can't know this from the API because it lied to you. The API clearly states that you can create an account and then debit it.
What's wrong with Singletons?
testDebitAccount()
{
AccountTransactionQueue.getInstance();
AccountTransactionProcessor.getInstance();
Account c = new Account("1234123412341234", "360", 5000);
c.debit(200);
assert(c.availableBalance == 4800);
}
testDebitAccount()
{
AccountTransactionQueue atQueue =
new AccountTransactionQueue();
AccountTransactionProcessor atProcessor =
new AccountTransactionProcessor(atQueue);
Account c = new Account("1234123412341234", "360", 5000);
atProcessor.debit(c, 200);
assert(c.availableBalance == 4800);
}
So when are Singletons ok?
Singletons are acceptable when.
- They do not effect the execution of your code.
- The singleton is immutable and all objects returned by the singleton are immutable.
A common example is loggers which have a global state but do not impact the execution of the application.
An example of this is environmental variables pulled at the application's launch.
Adapter Pattern
Adapter pattern works as a bridge between two incompatible interfaces. This type of design pattern comes under structural pattern as this pattern combines the capability of two independent interfaces.
Square peg in a round hole
class LegacySquarePeg {
private double width;
public SquarePeg(double w) { width = w; }
public double getWidth() { return width; }
public void setWidth(double w) { width = w; }
}
class RoundHole {
private int radius;
public RoundHole( int r ) { radius = r; }
public int getRadius() { return radius; }
}
class SquarePeg {
private LegacySquarePeg sp;
public SquarePegAdapter(double w) {
sp = new LegacySquarePeg(w);
}
public void makeFit(RoundHole rh) {
double amount = sp.getWidth() - rh.getRadius() * Math.sqrt(2);
if (amount > 0) {
sp.setWidth(sp.getWidth() - amount);
}
}
}
Decorator and Facade Pattern
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality
Decorator Pattern
Facade Pattern
Involves a single class which provides simplified
methods required by client and delegates calls to
methods of existing system classes.
Command Pattern
Uses a command object to represent the information needed to call a method at a later time. Command information includes the method's name, object that owns the method, and values for the method's parameters.
When to use?
- When a sequence of various method calls needs to occur at a later time
- When a collection of different method calls needs to be handled by a shared invoker
- When a history of actions is needed
Observer Pattern
Iterator Pattern
Design Patterns
By Scott Franks
Design Patterns
- 717