File: README.md
Design Patterns Implemented in Java
Read in different language : zh, zh-TW
,ko, fr, tr, ar, es, pt, id, ru, de, ja, vi, bn, np, it, da
Introduction
Design patterns are the best, formalized practices a programmer can use to solve common problems when designing an application or system.
Design patterns can speed up the development process by providing tested, proven development paradigms.
Reusing design patterns helps prevent subtle issues that cause major problems, and it also improves code readability for coders and architects who are familiar with the patterns.
Getting Started
This site showcases Java Design Patterns. The solutions have been developed by experienced programmers and architects from the open-source community. The patterns can be browsed by their high-level descriptions or by looking at their
source code. The source code examples are well commented and can be thought of as programming tutorials on how to implement a specific pattern. We use the most popular battle-proven open-source Java technologies.
Before you dive into the material, you should be familiar with various Software Design Principles.
All designs should be as simple as possible. You should start with KISS, YAGNI, and Do The Simplest Thing That Could Possibly Work principles. Complexity and patterns should only be introduced when they are needed for practical extensibility.
Once you are familiar with these concepts you can start drilling down into the available design patterns by any of the following approaches:
- Search for a specific pattern by name. Can't find one? Please report a new pattern here.
- Using tags such as
Performance,Gang of FourorData access. - Using pattern categories,
Creational,Behavioral, and others.
Hopefully, you find the object-oriented solutions presented on this site useful in your architectures and have as much fun learning them as we had while developing them.
How to Contribute
If you are willing to contribute to the project you will find the relevant information in our developer wiki. We will help you and answer your questions in the Gitter chatroom.
The Book
The design patterns are now available as an e-book. Find out more about "Open Source Java Design Patterns" here: https://payhip.com/b/bNQFX
The project contributors can get the book for free. Contact the maintainer via Gitter chatroom or email (iluwatar (at) gmail (dot) com ). Send a message that contains your email address, Github username, and a link to an accepted pull request.
License
This project is licensed under the terms of the MIT license.
Contributors
File: abstract-document/README.md
title: "Abstract Document Pattern in Java: Simplifying Data Handling with Flexibility"
shortTitle: Abstract Document
category: Structural
language: en
tag:
- Abstraction
- Decoupling
- Dynamic typing
- Encapsulation
- Extensibility
- Polymorphism
Intent of Abstract Document Design Pattern
The Abstract Document design pattern in Java is a crucial structural design pattern that provides a consistent way to handle hierarchical and tree-like data structures by defining a common interface for various document types. It separates the core document structure from specific data formats, enabling dynamic updates and simplified maintenance.
Detailed Explanation of Abstract Document Pattern with Real-World Examples
The Abstract Document design pattern in Java allows dynamic handling of non-static properties. This pattern uses concept of traits to enable type safety and separate properties of different classes into set of interfaces.
Real-world example
Consider a library system implementing the Abstract Document design pattern in Java, where books can have diverse formats and attributes: physical books, eBooks, and audiobooks. Each format has unique properties, such as page count for physical books, file size for eBooks, and duration for audiobooks. The Abstract Document design pattern allows the library system to manage these diverse formats flexibly. By using this pattern, the system can store and retrieve properties dynamically, without needing a rigid structure for each book type, making it easier to add new formats or attributes in the future without significant changes to the codebase.
In plain words
Abstract Document pattern allows attaching properties to objects without them knowing about it.
Wikipedia says
An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the support of type-safety. The pattern makes use of traits to separate different properties of a class into different interfaces.
Class diagram
Programmatic Example of Abstract Document Pattern in Java
Consider a car that consists of multiple parts. However, we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible.
Let's first define the base classes Document and AbstractDocument. They basically make the object hold a property map and any amount of child objects.
public interface Document {
Void put(String key, Object value);
Object get(String key);
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
}
public abstract class AbstractDocument implements Document {
private final Map<String, Object> properties;
protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}
@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}
@Override
public Object get(String key) {
return properties.get(key);
}
@Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
return Stream.ofNullable(get(key))
.filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el)
.findAny()
.stream()
.flatMap(Collection::stream)
.map(constructor);
}
// Other properties and methods...
}Next we define an enum Property and a set of interfaces for type, price, model and parts. This allows us to create static looking interface to our Car class.
public enum Property {
PARTS, TYPE, PRICE, MODEL
}
public interface HasType extends Document {
default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
}
public interface HasPrice extends Document {
default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
}
public interface HasModel extends Document {
default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString()));
}
}
public interface HasParts extends Document {
default Stream getParts() {
return children(Property.PARTS.toString(), Part::new);
}
}
public class Part extends AbstractDocument implements HasType, HasModel, HasPrice {
public Part(Map<String, Object> properties) {
super(properties);
}
}Now we are ready to introduce the Car.
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
public Car(Map<String, Object> properties) {
super(properties);
}
}And finally here's how we construct and use the Car in a full example.
public static void main(String[] args) {
LOGGER.info("Constructing parts and car");
var wheelProperties = Map.of(
Property.TYPE.toString(), "wheel",
Property.MODEL.toString(), "15C",
Property.PRICE.toString(), 100L);
var doorProperties = Map.of(
Property.TYPE.toString(), "door",
Property.MODEL.toString(), "Lambo",
Property.PRICE.toString(), 300L);
var carProperties = Map.of(
Property.MODEL.toString(), "300SL",
Property.PRICE.toString(), 10000L,
Property.PARTS.toString(), List.of(wheelProperties, doorProperties));
var car = new Car(carProperties);
LOGGER.info("Here is our car:");
LOGGER.info("-> model: {}", car.getModel().orElseThrow());
LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
LOGGER.info("-> parts: ");
car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}",
p.getType().orElse(null),
p.getModel().orElse(null),
p.getPrice().orElse(null))
);
}The program output:
07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Constructing parts and car
07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Here is our car:
07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> model: 300SL
07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> price: 10000
07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> parts:
07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- wheel/15C/100
07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300When to Use the Abstract Document Pattern in Java
The Abstract Document design pattern is especially beneficial in scenarios requiring management of different document types in Java that share some common attributes or behaviors, but also have unique attributes or behaviors specific to their individual types. Here are some scenarios where the Abstract Document design pattern can be applicable:
Content Management Systems (CMS): In a CMS, you might have various types of content such as articles, images, videos, etc. Each type of content could have shared attributes like creation date, author, and tags, while also having specific attributes like image dimensions for images or video duration for videos.
File Systems: If you're designing a file system where different types of files need to be managed, such as documents, images, audio files, and directories, the Abstract Document pattern can help provide a consistent way to access attributes like file size, creation date, etc., while allowing for specific attributes like image resolution or audio duration.
E-commerce Systems: An e-commerce platform might have different product types such as physical products, digital downloads, and subscriptions. Each type could share common attributes like name, price, and description, while having unique attributes like shipping weight for physical products or download link for digital products.
Medical Records Systems: In healthcare, patient records might include various types of data such as demographics, medical history, test results, and prescriptions. The Abstract Document pattern can help manage shared attributes like patient ID and date of birth, while accommodating specialized attributes like test results or prescribed medications.
Configuration Management: When dealing with configuration settings for software applications, there can be different types of configuration elements, each with its own set of attributes. The Abstract Document pattern can be used to manage these configuration elements while ensuring a consistent way to access and manipulate their attributes.
Educational Platforms: Educational systems might have various types of learning materials such as text-based content, videos, quizzes, and assignments. Common attributes like title, author, and publication date can be shared, while unique attributes like video duration or assignment due dates can be specific to each type.
Project Management Tools: In project management applications, you could have different types of tasks like to-do items, milestones, and issues. The Abstract Document pattern could be used to handle general attributes like task name and assignee, while allowing for specific attributes like milestone date or issue priority.
Documents have diverse and evolving attribute structures.
Dynamically adding new properties is a common requirement.
Decoupling data access from specific formats is crucial.
Maintainability and flexibility are critical for the codebase.
The key idea behind the Abstract Document design pattern is to provide a flexible and extensible way to manage different types of documents or entities with shared and distinct attributes. By defining a common interface and implementing it across various document types, you can achieve a more organized and consistent approach to handling complex data structures.
Benefits and Trade-offs of Abstract Document Pattern
Benefits:
Flexibility: Accommodates varied document structures and properties.
Extensibility: Dynamically add new attributes without breaking existing code.
Maintainability: Promotes clean and adaptable code due to separation of concerns.
Reusability: Typed views enable code reuse for accessing specific attribute types.
Trade-offs:
Complexity: Requires defining interfaces and views, adding implementation overhead.
Performance: Might introduce slight performance overhead compared to direct data access.
References and Credits
- Design Patterns: Elements of Reusable Object-Oriented Software
- Java Design Patterns: A Hands-On Experience with Real-World Examples
- Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)
- Patterns of Enterprise Application Architecture
- Abstract Document Pattern (Wikipedia)
- Dealing with Properties (Martin Fowler)
File: abstract-factory/README.md
title: "Abstract Factory Pattern in Java: Mastering Object Creation Elegantly"
shortTitle: Abstract Factory
category: Creational
language: en
tag:
- Abstraction
- Decoupling
- Gang of Four
- Instantiation
- Polymorphism
Also known as
- Kit
Intent of Abstract Factory Design Pattern
The Abstract Factory pattern in Java provides an interface for creating families of related or dependent objects without specifying their concrete classes, enhancing modularity and flexibility in software design.
Detailed Explanation of Abstract Factory Pattern with Real-World Examples
Real-world example
Imagine a furniture company that uses the Abstract Factory pattern in Java to produce various styles of furniture: modern, Victorian, and rustic. Each style includes products like chairs, tables, and sofas. To ensure consistency within each style, the company uses an Abstract Factory pattern.
In this scenario, the Abstract Factory is an interface for creating families of related furniture objects (chairs, tables, sofas). Each concrete factory (ModernFurnitureFactory, VictorianFurnitureFactory, RusticFurnitureFactory) implements the Abstract Factory interface and creates a set of products that match the specific style. This way, clients can create a whole set of modern or Victorian furniture without worrying about the details of their instantiation. This maintains a consistent style and allows easy swapping of one style of furniture for another.
In plain words
A factory of factories; a factory that groups the individual but related/dependent factories together without specifying their concrete classes.
Wikipedia says
The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes
Class diagram
Programmatic Example of Abstract Factory in Java
To create a kingdom using the Abstract Factory pattern in Java, we need objects with a common theme. The elven kingdom needs an elven king, elven castle, and elven army whereas the orcish kingdom needs an orcish king, orcish castle, and orcish army. There is a dependency between the objects in the kingdom.
Translating the kingdom example above. First of all, we have some interfaces and implementation for the objects in the kingdom.
public interface Castle {
String getDescription();
}
public interface King {
String getDescription();
}
public interface Army {
String getDescription();
}
// Elven implementations ->
public class ElfCastle implements Castle {
static final String DESCRIPTION = "This is the elven castle!";
@Override
public String getDescription() {
return DESCRIPTION;
}
}
public class ElfKing implements King {
static final String DESCRIPTION = "This is the elven king!";
@Override
public String getDescription() {
return DESCRIPTION;
}
}
public class ElfArmy implements Army {
static final String DESCRIPTION = "This is the elven Army!";
@Override
public String getDescription() {
return DESCRIPTION;
}
}
// Orcish implementations similarly -> ...Then we have the abstraction and implementations for the kingdom factory.
public interface KingdomFactory {
Castle createCastle();
King createKing();
Army createArmy();
}
public class ElfKingdomFactory implements KingdomFactory {
@Override
public Castle createCastle() {
return new ElfCastle();
}
@Override
public King createKing() {
return new ElfKing();
}
@Override
public Army createArmy() {
return new ElfArmy();
}
}
// Orcish implementations similarly -> ...Now, we can design a factory for our different kingdom factories. In this example, we created FactoryMaker, responsible for returning an instance of either ElfKingdomFactory or OrcKingdomFactory. The client can use FactoryMaker to create the desired concrete factory which, in turn, will produce different concrete objects (derived from Army, King, Castle). In this example, we also used an enum to parameterize which type of kingdom factory the client will ask for.
public static class FactoryMaker {
public enum KingdomType {
ELF, ORC
}
public static KingdomFactory makeFactory(KingdomType type) {
return switch (type) {
case ELF -> new ElfKingdomFactory();
case ORC -> new OrcKingdomFactory();
};
}
}Here is the main function of our example application:
LOGGER.info("elf kingdom");
createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
LOGGER.info(kingdom.getArmy().getDescription());
LOGGER.info(kingdom.getCastle().getDescription());
LOGGER.info(kingdom.getKing().getDescription());
LOGGER.info("orc kingdom");
createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
LOGGER.info(kingdom.getArmy().getDescription());
LOGGER.info(kingdom.getCastle().getDescription());
LOGGER.info(kingdom.getKing().getDescription());The program output:
07:35:46.340 [main] INFO com.iluwatar.abstractfactory.App -- elf kingdom
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven army!
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven castle!
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven king!
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- orc kingdom
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc army!
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc castle!
07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc king!When to Use the Abstract Factory Pattern in Java
Use the Abstract Factory pattern in Java when:
- The system should be independent of how its products are created, composed, and represented.
- You need to configure the system with one of multiple families of products.
- A family of related product objects must be used together, enforcing consistency.
- You want to provide a class library of products, exposing only their interfaces, not their implementations.
- The lifetime of dependencies is shorter than the consumer's lifetime.
- Dependencies need to be constructed using runtime values or parameters.
- You need to choose which product to use from a family at runtime.
- Adding new products or families should not require changes to existing code.
Abstract Factory Pattern Java Tutorials
Benefits and Trade-offs of Abstract Factory Pattern
Benefits:
Flexibility: Easily switch between product families without code modifications.
Decoupling: Client code only interacts with abstract interfaces, promoting portability and maintainability.
Reusability: Abstract factories and products facilitate component reuse across projects.
Maintainability: Changes to individual product families are localized, simplifying updates.
Trade-offs:
Complexity: Defining abstract interfaces and concrete factories adds initial overhead.
Indirectness: Client code interacts with products indirectly through factories, potentially reducing transparency.
Real-World Applications of Abstract Factory Pattern in Java
- Java Swing's
LookAndFeelclasses for providing different look-and-feel options. - Various implementations in the Java Abstract Window Toolkit (AWT) for creating different GUI components.
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
Related Java Design Patterns
- Factory Method: Abstract Factory uses Factory Methods to create products.
- Singleton: Abstract Factory classes are often implemented as Singletons.
- Factory Kit: Similar to Abstract Factory but focuses on configuring and managing a set of related objects in a flexible way.
References and Credits
- Design Patterns: Elements of Reusable Object-Oriented Software
- Design Patterns in Java
- Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software
- Java Design Patterns: A Hands-On Experience with Real-World Examples
File: active-object/README.md
title: "Active Object Pattern in Java: Achieving Efficient Asynchronous Processing"
shortTitle: Active Object
category: Concurrency
language: en
tag:
- Asynchronous
- Decoupling
- Messaging
- Synchronization
- Thread management
Intent of Active Object Design Pattern
The Active Object pattern provides a reliable method for asynchronous processing in Java, ensuring responsive applications and efficient thread management. It achieves this by encapsulating tasks within objects that have their own thread and message queue. This separation keeps the main thread responsive and avoids issues like direct thread manipulation or shared state access.
Detailed Explanation of Active Object Pattern with Real-World Examples
Real-world example
Imagine a busy restaurant where customers place orders with waiters. Instead of the waiters going to the kitchen to prepare the food themselves, they write the orders on slips and hand them to a dispatcher. The dispatcher manages a pool of chefs who prepare the meals asynchronously. Once a chef is free, they pick up the next order from the queue, prepare the dish, and notify the waiter when it's ready for serving.
In this analogy, the waiters represent the client threads, the dispatcher represents the scheduler, and the chefs represent the method execution in separate threads. This setup allows the waiters to continue taking orders without being blocked by the food preparation process, much like the Active Object pattern decouples method invocation from execution to enhance concurrency.
In plain words
The Active Object pattern decouples method execution from method invocation to improve concurrency and responsiveness in multithreaded applications.
Wikipedia says
The active object design pattern decouples method execution from method invocation for objects that each reside in their own thread of control.[1] The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests.
The pattern consists of six elements:
- A proxy, which provides an interface towards clients with publicly accessible methods.
- An interface which defines the method request on an active object.
- A list of pending requests from clients.
- A scheduler, which decides which request to execute next.
- The implementation of the active object method.
- A callback or variable for the client to receive the result.
Sequence diagram
Programmatic Example of Active Object in Java
This section explains how the Active Object design pattern works in Java, highlighting its use in asynchronous task management and concurrency control.
The Orcs are known for their wildness and untameable soul. It seems like they have their own thread of control based on previous behavior. To implement a creature that has its own thread of control mechanism and expose its API only and not the execution itself, we can use the Active Object pattern.
public abstract class ActiveCreature {
private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
private BlockingQueue<Runnable> requests;
private String name;
private Thread thread;
public ActiveCreature(String name) {
this.name = name;
this.requests = new LinkedBlockingQueue<Runnable>();
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
requests.take().run();
} catch (InterruptedException e) {
logger.error(e.getMessage());
}
}
}
}
);
thread.start();
}
public void eat() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} is eating!", name());
logger.info("{} has finished eating!", name());
}
}
);
}
public void roam() throws InterruptedException {
requests.put(new Runnable() {
@Override
public void run() {
logger.info("{} has started to roam the wastelands.", name());
}
}
);
}
public String name() {
return this.name;
}
}We can see that any class that will extend the ActiveCreature class will have its own thread of control to invoke and execute methods.
For example, the Orc class:
public class Orc extends ActiveCreature {
public Orc(String name) {
super(name);
}
}Now, we can create multiple creatures such as orcs, tell them to eat and roam, and they will execute it on their own thread of control:
public class App implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
private static final int NUM_CREATURES = 3;
public static void main(String[] args) {
var app = new App();
app.run();
}
@Override
public void run() {
List<ActiveCreature> creatures = new ArrayList<>();
try {
for (int i = 0; i < NUM_CREATURES; i++) {
creatures.add(new Orc(Orc.class.getSimpleName() + i));
creatures.get(i).eat();
creatures.get(i).roam();
}
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.error(e.getMessage());
Thread.currentThread().interrupt();
} finally {
for (int i = 0; i < NUM_CREATURES; i++) {
creatures.get(i).kill(0);
}
}
}
}Program output:
09:00:02.501 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 is eating!
09:00:02.501 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 is eating!
09:00:02.501 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 is eating!
09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has finished eating!
09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has finished eating!
09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has started to roam in the wastelands.
09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has finished eating!
09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has started to roam in the wastelands.
09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has started to roam in the wastelands.When to Use the Active Object Pattern in Java
Use the Active Object pattern in Java when:
- when you need to handle asynchronous tasks without blocking the main thread, ensuring better performance and responsiveness.
- When you need to interact with external resources asynchronously.
- When you want to improve the responsiveness of your application.
- When you need to manage concurrent tasks in a modular and maintainable way.
Active Object Pattern Java Tutorials
Real-World Applications of Active Object Pattern in Java
- Real-time trading systems where transaction requests are handled asynchronously.
- GUIs where long-running tasks are executed in the background without freezing the user interface.
- Game programming to handle concurrent updates to game state or AI computations.
Benefits and Trade-offs of Active Object Pattern
Discover the benefits and trade-offs of using the Active Object pattern in Java, including improved thread safety and potential overhead concerns.
Benefits:
- Improves responsiveness of the main thread.
- Encapsulates concurrency concerns within objects.
- Promotes better code organization and maintainability.
- Provides thread safety and avoids shared state access problems.
Trade-offs:
- Introduces additional overhead due to message passing and thread management.
- May not be suitable for all types of concurrency problems.
Related Java Design Patterns
- Command: Encapsulates a request as an object, similarly to how the Active Object pattern encapsulates method calls.
- Promise: Provides a means to retrieve the result of an asynchronous method call, often used in conjunction with Active Object.
- Proxy: The Active Object pattern can use a proxy to handle method invocations asynchronously.
References and Credits
- Design Patterns: Elements of Reusable Object Software
- Concurrent Programming in Java: Design Principles and Patterns
- Java Concurrency in Practice
- Learning Concurrent Programming in Scala
- Pattern Languages of Program Design 3
- Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects
File: acyclic-visitor/README.md
title: "Acyclic Visitor Pattern in Java: Streamlining Object Interactions"
shortTitle: Acyclic Visitor
category: Behavioral
language: en
tag:
- Decoupling
- Extensibility
- Interface
- Object composition
Intent of Acyclic Visitor Design Pattern
The Acyclic Visitor pattern in Java decouples operations from an object hierarchy, providing a flexible design for various applications.
Detailed Explanation of Acyclic Visitor Pattern with Real-World Examples
Real-world example
An analogous real-world example of the Acyclic Visitor pattern in Java is a museum guide system, demonstrating the practical application of this design pattern. Imagine a museum with various exhibits like paintings, sculptures, and historical artifacts. The museum has different types of guides (audio guide, human guide, virtual reality guide) that provide information about each exhibit. Instead of modifying the exhibits every time a new guide type is introduced, each guide implements an interface to visit different exhibit types. This way, the museum can add new types of guides without altering the existing exhibits, ensuring that the system remains extensible and maintainable without forming any dependency cycles.
In plain words
Acyclic Visitor allows functions to be added to existing class hierarchies without modifying the hierarchies.
WikiWikiWeb says
The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern.
Sequence diagram
Programmatic Example of Acyclic Visitor in Java
In this Java example, we have a hierarchy of modem classes illustrating the Acyclic Visitor pattern. The modems in this hierarchy need to be visited by an external algorithm based on filtering criteria (is it Unix or DOS compatible modem).
Here's the Modem hierarchy.
public abstract class Modem {
public abstract void accept(ModemVisitor modemVisitor);
}
public class Zoom extends Modem {
// Other properties and methods...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
}
}
}
public class Hayes extends Modem {
// Other properties and methods...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
}
}
}Next, we introduce the ModemVisitor hierarchy.
public interface ModemVisitor {
}
public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes);
}
public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom);
}
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
}
public class ConfigureForDosVisitor implements AllModemVisitor {
// Other properties and methods...
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Dos configurator.");
}
}
public class ConfigureForUnixVisitor implements ZoomVisitor {
// Other properties and methods...
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");
}
}Finally, here are the visitors in action.
public static void main(String[] args) {
var conUnix = new ConfigureForUnixVisitor();
var conDos = new ConfigureForDosVisitor();
var zoom = new Zoom();
var hayes = new Hayes();
hayes.accept(conDos); // Hayes modem with Dos configurator
zoom.accept(conDos); // Zoom modem with Dos configurator
hayes.accept(conUnix); // Hayes modem with Unix configurator
zoom.accept(conUnix); // Zoom modem with Unix configurator
}Program output:
09:15:11.125 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForDosVisitor -- Hayes modem used with Dos configurator.
09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForDosVisitor -- Zoom modem used with Dos configurator.
09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.Hayes -- Only HayesVisitor is allowed to visit Hayes modem
09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor -- Zoom modem used with Unix configurator.When to Use the Acyclic Visitor Pattern in Java
This pattern can be used:
- When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy.
- When there are functions that operate upon a hierarchy, but which do not belong in the hierarchy itself. e.g. the ConfigureForDOS / ConfigureForUnix / ConfigureForX issue.
- When you need to perform very different operations on an object depending upon its type.
- When the visited class hierarchy will be frequently extended with new derivatives of the Element class.
- When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive.
Acyclic Visitor Pattern Java Tutorials
Benefits and Trade-offs of Acyclic Visitor Pattern
Benefits:
- Extensible: New operations can be added easily without changing the object structure.
- Decoupled: Reduces coupling between the objects and the operations on them.
- No dependency cycles: Ensures acyclic dependencies, improving maintainability and reducing complexity.
Trade-offs:
- Increased complexity: Can introduce additional complexity with the need for multiple visitor interfaces.
- Maintenance overhead: Modifying the object hierarchy requires updating all visitors.
Related Java Design Patterns
- Composite: Often used in conjunction with Acyclic Visitor to allow treating individual objects and compositions uniformly.
- Decorator: Can be used alongside to add responsibilities to objects dynamically.
- Visitor: The Acyclic Visitor pattern is a variation of the Visitor pattern that avoids cyclic dependencies.
References and Credits
- Design Patterns: Elements of Reusable Object-Oriented Software
- Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software
- Java Design Patterns: A Hands-On Experience with Real-World Examples
- Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML
- Acyclic Visitor (Robert C. Martin)
- Acyclic Visitor (WikiWikiWeb)
File: adapter/README.md
title: "Adapter Pattern in Java: Seamless Integration of Incompatible Systems"
shortTitle: Adapter
category: Structural
language: en
tag:
- Compatibility
- Decoupling
- Gang of Four
- Interface
- Object composition
- Wrapping
Also known as
- Wrapper
Intent of Adapter Design Pattern
The Adapter Design Pattern in Java converts the interface of a class into another interface that clients expect, enabling compatibility.
Detailed Explanation of Adapter Pattern with Real-World Examples
Real-world example
Consider that you have some pictures on your memory card and you need to transfer them to your computer. To transfer them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to your computer. In this case card reader is an adapter. Another example would be the famous power adapter; a three-legged plug can't be connected to a two-pronged outlet, it needs to use a power adapter that makes it compatible with the two-pronged outlets. Yet another example would be a translator translating words spoken by one person to another
In plain words
Adapter pattern lets you wrap an otherwise incompatible object in an adapter to make it compatible with another class.
Wikipedia says
In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
Sequence diagram
Programmatic Example of Adapter Pattern in Java
The Adapter Pattern example in Java shows how a class with an incompatible interface can be adapted to work with another class.
Consider a wannabe captain that can only use rowing boats but can't sail at all.
First, we have interfaces RowingBoat and FishingBoat
public interface RowingBoat {
void row();
}
@Slf4j
public class FishingBoat {
public void sail() {
LOGGER.info("The fishing boat is sailing");
}
}The captain expects an implementation of RowingBoat interface to be able to move.
public class Captain {
private final RowingBoat rowingBoat;
// default constructor and setter for rowingBoat
public Captain(RowingBoat rowingBoat) {
this.rowingBoat = rowingBoat;
}
public void row() {
rowingBoat.row();
}
}Now, let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
@Slf4j
public class FishingBoatAdapter implements RowingBoat {
private final FishingBoat boat;
public FishingBoatAdapter() {
boat = new FishingBoat();
}
@Override
public void row() {
boat.sail();
}
}Now the Captain can use the FishingBoat to escape the pirates.
public static void main(final String[] args) {
// The captain can only operate rowing boats but with adapter he is able to
// use fishing boats as well
var captain = new Captain(new FishingBoatAdapter());
captain.row();
}The program outputs:
10:25:08.074 [main] INFO com.iluwatar.adapter.FishingBoat -- The fishing boat is sailingWhen to Use the Adapter Pattern in Java
Use the Adapter pattern in Java when
- You want to use an existing class, and its interface does not match the one you need
- You want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces
- You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing everyone. An object adapter can adapt the interface of its parent class.
- Most of the applications using third-party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code.
Adapter Pattern Java Tutorials
- Using the Adapter Design Pattern in Java (Dzone)
- Adapter in Java (Refactoring Guru)
- The Adapter Pattern in Java (Baeldung)
- Adapter Design Pattern (GeeksForGeeks)
Benefits and Trade-offs of Adapter Pattern
Class and object adapters offer different benefits and drawbacks. A class adapter adapts the Adaptee to the Target by binding to a specific Adaptee class, which means it cannot adapt a class and all its subclasses. This type of adapter allows the Adapter to override some of the Adapteeโs behavior because the Adapter is a subclass of the Adaptee. Additionally, it introduces only one object without needing extra pointer indirection to reach the Adaptee.
On the other hand, an object adapter allows a single Adapter to work with multiple Adaptees, including the Adaptee and all its subclasses. This type of adapter can add functionality to all Adaptees simultaneously. However, it makes overriding the Adapteeโs behavior more difficult, as it requires subclassing the Adaptee and having the Adapter refer to this subclass instead of the Adaptee itself.
Real-World Applications of Adapter Pattern in Java
java.io.InputStreamReaderandjava.io.OutputStreamWriterin the Java IO library.- GUI component libraries that allow for plug-ins or adapters to convert between different GUI component interfaces.
- java.util.Arrays#asList()
- java.util.Collections#list()
- java.util.Collections#enumeration()
- javax.xml.bind.annotation.adapters.XMLAdapter
References and Credits
- Design Patterns: Elements of Reusable Object-Oriented Software
- Effective Java
- Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software
- J2EE Design Patterns
- Refactoring to Patterns
File: ambassador/README.md
title: "Ambassador Pattern in Java: Simplifying Remote Resource Management"
shortTitle: Ambassador
category: Integration
language: en
tag:
- API design
- Decoupling
- Fault tolerance
- Proxy
- Resilience
- Scalability
Intent of Ambassador Design Pattern
The Ambassador Pattern in Java helps offload common functionalities such as monitoring, logging, and routing from a shared resource to a helper service instance, enhancing performance and maintainability in distributed systems.
Detailed Explanation of Ambassador Pattern with Real-World Examples
Real-world example
Imagine a busy hotel where guests frequently request restaurant reservations, event tickets, or transportation arrangements. Instead of each guest individually contacting these services, the hotel provides a concierge. The concierge handles these tasks on behalf of the guests, ensuring that reservations are made smoothly, tickets are booked on time, and transportation is scheduled efficiently.
In this analogy, the guests are the client services, the external providers (restaurants, ticket vendors, transportation) are the remote services, and the concierge represents the ambassador service. This setup allows the guests to focus on enjoying their stay while the concierge manages the complexities of external interactions, providing a seamless and enhanced experience.
In plain words
With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging.
Microsoft documentation states
An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features.
Sequence diagram
Programmatic Example of Ambassador Pattern in Java
In this example of the Ambassador Pattern in Java, we demonstrate how to implement latency checks, logging, and retry mechanisms to improve system reliability.
A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging.
With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented by the remote service as well as the ambassador service.
interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception;
}A remote services represented as a singleton.
@Slf4j
public class RemoteService implements RemoteServiceInterface {
private static RemoteService service = null;
static synchronized RemoteService getRemoteService() {
if (service == null) {
service = new RemoteService();
}
return service;
}
private RemoteService() {
}
@Override
public long doRemoteFunction(int value) {
long waitTime = (long) Math.floor(Math.random() * 1000);
try {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep interrupted", e);
}
return waitTime >= 200 ? value * 10 : -1;
}
}A service ambassador adds additional features such as logging and latency checks.
@Slf4j
public class ServiceAmbassador implements RemoteServiceInterface {
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
ServiceAmbassador() {
}
@Override
public long doRemoteFunction(int value) {
return safeCall(value);
}
private long checkLatency(int value) {
var startTime = System.currentTimeMillis();
var result = RemoteService.getRemoteService().doRemoteFunction(value);
var timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
private long safeCall(int value) {
var retries = 0;
var result = (long) FAILURE;
for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) {
return FAILURE;
}
if ((result = checkLatency(value)) == FAILURE) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
} else {
break;
}
}
return result;
}
}A client has a local service ambassador used to interact with the remote service.
@Slf4j
public class Client {
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
long useService(int value) {
var result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: " + result);
return result;
}
}Here are two clients using the service.
public class App {
public static void main(String[] args) {
var host1 = new Client();
var host2 = new Client();
host1.useService(12);
host2.useService(73);
}
}Here's the output for running the example:
Time taken(ms):111
Service result:120
Time taken(ms):931
Failed to reach remote:(1)
Time taken(ms):665
Failed to reach remote:(2)
Time taken(ms):538
Failed to reach remote:(3)
Service result:-1When to Use the Ambassador Pattern in Java
- The Ambassador Pattern is particularly beneficial for Cloud Native and Microservices Architectures in Java. It helps in monitoring, logging, and securing inter-service communication, making it ideal for distributed systems.
- Legacy System Integration: Facilitates communication with newer services by handling necessary but non-core functionalities.
- Performance Enhancement: Can be used to cache results or compress data to improve communication efficiency.
Typical use cases include:
- Control access to another object
- Implement logging
- Implement circuit breaking
- Offload remote service tasks
- Facilitate network connection
Benefits and Trade-offs of Ambassador Pattern
Benefits:
- Separation of Concerns: Offloads cross-cutting concerns from the service logic, leading to cleaner, more maintainable code.
- Reusable Infrastructure Logic: The ambassador pattern allows the same logic (e.g., logging, monitoring) to be reused across multiple services.
- Improved Security: Centralizes security features like SSL termination or authentication, reducing the risk of misconfiguration.
- Flexibility: Makes it easier to update or replace infrastructure concerns without modifying the service code.
Trade-offs:
- Increased Complexity: Adds another layer to the architecture, which can complicate the system design and debugging.
- Potential Performance Overhead: The additional network hop can introduce latency and overhead, particularly if not optimized.
- Deployment Overhead: Requires additional resources and management for deploying and scaling ambassador services.
Real-World Applications of Ambassador Pattern in Java
- Service Mesh Implementations: In a service mesh architecture, like Istio or Linkerd, the Ambassador pattern is often employed as a sidecar proxy that handles inter-service communications. This includes tasks such as service discovery, routing, load balancing, telemetry (metrics and tracing), and security (authentication and authorization).
- API Gateways: API gateways can use the Ambassador pattern to encapsulate common functionalities like rate limiting, caching, request shaping, and authentication. This allows backend services to focus on their core business logic without being burdened by these cross-cutting concerns.
- Logging and Monitoring: An Ambassador can aggregate logs and metrics from various services and forward them to centralized monitoring tools like Prometheus or ELK Stack (Elasticsearch, Logstash, Kibana). This simplifies the logging and monitoring setup for each service and provides a unified view of the system's health.
- Security: Security-related functionalities such as SSL/TLS termination, identity verification, and encryption can be managed by an Ambassador. This ensures consistent security practices across services and reduces the likelihood of security breaches due to misconfigurations.
- Resilience: The Ambassador can implement resilience patterns like circuit breakers, retries, and timeouts. For instance, Netflix's Hystrix library can be used within an Ambassador to prevent cascading failures in a microservices ecosystem.
- Database Proxy: Ambassadors can act as proxies for database connections, providing functionalities like connection pooling, read/write splitting for replicas, and query caching. This offloads significant complexity from the application services.
- Legacy System Integration: In scenarios where modern microservices need to communicate with legacy systems, an Ambassador can serve as an intermediary that translates protocols, handles necessary transformations, and implements modern security practices, easing the integration process.
- Network Optimization: For services deployed across different geographical locations or cloud regions, Ambassadors can optimize communication by compressing data, batching requests, or even implementing smart routing to reduce latency and costs.
- Kubernetes-native API gateway for microservices
Related Java Design Patterns
- Circuit Breaker: Often used in conjunction to manage fault tolerance by stopping calls to an unresponsive service.
- Decorator: The decorator pattern is used to add functionality to an object dynamically, while the ambassador pattern is used to offload functionality to a separate object.
- Proxy: Shares similarities with the proxy pattern, but the ambassador pattern specifically focuses on offloading ancillary functionalities.
- Sidecar: A similar pattern used in the context of containerized applications, where a sidecar container provides additional functionality to the main application container.
References and Credits
- Building Microservices: Designing Fine-Grained Systems
- Cloud Native Patterns: Designing Change-tolerant Software
- Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services
- Microservices Patterns: With examples in Java
- Ambassador pattern (Microsoft)
File: anti-corruption-layer/README.md
title: "Anti-Corruption Layer Pattern in Java: Ensuring System Integrity Amidst Legacy Systems"
shortTitle: Anti-Corruption Layer
category: Integration
language: en
tag:
- Architecture
- Decoupling
- Integration
- Isolation
- Layered architecture
- Migration
- Modernization
- Refactoring
- Wrapping
Also known as
- ACL
- Interface layer
- Translation layer
Intent of Anti-Corruption Layer Design Pattern
The Anti-Corruption Layer (ACL) is a crucial design pattern in Java development, particularly for system integration and maintaining data integrity. Implement a faรงade or adapter layer between different subsystems that don't share the same semantics. It translates between different data formats and systems, ensuring that the integration between systems does not lead to corruption of business logic or data integrity.
Detailed Explanation of Anti-Corruption Layer Pattern with Real-World Examples
Real-world example
This example demonstrates how the Anti-Corruption Layer ensures seamless integration between legacy systems and modern platforms, crucial for maintaining business logic integrity during system migration.
Imagine a large retail company transitioning its inventory management system from an old legacy software to a new modern platform. The legacy system has been in use for decades and contains complex business rules and data formats that are incompatible with the new system. Instead of directly connecting the new system to the legacy one, the company implements an Anti-Corruption Layer (ACL).
The ACL acts as a mediator, translating and adapting data between the two systems. When the new system requests inventory data, the ACL translates the request into a format the legacy system understands, retrieves the data, and then translates it back into a format suitable for the new system. This approach ensures that the new system remains unaffected by the intricacies of the legacy system, preventing corruption of data and business logic while facilitating a smooth transition.
In plain words
The Anti-Corruption Layer design pattern protects a system from the complexities and changes of external systems by providing an intermediary translation layer.
Microsoft's documentation says
Implement a faรงade or adapter layer between different subsystems that don't share the same semantics. This layer translates requests that one subsystem makes to the other subsystem. Use this pattern to ensure that an application's design is not limited by dependencies on outside subsystems. This pattern was first described by Eric Evans in Domain-Driven Design.
Sequence diagram
Programmatic Example of Anti-Corruption Layer Pattern in Java
The ACL design pattern in Java provides an intermediary layer that translates data formats, ensuring that integration between different systems does not lead to data corruption.
Here are 2 shop-ordering systems: Legacy and Modern.
The aforementioned systems have different domain models and have to operate simultaneously. Since they work independently the orders can come either from the Legacy or Modern system. Therefore, the system that receives the legacyOrder needs to check if the legacyOrder is valid and not present in the other system. Then it can place the legacyOrder in its own system.
But for that, the system needs to know the domain model of the other system and to avoid that, the anti-corruption layer(ACL) is introduced. The ACL is a layer that translates the domain model of the Legacy system to the domain model of the Modern system and vice versa. Also, it hides all other operations with the other system, uncoupling the systems.
Domain model of the Legacy system:
public class LegacyOrder {
private String id;
private String customer;
private String item;
private String qty;
private String price;
}Domain model of the Modern system:
public class ModernOrder {
private String id;
private Customer customer;
private Shipment shipment;
private String extra;
}
public class Customer {
private String address;
}
public class Shipment {
private String item;
private String qty;
private String price;
}Anti-corruption layer:
public class AntiCorruptionLayer {
@Autowired
private ModernShop modernShop;
@Autowired
private LegacyShop legacyShop;
public Optional<LegacyOrder> findOrderInModernSystem(String id) {
return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/);
}
public Optional<ModernOrder> findOrderInLegacySystem(String id) {
return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/);
}
}The connection between the systems. Wherever the Legacy or Modern system needs to communicate with the counterpart the ACL needs to be used to avoid corrupting the current domain model. The example below shows how the Legacy system places an order with a validation from the Modern system.
public class LegacyShop {
@Autowired
private AntiCorruptionLayer acl;
public void placeOrder(LegacyOrder legacyOrder) throws ShopException {
String id = legacyOrder.getId();
Optional<ModernOrder> orderInModernSystem = acl.findOrderInModernSystem(id);
if (orderInModernSystem.isPresent()) {
// order is already in the modern system
} else {
// place order in the current system
}
}
}When to Use the Anti-Corruption Layer Pattern in Java
Use this pattern when:
- A migration is planned to happen over multiple stages, but integration between new and legacy systems needs to be maintained
- Two or more subsystems have different semantics, but still need to communicate
- When integrating with legacy systems or external systems where direct integration might pollute the domain model of the new system
- In scenarios where different subsystems within a larger system use different data formats or structures
- When there is a need to ensure loose coupling between different subsystems or external services to facilitate easier maintenance and scalability
Anti-Corruption Layer Pattern Java Tutorials
Real-World Applications of Anti-Corruption Layer Pattern in Java
- Microservices architectures where individual services must communicate without being tightly coupled to each otherโs data schemas
- Enterprise systems integration, especially when integrating modern systems with legacy systems
- In bounded contexts within Domain-Driven Design (DDD) to maintain the integrity of a domain model when interacting with external systems or subsystems
Benefits and Trade-offs of Anti-Corruption Layer Pattern
Benefits:
- Protects the integrity of the domain model by providing a clear boundary
- Promotes loose coupling between systems, making the system more resilient to changes in external systems
- Facilitates cleaner and more maintainable code by isolating integration code from business logic
Trade-offs:
- Introduces additional complexity and potential performance overhead due to the translation process
- Requires extra effort in design and implementation to ensure the layer is effective without becoming a bottleneck
- Can lead to duplication of models if not carefully managed
Related Java Design Patterns
- Adapter: The Anti-Corruption Layer can be implemented using the Adapter pattern to translate between different data formats or structures
- Facade: The Anti-Corruption Layer can be seen as a specialized form of the Facade pattern that is used to isolate different subsystems
- Gateway: The Anti-Corruption Layer can be used as a Gateway to external systems to provide a unified interface
References and Credits
- Domain-Driven Design: Tackling Complexity in the Heart of Software
- Implementing Domain-Driven Design
- Patterns of Enterprise Application Architecture
File: arrange-act-assert/README.md
title: "Arrange/Act/Assert Pattern in Java: Enhance Testing Clarity and Simplicity"
shortTitle: Arrange/Act/Assert
category: Testing
language: en
tag:
- Code simplification
- Isolation
- Testing
Also known as
- Given/When/Then
Intent of Arrange/Act/Assert Design Pattern
The Arrange/Act/Assert pattern is essential in unit testing in Java. This testing method structures unit tests clearly by dividing them into three distinct sections: setup (Arrange), execution (Act), and verification (Assert).
Detailed Explanation of Arrange/Act/Assert Pattern with Real-World Examples
Real-world example
Imagine you are organizing a small event. To ensure everything runs smoothly, you follow a pattern similar to Arrange/Act/Assert:
- Arrange: You set up the venue, prepare the guest list, arrange seating, and organize the catering.
- Act: You conduct the event according to the plan, welcoming guests, serving food, and following the schedule.
- Assert: After the event, you evaluate its success by checking guest feedback, ensuring all tasks were completed, and reviewing if everything went as planned.
This clear separation of preparation, execution, and evaluation helps ensure the event is well-organized and successful, mirroring the structured approach of the Arrange/Act/Assert pattern in software testing.
In plain words
Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy maintenance.
WikiWikiWeb says
Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods.
Flowchart
Programmatic Example of Arrange/Act/Assert Pattern in Java
We need to write comprehensive and clear unit test suite for a class. Using the Arrange/Act/Assert pattern in Java testing ensures clarity.
Let's first introduce our Cash class to be unit tested.
public class Cash {
private int amount;
Cash(int amount) {
this.amount = amount;
}
void plus(int addend) {
amount += addend;
}
boolean minus(int subtrahend) {
if (amount >= subtrahend) {
amount -= subtrahend;
return true;
} else {
return false;
}
}
int count() {
return amount;
}
}Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly separated steps for each unit test.
class CashAAATest {
@Test
void testPlus() {
//Arrange
var cash = new Cash(3);
//Act
cash.plus(4);
//Assert
assertEquals(7, cash.count());
}
@Test
void testMinus() {
//Arrange
var cash = new Cash(8);
//Act
var result = cash.minus(5);
//Assert
assertTrue(result);
assertEquals(3, cash.count());
}
@Test
void testInsufficientMinus() {
//Arrange
var cash = new Cash(1);
//Act
var result = cash.minus(6);
//Assert
assertFalse(result);
assertEquals(1, cash.count());
}
@Test
void testUpdate() {
//Arrange
var cash = new Cash(5);
//Act
cash.plus(6);
var result = cash.minus(3);
//Assert
assertTrue(result);
assertEquals(8, cash.count());
}
}When to Use the Arrange/Act/Assert Pattern in Java
Use Arrange/Act/Assert pattern when
- Unit testing, especially within the context of TDD and BDD
- Anywhere clarity and structure are needed in test cases
Real-World Applications of Arrange/Act/Assert Pattern in Java
- This pattern is particularly useful when practicing TDD and/or BDD methodologies in Java.
- Utilized in various programming languages and testing frameworks, such as JUnit (Java), NUnit (.NET), and xUnit frameworks.
Benefits and Trade-offs of Arrange/Act/Assert Pattern
Benefits:
- Improved readability of tests by clearly separating the setup, action, and verification steps.
- Easier maintenance and understanding of tests, as each test is structured in a predictable way.
- Facilitates debugging by isolating test failures to specific phases within the test.
Trade-offs:
- May introduce redundancy in tests, as similar arrangements may be repeated across tests.
- Some complex tests might not fit neatly into this structure, requiring additional context or setup outside these three phases.
Related Java Design Patterns
- Page Object: A pattern for organizing UI tests that can be used in conjunction with Arrange/Act/Assert.
References and Credits
- The Art of Unit Testing: with examples in C#
- Test Driven Development: By Example
- Unit Testing Principles, Practices, and Patterns: Effective testing styles, patterns, and reliable automation for unit testing, mocking, and integration testing with examples in C#
- xUnit Test Patterns: Refactoring Test Code
- Arrange, Act, Assert: What is AAA Testing?
- Bill Wake: 3A โ Arrange, Act, Assert (NCrunch)
- GivenWhenThen (Martin Fowler)
File: async-method-invocation/README.md
title: "Async Method Invocation Pattern in Java: Elevate Performance with Asynchronous Programming"
shortTitle: Async Method Invocation
category: Concurrency
language: en
tag:
- Asynchronous
- Decoupling
- Reactive
- Scalability
- Thread management
Also known as
- Asynchronous Procedure Call
Intent of Async Method Invocation Design Pattern
The Async Method Invocation pattern is designed to enhance concurrency by allowing methods to be called asynchronously. This pattern helps in executing parallel tasks, reducing wait times, and improving system throughput.
Detailed Explanation of Async Method Invocation Pattern with Real-World Examples
Real-world example
Asynchronous method invocation enables non-blocking operations, allowing multiple processes to run concurrently. This pattern is particularly useful in applications requiring high scalability and performance, such as web servers and microservices.
In the context of space rockets, an analogous example of the Async Method Invocation pattern can be seen in the communication between the mission control center and the onboard systems of the rocket. When mission control sends a command to the rocket to adjust its trajectory or perform a system check, they do not wait idly for the rocket to complete the task and report back. Instead, mission control continues to monitor other aspects of the mission and manage different tasks. The rocket executes the command asynchronously and sends a status update or result back to mission control once the operation is complete. This allows mission control to efficiently manage multiple concurrent operations without being blocked by any single task, similar to how asynchronous method invocation works in software systems.
In plain words
Asynchronous method invocation starts task processing and returns immediately before the task is ready. The results of the task processing are returned to the caller later.
Wikipedia says
In multithreaded computer programming, asynchronous method invocation (AMI), also known as asynchronous method calls or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the called code to finish. Instead, the calling thread is notified when the reply arrives. Polling for a reply is an undesired option.
Sequence diagram
Programmatic Example of Async Method Invocation Pattern in Java
Consider a scenario where multiple tasks need to be executed simultaneously. Using the Async Method Invocation pattern, you can initiate these tasks without waiting for each to complete, thus optimizing resource usage and reducing latency.
In this example, we are launching space rockets and deploying lunar rovers.
The application demonstrates the async method invocation pattern. The key parts of the pattern areAsyncResult which is an intermediate container for an asynchronously evaluated value, AsyncCallback which can be provided to be executed on task completion and AsyncExecutor that manages the execution of the async tasks.
public interface AsyncResult<T> {
boolean isCompleted();
T getValue() throws ExecutionException;
void await() throws InterruptedException;
}public interface AsyncCallback<T> {
void onComplete(T value);
void onError(Exception ex);
}public interface AsyncExecutor {
<T> AsyncResult<T> startProcess(Callable<T> task);
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
}ThreadAsyncExecutor is an implementation of AsyncExecutor. Some of its key parts are highlighted next.
public class ThreadAsyncExecutor implements AsyncExecutor {
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback);
new Thread(
() -> {
try {
result.setValue(task.call());
} catch (Exception ex) {
result.setException(ex);
}
},
"executor-" + idx.incrementAndGet())
.start();
return result;
}
@Override
public <T> T endProcess(AsyncResult<T> asyncResult)
throws ExecutionException, InterruptedException {
if (!asyncResult.isCompleted()) {
asyncResult.await();
}
return asyncResult.getValue();
}
}Then we are ready to launch some rockets to see how everything works together.
public static void main(String[] args) throws Exception {
// construct a new executor that will run async tasks
var executor = new ThreadAsyncExecutor();
// start few async tasks with varying processing times, two last with callback handlers
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
final var asyncResult4 = executor.startProcess(lazyval(20, 400),
callback("Deploying lunar rover"));
final var asyncResult5 =
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
// emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy, we are working hard here
log("Mission command is sipping coffee");
// wait for completion of the tasks
final var result1 = executor.endProcess(asyncResult1);
final var result2 = executor.endProcess(asyncResult2);
final var result3 = executor.endProcess(asyncResult3);
asyncResult4.await();
asyncResult5.await();
// log the results of the tasks, callbacks log immediately when complete
log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result1));
log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result2));
log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result3));
}Here's the program console output.
21:47:08.227[executor-2]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launched successfully
21:47:08.269[main]INFO com.iluwatar.async.method.invocation.App-Mission command is sipping coffee
21:47:08.318[executor-4]INFO com.iluwatar.async.method.invocation.App-Space rocket<20>launched successfully
21:47:08.335[executor-4]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<20>
21:47:08.414[executor-1]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Space rocket<callback> launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<callback>
21:47:08.616[executor-3]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launched successfully
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launch complete
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launch complete
21:47:08.618[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launch completeWhen to Use the Async Method Invocation Pattern in Java
This pattern is ideal for applications needing to manage multiple parallel tasks efficiently. It is commonly used in scenarios such as handling background processes, improving user interface responsiveness, and managing asynchronous data processing.
Use the async method invocation pattern when
- When operations do not need to complete before proceeding with the next steps in a program.
- For tasks that are resource-intensive or time-consuming, such as IO operations, network requests, or complex computations, where making the operation synchronous would significantly impact performance or user experience.
- In GUI applications to prevent freezing or unresponsiveness during long-running tasks.
- In web applications for non-blocking IO operations.
Real-World Applications of Async Method Invocation Pattern in Java
Many modern applications leverage the Async Method Invocation pattern, including web servers handling concurrent requests, microservices architectures, and systems requiring intensive background processing.
- Web servers handling HTTP requests asynchronously to improve throughput and reduce latency.
- Desktop and mobile applications using background threads to perform time-consuming operations without blocking the user interface.
- Microservices architectures where services perform asynchronous communications via messaging queues or event streams.
- FutureTask
- CompletableFuture
- ExecutorService
- Task-based Asynchronous Pattern
Benefits and Trade-offs of Async Method Invocation Pattern
While this pattern offers significant performance benefits, it also introduces complexity in error handling and resource management. Proper implementation is essential to avoid potential pitfalls such as race conditions and deadlocks.
Benefits:
- Improved Responsiveness: The main thread or application flow remains unblocked, improving the user experience in GUI applications and overall responsiveness.
- Better Resource Utilization: By enabling parallel execution, system resources (like CPU and IO) are utilized more efficiently, potentially improving the application's throughput.
- Scalability: Easier to scale applications, as tasks can be distributed across available resources more effectively.
Trade-offs:
- Complexity: Introducing asynchronous operations can complicate the codebase, making it more challenging to understand, debug, and maintain.
- Resource Management: Requires careful management of threads or execution contexts, which can introduce overhead and potential resource exhaustion issues.
- Error Handling: Asynchronous operations can make error handling more complex, as exceptions may occur in different threads or at different times.
Related Java Design Patterns
The Async Method Invocation pattern often works well with other design patterns like the Command Pattern for encapsulating requests, the Observer Pattern for event handling, and the Promise Pattern for managing asynchronous results.
- Command: Asynchronous method invocation can be used to implement the Command pattern, where commands are executed asynchronously.
- Observer: Asynchronous method invocation can be used to notify observers asynchronously when a subject's state changes.
- Promise: The AsyncResult interface can be considered a form of Promise, representing a value that may not be available yet.