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

JAVA Development

Design Patterns
Design Patterns

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

Advantages:
● identify classes of similar problems where known solutions can be applied
○ don’t reinvent the wheel every time
○ gives you a more robust/tested solution to your problem

● refer common 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 what solutions you should not apply a certain problems
○ don’t reinvent the wheel badly every time
○ gives you the reasons to avoid that common solution (which may not be so
obvious to less experienced designers)
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:


- has only a private constructor -> prevents creating multiple instances
- has a private static field -> for storing the only instance
- has a public static method -> for obtaining the instance from outside code

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!


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

Creating the instance - by using a static field initializer:

class ConnectionPool {
private static ConnectionPool instance = new ConnectionPool();

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

Advantages:
- only called the first time the class is used
- guaranteed to only happen once! (by JVM - it does the synchronization for us)
- no synchronization required for subsequent calls to getInstance()
Singleton Pattern

Creating the instance - by using a static initializer block:

class ConnectionPool {

private static ConnectionPool instance;


static { //static init block
//more complex initialization code here?…
instance = new ConnectionPool();
}
}

Advantages: same as for field initializer, plus allows more complex initialization code
Singleton Pattern

Note: Singleton is also considered an anti-pattern, depending on context/usage:

- many times it turns out we do want multiple instances

- using it makes the code hard to test


○ e.g the test code may want to use a different ConnectionPool that just fakes
connections, but is stuck with the single global ‘good’ instance

- in many cases there are better alternative solutions (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 - Usage Examples

Examples of factory method usage in Java libraries:

● java.time.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

● Decoupled 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..)

Possible solutions:
- create a class with lots of constructors (for all combinations 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) + many 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..)

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

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

- a corresponding Builder class:


- is a static inner class of first class (so it can access its private c’tor)
- has copies of all fields of first class (to store Builder’s state/settings..)
- has a single (public) constructor, with params for all mandatory fields
- has (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
- has 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 forms there is also a Director class, 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 { …

//4) 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 - Example

Details to note:
- Car class: the fields and (the single) constructor are all private (and no setters, just getters)
- Car.Builder: is a nested class for Car; it has similar fields, a public constructor (to initialize only the
mandatory fields), some ‘setter’ methods, and the specific build() method
Builder - Usage Examples

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):


- converts 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 API of Book class to match the API of Printable interface
required by the Printer (‘translates’ between them…)

- having a Student class and a Mailer class that receives Addressable instances -> we
create a StudentAddressableAdapter ...
Adapter Pattern - Example

Note:
- the adapter (BookPrintableAdapter) uses the public API of the original class (Book)
- it contains translation code to expose this under the new form expected by target interface (Printable)
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: allows 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 actual 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, expirationDate;
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 Cart {
private final List<Item> items = new ArrayList<>();
void add(Item... newItems) { items.addAll(Arrays.asList(newItems)); }

int calculateTotal() {
return items.stream().mapToInt(Item::getPrice).sum();
}

void pay(PaymentStrategy strategy) {


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

class Item { private final int price; //* … other fields,methods … */ }


Strategy Pattern – Shopping Cart Example

public class ShoppingMain {


public static void main(String[] args) {

Cart cart = new Cart();


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(CartVisitor visitor);
}

class Book implements Item {


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

@Override
public int accept(CartVisitor 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(CartVisitor visitor) {
return visitor.visit(this);
}
}
Visitor – Shopping Cart Example

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

class CartVisitorImpl implements CartVisitor {


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 ShoppingApp {
public static void main(String[] args) {
Item[] items = new Item[]
{new Book(40), new Book(120), new Vegetable(10, 3)};

CartVisitor visitor = new CartVisitorImpl();


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