Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
9 views

Java Design Patterns

The document discusses design patterns including Singleton, Factory Method, Abstract Factory, and Builder patterns. It provides examples of how each pattern addresses common programming problems. The Singleton pattern ensures only one instance of a class is created. Factory Method creates class instances through static methods. Abstract Factory decouples object creation behind common interfaces. Builder is used for complex object construction by delegating the building process to a separate class.

Uploaded by

Radu Radu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Java Design Patterns

The document discusses design patterns including Singleton, Factory Method, Abstract Factory, and Builder patterns. It provides examples of how each pattern addresses common programming problems. The Singleton pattern ensures only one instance of a class is created. Factory Method creates class instances through static methods. Abstract Factory decouples object creation behind common interfaces. Builder is used for complex object construction by delegating the building process to a separate class.

Uploaded by

Radu Radu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 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