Download as pdf or txt
Download as pdf or txt
You are on page 1of 50

JAVA Development

Design Patterns
Design Patterns
Design Patterns

Design Pattern = a general reusable solution to a recurring design problem

Advantages:
● identify classes of similar problems where patterns can be applied
○ don’t reinvent the wheel every time
○ gives you a more robust/tested solution to your problem
● refer to solutions by name
○ easier to communicate to others
○ easier to identify when reading code
Design Anti-Patterns

Design Anti-Pattern: a general solution (to a class of problems) that should be


avoided in practice.

Advantages:
● identify that you should not apply a certain solution
○ don’t reinvent the wheel badly every time
Categories

Divided in 3 categories:

- Creational
- how objects are created (more control, adapt it to specific needs..)
- patterns: Singleton, Factory, Abstract Factory, Builder, Object Pool, Prototype

- Structural
- how to design class structure, relationships between classes
- patterns: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Memento, Proxy

- Behavioral
- the interaction/communication between objects
- patterns: Chain of Responsibility, Command, Iterator, Observer, Strategy, Visitor ..
Singleton
Singleton Pattern

Singleton:
● Ensures that only a single instance of a given class is created
● Provides a global access point to that instance

Examples:
● Database connection pool
● HTTP session manager
● Cache
● Loggers
● App configuration
Singleton Pattern

General principles for a Singleton class:


● private constructor -> prevents creating multiple instances
● private static field -> for storing the only instance
● public static method -> for obtaining the instance

class ConnectionPool {
private static ConnectionPool instance; //need to somehow init this!
private ConnectionPool() {}

public static ConnectionPool getInstance() {


return instance;
}
}
Singleton Pattern

Creating the instance - naive implementation (1):

public static ConnectionPool getInstance() {


if (instance == null) {
instance = new ConnectionPool();
}
return instance;
}

Is this thread-safe?
What happens if two threads call getInstance() before initialization at the same
time?..
Singleton Pattern

Creating the instance - naive implementation (2):

public synchronized static ConnectionPool getInstance() {


if (instance == null) {
instance = new ConnectionPool();
}
return instance;
}

This fixes the multithreading issue, but synchronization is expensive!


● we pay the price for every call to getInstance() (even if it’s actually needed only for
the first access...)
Singleton Pattern

Creating the instance - using a static field initializer:

class ConnectionPool {

private static ConnectionPool instance = new ConnectionPool();

private ConnectionPool() {}
public static ConnectionPool getInstance() { return instance; }
}

Advantages of static field initializers:


● only called the first time the class is used
● guaranteed to only happen once! (the JVM does the synchronization for us)
● no synchronization require for subsequent calls to getInstance()
Singleton Pattern

Creating the instance: using a static initializer block:

class ConnectionPool3 {

private static ConnectionPool3 instance;


static {
//...more complex initialization here!...
instance = new ConnectionPool3();
}
//...
}

Advantages: same as with field initializer, but allows more complex initialization code.
Singleton Pattern

Singleton is also considered an Anti-Pattern (depending on context/usage):


● many times it turns out we do want multiple instances
● makes code hard to test
○ e.g the test code may want to use a different ConnectionPool that just fakes
connections
● in many cases there are better alternative solutions (like Dependency Injection…)
Factory Method
Factory Method

Factory Method
- Create/obtain class instances by using a static method

MyClass.create(...)

Differences from using constructors (calling new() ):


● The factory method may actually return an existing instance (if creating a new
one is not really needed/wanted)
● Returned instance may be a subclass of the class where the method is declared
● Multiple factory methods may have the same set of parameters
Factory Method

Examples in the Java libraries:

● java.time.ZonedDateTime , LocalDateTime
○ from(...)
○ now()
○ of(int year, int month, int dayOfMonth, int hour, int minute)

● java.text.NumberFormat.getInstance(Locale locale)

● java.nio.charset.Charset.forName(...)
Abstract Factory
Abstract Factory

Allows the decoupling of how and what objects are created behind an interface:
● An AbstractFactory interface declares methods for creating families of objects
○ createButton, createRadioButton, createComboBox
○ createCache

● Clients call a factory instance to create objects that implement certain interfaces/classes,
but without knowing their concrete types
○ Button, ComboBox, RadioButton
○ Cache

● Classes that implement the AbstractFactory interface actually decide what concrete
implementation will be used for each type of produced objects
○ MacOSButton/WindowsButton, MacOSComboBox/WindowsComboBox
○ RedisCache/InMemoryCache, etc.
Abstract Factory
Abstract Factory

Advantages:

● Easier to test: test code can use a separate factory that creates mock objects

● Decoupling of code:
○ clients don’t need to know about the concrete implementations
○ easy to create new factories for new type families (e.g support Linux
besides Windows and MacOS)
Builder
Builder

Problem: we need to build an object:


- with lots of attributes (some optional, some mandatory)
- and/or with a complex build process (need to validate the combination of optional fields used, etc..)

Some possible solutions:


- create a class with lots of constructors (for all combination of optional fields)
- cumbersome to write/use (get the parameters in right order, etc)
- may actually be impossible (if optional fields have same type, cannot define a separate constructor
for each of them, as they have same signature)
- OR: create a class with 1 constructor (for mandatory params) + setters for all optional fields
- works, but no place to put final validation (new object may remain inconsistent)
- produced object cannot be made immutable (so it can be change by accident later..)

A better solution:
- use the Builder pattern to delegate the building process to a separate class...
Builder - Principles

Needed parts:
- a class to build instances of:
- should have a single private constructor (with params for all fields, including optional ones)
- should have only getters for all fields (no setters)

- a corresponding Builder class:


- should be a static inner class of first class (so it can access its private c’tor)
- should have copies of all fields of first class (to store Builder’s state/settings..)
- should have a single (public) constructor, with params for all mandatory fields
- should have a kind of ‘setter’ methods for all optional fields; each such setter will set a field’s
value, but will also return current builder instance (this) instead of void (for easy chaining of calls)
- should have a build() method which will be called at the end and build the actual target object
(based on settings from builder fields) and return it

- note: in more complex form of the pattern, there is also a Director class, which the client code calls
first, and which direct the builder(s) on what to build
Builder - Example

public class Car { //the target class (to be built)

//1) Has many fields (some mandatory + some optional)


private int doors;
private String color;
//optional:
private boolean leatherSeats;
private boolean electricWindows;

//2) Has a SINGLE CONSTRUCTOR, PRIVATE, with params for ALL fields! (mandatory+optional)
private Car(int doors, String color, boolean leatherSeats, boolean electricWindows) {
this.doors = doors;
this.color = color;
this.leatherSeats = leatherSeats;
this.electricWindows = electricWindows;
}

//3) Fields are read-only, so ONLY GETTERS here (no setters)


public int getDoors() { return doors; }
public String getColor() { return color; }
public boolean isLeatherSeats() { return leatherSeats; }
public boolean isElectricWindows() { return electricWindows; }
Builder - Example

public class Car { …

//1) SEPARATE BUILDER CLASS, NEEDS to be a STATIC INNER CLASS of it (to have access to Car private constructor)
public static class Builder {

//2) Has COPIES of ALL FIELDS of target class (Car)


private int doors;
private String color;
private boolean leatherSeats;
private boolean electricWindows;

//3) Has a SINGLE CONSTRUCTOR, PUBLIC, with params for all MANDATORY fields
public Builder(int doors, String color) { this.doors = doors; this.color = color; }

//4) Has 'setter-like' methods for all OPTIONAL fields; each one sets the field value (like a regular setter),
// and then also returns 'this', instead of void (for easier chaining of multiple calls later)
public Builder withLeatherSeats (boolean leatherSeats) { this.leatherSeats = leatherSeats; return this;}
public Builder withElectricWindows(boolean electricWindows) { this.electricWindows = electricWindows; return this;}

//5) Has a build() method, to be called at the end to actually build and return an instance of target class
// (based on current builder settings), possibly also running now some final validations
public Car build() {
//…may run some final verifications here…
return new Car(doors, color,leatherSeats, electricWindows);
}
Builder - Example

//Client code:
//First build a Builder instance (with just the default settings here)…
Car.Builder basicBuilder = new Car.Builder(3, "gray");

//…then may (re)use it to build multiple cars (with same settings)


Car car1 = basicBuilder.build();
Car car2 = basicBuilder.build();

//Use another builder for building cars with other settings:


Car.Builder luxuryBuilder = new Car.Builder(4, "black")
.withSatNav(true)
.withElectricWindows(true)
.withLeatherSeats(false);
Car car3 = luxuryBuilder.build();
Car car4 = luxuryBuilder.build();

//Shorter example of declaring and directly using a builder (for just 1 car):
Car car5 = new Car.Builder(4, "red") //build the builder, with values for mandatory fields…
.withElectricWindows(true) //may also configure the optional fields (each returns Builder)
.withLeatherSeats(true) //…
.build(); //call .build() to end the build chain and return the actual Car
Builder pattern in Java API

Examples of Builder pattern usage in Java libs:

- java.lang.StringBuilder: (one difference here: it’s not a inner class of String)


String str = new StringBuilder(20) //build the builder instance (with an initial capacity)
.append("abc") //append some stuff…
.append(123) //…
.toString(); //call the final 'build' method to produce the String

- java.util.Calendar:
Calendar cal = new Calendar.Builder()
.setCalendarType("iso8601")
.setWeekDate(2018, 1, MONDAY)
.build();

- java.util.Locale:
Locale loc = new Locale.Builder()
.setLanguage("sr").setScript("Latn").setRegion("RS")
.build();
Adapter
Adapter Pattern

Adapter (aka Wrapper, Translator):


- convert the interface of a class into another interface expected by a client
- especially useful when the interfaces are not under your control (e.g is a library)

Examples:
● Having a Book class and a Printer class that only receives Printable
instances, we create a BookPrintableAdapter that implements Printable
○ the new class “adapts” the functionality of the Book class to match the
interface required by the Printer class
● Having a Student class and a Mailer class that receives Addressable
instances, we create a StudentAddressableAdapter ...
Adapter Pattern

Adapter Pattern Diagram


Adapter Pattern

Advantages:

● Allows merging two independently-developed APIs together


○ both of them may come from external libraries

● Allows you to keep two APIs with different responsibilities separated:


○ e.g Mailer and Student

Examples from the Java libraries:


● Arrays.asList: allow arrays to be used by all APIs that require Lists
● ByteArrayInputStream: wraps a Byte Array as an input stream
Strategy
Strategy Pattern

Strategy:
● Describe one or more steps of an algorithm in an interface
● Clients use instances of the algorithm without knowing the implementation
● The algorithm used can change depending on various factors

Examples:
● The Basket class uses a DiscountCalculator to calculate discounts
● DiscountCalculator is an interface with several methods:
○ applyCoupon(...)
○ applyBasketDiscount(...)
● DiscountCalculator has multiple implementations (regular / happy hour / black friday)
Strategy Pattern - Examples

Examples:

● Collections.sort() method that takes a Comparator parameter


○ based on the different implementations of Comparator interfaces, the
collection items are sorted in different ways

● a Shopping Cart where we have two payment strategies – using Credit Card or
using PayPal
Strategy Pattern – Shopping Cart Example

interface PaymentStrategy {
void pay(int amount);
}

class CreditCardStrategy implements PaymentStrategy {


private final String cardNumber, cvv, dateOfExpiry;
CreditCardStrategy(...) {...}

@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card");
}
}
Strategy Pattern – Shopping Cart Example

class PaypalStrategy implements PaymentStrategy {


private final String emailId, password;
PaypalStrategy(...) {...}

@Override
public void pay(int amount) {
System.out.println(amount + " paid using Paypal");
}
}
Strategy Pattern – Shopping Cart Example

class Item {
private final int price;
//other fields...

//constructor, getters...
}
Strategy Pattern – Shopping Cart Example

class ShoppingCart {

private final List<Item> items = new ArrayList<>();


void add(Item... newItems) { items.addAll(Arrays.asList(newItems)); }

void pay(PaymentStrategy paymentStrategy) {


int amount = calculateTotal();
paymentStrategy.pay(amount);
}

int calculateTotal() {
return items.stream().mapToInt(Item::getPrice).sum();
}
}
Strategy Pattern – Shopping Cart Example

public class ShoppingMain {


public static void main(String[] args) {

ShoppingCart cart = new ShoppingCart();


cart.add(new Item("1234", 10), new Item("5678", 40));

//pay by paypal
cart.pay(new PaypalStrategy("myemail@example.com", "mypwd"));

//pay by credit card


cart.pay(new CreditCardStrategy("567890123456","786","12/15"));
}
}
Strategy Pattern – Shopping Cart Example
Visitor
Visitor

Visitor:
● used when we have to perform an operation on a group of similar objects
● helps to move the operational logic from the objects to another class

● Example:
○ shopping cart - we can add different types of items (all sharing a base type)
○ when we click on checkout button, it calculates the total amount to be paid
○ we can have the calculation logic in the item classes or we can move out
this logic to another class using visitor pattern
Visitor – Shopping Cart Example

interface Item {
int accept(ShoppingCartVisitor visitor);
}

class Book implements Item {


private int price;
Book(int price) { this.price = price; }
int getPrice() { return price; }

@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
Visitor – Shopping Cart Example

class Vegetable implements Item {

private int pricePerKg;


private int weight;

Vegetable(int pricePerKg, int weight) {


this.pricePerKg = pricePerKg;
this.weight = weight;
}
int getPricePerKg() { return pricePerKg; }
int getWeight() { return weight; }

@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
Visitor – Shopping Cart Example

interface ShoppingCartVisitor {
int visit(Book book);
int visit(Vegetable vegetable);
}

class ShoppingCartVisitorImpl implements ShoppingCartVisitor {


public int visit(Book book) {
return book.getPrice() > 100 ?
book.getPrice() - 5 :
book.getPrice();
}
public int visit(Vegetable vegetable) {
return vegetable.getPricePerKg() * vegetable.getWeight();
}
}
Visitor – Shopping Cart Example

class ShoppingCartClient {
public static void main(String[] args) {
Item[] items = new Item[]
{new Book(40), new Book(120), new Vegetable(10, 3)};

ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();


int total = 0;
for (Item item : items) {
total += item.accept(visitor);
}
System.out.println("total: " + total);
}
}
Visitor – Shopping Cart Example
Visitor – benefits & limitations

Benefits:
● if the logic of operation changes, then we need to make change only in the
visitor implementation rather than doing it in all the item classes

● adding a new item to the system is easy, as it will require changes in visitor
interface+implementation, but existing item classes will not be affected

Limitations:
● we should know the return type of visit() methods at the time of designing
otherwise we will have to change the interface and all of its implementations.

● if there are too many implementations of visitor interface, it makes it hard to


extend.
Questions?
Extra reading

● https://www.journaldev.com/1827/java-design-patterns-example-tutorial
● https://java-design-patterns.com/patterns/
● https://www.oodesign.com/

● https://www.byteslounge.com/tutorials/java-builder-pattern-example
● https://www.byteslounge.com/tutorials/java-factory-pattern-example
● https://www.byteslounge.com/tutorials/java-abstract-factory-pattern-example
● https://www.byteslounge.com/tutorials/java-visitor-pattern
● https://www.byteslounge.com/tutorials/java-adapter-pattern-example

● http://tutorials.jenkov.com/java/nested-classes.html
● https://www.geeksforgeeks.org/nested-classes-java/

You might also like