Professional Documents
Culture Documents
JAVA Development: Design Patterns
JAVA Development: Design Patterns
Design Patterns
Design Patterns
Design Patterns
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
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
class ConnectionPool {
private static ConnectionPool instance; //need to somehow init this!
private ConnectionPool() {}
Is this thread-safe?
What happens if two threads call getInstance() before initialization at the same
time?..
Singleton Pattern
class ConnectionPool {
private ConnectionPool() {}
public static ConnectionPool getInstance() { return instance; }
}
class ConnectionPool3 {
Advantages: same as with field initializer, but allows more complex initialization code.
Singleton Pattern
Factory Method
- Create/obtain class instances by using a static method
MyClass.create(...)
● 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
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)
- 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
//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;
}
//1) SEPARATE BUILDER CLASS, NEEDS to be a STATIC INNER CLASS of it (to have access to Car private constructor)
public static class Builder {
//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");
//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
- 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
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
Advantages:
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:
● 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);
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card");
}
}
Strategy Pattern – Shopping Cart Example
@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 {
int calculateTotal() {
return items.stream().mapToInt(Item::getPrice).sum();
}
}
Strategy Pattern – Shopping Cart Example
//pay by paypal
cart.pay(new PaypalStrategy("myemail@example.com", "mypwd"));
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);
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
Visitor – Shopping Cart Example
@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 ShoppingCartClient {
public static void main(String[] args) {
Item[] items = new Item[]
{new Book(40), new Book(120), new Vegetable(10, 3)};
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.
● 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/