Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This project is intentionally optimized for learning-by-reading and learning-by-
Phase tracking:
- [Phase 1 — Production-ready reference baseline](docs/phase-1-production-readiness.md)
- [Phase 2 — Expand from GoF to enterprise patterns](docs/phase-2-enterprise-patterns.md)
- [Phase 3 — Modern Java implementation variants](docs/phase-3-modern-java-variants.md)

---

Expand Down Expand Up @@ -175,7 +176,8 @@ src/
│ ├── event_sourcing_pattern/
│ ├── fluent_api_pattern/
│ ├── immutable_pattern/
│ └── reactive/
│ ├── reactive/
│ └── modern_java/
└── test/java/example/
└── *Test.java
```
Expand Down
42 changes: 42 additions & 0 deletions docs/phase-3-modern-java-variants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Phase 3 — Modern Java implementation variants

This phase adds side-by-side examples to show how the same pattern can be implemented in three styles:

1. Classic OO implementation
2. Functional/lambda-first implementation
3. Java 21+ friendly implementation (records, sealed interfaces, switch pattern matching, virtual threads)

## Classic vs Modern Java examples

Package: `org.example.modern_java`

### Strategy
- `StrategyVariants.ClassicCalculator`: interface-based strategy injection.
- `StrategyVariants.FunctionalCalculator`: `IntBinaryOperator` strategy via lambda.
- `StrategyVariants.ModernCalculator`: sealed operation hierarchy (`Add`, `Multiply`) with pattern matching switch.

### Observer
- `ObserverVariants.ClassicSubject`: concrete observer interface list.
- `ObserverVariants.FunctionalSubject`: `Consumer<String>` subscribers.
- `ObserverVariants.ModernPublisher`: `Flow.Publisher` via `SubmissionPublisher`.

### Command
- `CommandVariants.ClassicInvoker`: command objects implementing `execute`.
- `CommandVariants.FunctionalInvoker`: queued `Supplier<String>` commands.
- `CommandVariants.ModernInvoker`: sealed command records and switch dispatch.

## Concurrency patterns pack

Implemented in `ConcurrencyPatternsPack`:

1. **Producer-Consumer with virtual threads**
- `producerConsumerWithVirtualThreads(int itemCount)` uses `Executors.newVirtualThreadPerTaskExecutor()`.
2. **Bounded queue/backpressure**
- `BoundedBackpressureQueue<T>` wraps `ArrayBlockingQueue` and provides timed `tryPublish`.
3. **Thread confinement / immutable handoff**
- `ThreadConfinedMailbox<T>` runs consumption on a dedicated single thread.
- `ImmutableMessage` record models immutable handoff data.

## Validation

- `ModernJavaVariantsTest` validates all new variants end-to-end.
56 changes: 56 additions & 0 deletions src/main/java/org/example/modern_java/CommandVariants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.example.modern_java;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

public final class CommandVariants {
private CommandVariants() {
}

public interface Command {
String execute();
}

public static final class ClassicInvoker {
private final List<Command> queue = new ArrayList<>();

public void enqueue(final Command command) {
queue.add(command);
}

public List<String> runAll() {
return queue.stream().map(Command::execute).toList();
}
}

public static final class FunctionalInvoker {
private final List<Supplier<String>> queue = new ArrayList<>();

public void enqueue(final Supplier<String> command) {
queue.add(command);
}

public List<String> runAll() {
return queue.stream().map(Supplier::get).toList();
}
}

public sealed interface AppCommand permits CreateUser, SuspendUser {
}

public record CreateUser(String userId) implements AppCommand {
}

public record SuspendUser(String userId) implements AppCommand {
}

public static final class ModernInvoker {
public String run(final AppCommand command) {
return switch (command) {
case CreateUser createUser -> "created:" + createUser.userId();
case SuspendUser suspendUser -> "suspended:" + suspendUser.userId();
};
}
}
}
108 changes: 108 additions & 0 deletions src/main/java/org/example/modern_java/ConcurrencyPatternsPack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.example.modern_java;

import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

public final class ConcurrencyPatternsPack {
private ConcurrencyPatternsPack() {
}

public static List<Integer> producerConsumerWithVirtualThreads(final int itemCount) {
final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
final AtomicInteger sum = new AtomicInteger();

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
final Future<Void> producer = executor.submit(() -> {
for (int i = 1; i <= itemCount; i++) {
queue.put(i);
}
queue.put(0);
return null;
});

final Future<Integer> consumer = executor.submit(() -> {
int consumed = 0;
while (true) {
final int value = queue.take();
if (value == 0) {
break;
}
consumed++;
sum.addAndGet(value);
}
return consumed;
});

producer.get();
return List.of(consumer.get(), sum.get());
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while processing queue", interruptedException);
} catch (ExecutionException executionException) {
throw new IllegalStateException("Producer-consumer execution failed", executionException);
}
}

public static final class BoundedBackpressureQueue<T> {
private final ArrayBlockingQueue<T> queue;

public BoundedBackpressureQueue(final int capacity) {
this.queue = new ArrayBlockingQueue<>(capacity);
}

public boolean tryPublish(final T value, final Duration timeout) throws InterruptedException {
Objects.requireNonNull(timeout, "timeout must not be null");
return queue.offer(value, timeout.toMillis(), TimeUnit.MILLISECONDS);
}

public T take() throws InterruptedException {
return queue.take();
}

public int size() {
return queue.size();
}
}

public static final class ThreadConfinedMailbox<T> implements AutoCloseable {
private final BlockingQueue<T> queue = new LinkedBlockingQueue<>();
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final Future<?> worker;

public ThreadConfinedMailbox(final Consumer<T> consumer) {
final Callable<Void> task = () -> {
while (!Thread.currentThread().isInterrupted()) {
final T item = queue.take();
consumer.accept(item);
}
return null;
};
this.worker = executorService.submit(task);
}

public void submitImmutable(final T value) throws InterruptedException {
queue.put(value);
}

@Override
public void close() {
worker.cancel(true);
executorService.shutdownNow();
}
}

public record ImmutableMessage(String topic, String payload) {
}
}
57 changes: 57 additions & 0 deletions src/main/java/org/example/modern_java/ObserverVariants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.example.modern_java;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
import java.util.function.Consumer;

public final class ObserverVariants {
private ObserverVariants() {
}

public interface Observer {
void onEvent(String event);
}

public static final class ClassicSubject {
private final List<Observer> observers = new CopyOnWriteArrayList<>();

public void subscribe(final Observer observer) {
observers.add(observer);
}

public void publish(final String event) {
observers.forEach(observer -> observer.onEvent(event));
}
}

public static final class FunctionalSubject {
private final List<Consumer<String>> observers = new CopyOnWriteArrayList<>();

public void subscribe(final Consumer<String> observer) {
observers.add(observer);
}

public void publish(final String event) {
observers.forEach(observer -> observer.accept(event));
}
}

public static final class ModernPublisher implements AutoCloseable {
private final SubmissionPublisher<String> publisher = new SubmissionPublisher<>();

public void subscribe(final Flow.Subscriber<String> subscriber) {
publisher.subscribe(subscriber);
}

public void publish(final String event) {
publisher.submit(event);
}

@Override
public void close() {
publisher.close();
}
}
}
61 changes: 61 additions & 0 deletions src/main/java/org/example/modern_java/StrategyVariants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.example.modern_java;

import java.util.Map;
import java.util.function.IntBinaryOperator;

public final class StrategyVariants {
private StrategyVariants() {
}

public interface Strategy {
int apply(int left, int right);
}

public static final class ClassicCalculator {
private final Strategy strategy;

public ClassicCalculator(final Strategy strategy) {
this.strategy = strategy;
}

public int calculate(final int left, final int right) {
return strategy.apply(left, right);
}
}

public static final class FunctionalCalculator {
private final IntBinaryOperator operation;

public FunctionalCalculator(final IntBinaryOperator operation) {
this.operation = operation;
}

public int calculate(final int left, final int right) {
return operation.applyAsInt(left, right);
}
}

public sealed interface Operation permits Add, Multiply {
}

public record Add(int left, int right) implements Operation {
}

public record Multiply(int left, int right) implements Operation {
}

public static final class ModernCalculator {
private final Map<Class<? extends Operation>, IntBinaryOperator> registry = Map.of(
Add.class, Integer::sum,
Multiply.class, (left, right) -> left * right
);

public int calculate(final Operation operation) {
return switch (operation) {
case Add add -> registry.get(Add.class).applyAsInt(add.left(), add.right());
case Multiply multiply -> registry.get(Multiply.class)
.applyAsInt(multiply.left(), multiply.right());
};
}
}
}
Loading
Loading