Compare commits
1 Commits
multitonUp
...
refactorUn
Author | SHA1 | Date | |
---|---|---|---|
2823166c91 |
@ -9,10 +9,6 @@ tags:
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## Name
|
||||
|
||||
Event Aggregator
|
||||
|
||||
## Intent
|
||||
A system with lots of objects can lead to complexities when a
|
||||
client wants to subscribe to events. The client has to find and register for
|
||||
@ -21,136 +17,6 @@ requires a separate subscription. An Event Aggregator acts as a single source
|
||||
of events for many objects. It registers for all the events of the many objects
|
||||
allowing clients to register with just the aggregator.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most
|
||||
> of his critical information from King's Hand, the second in command. King's hand has many
|
||||
> close advisors himself, feeding him with relevant information about events occurring in the
|
||||
> kingdom.
|
||||
|
||||
In Plain Words
|
||||
|
||||
> Event Aggregator is an event mediator that collects events from multiple sources and delivers
|
||||
> them to registered observers.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of
|
||||
the objects are event listeners, some are event emitters, and the event aggregator does both.
|
||||
|
||||
```java
|
||||
public interface EventObserver {
|
||||
void onEvent(Event e);
|
||||
}
|
||||
|
||||
public abstract class EventEmitter {
|
||||
|
||||
private final Map<Event, List<EventObserver>> observerLists;
|
||||
|
||||
public EventEmitter() {
|
||||
observerLists = new HashMap<>();
|
||||
}
|
||||
|
||||
public final void registerObserver(EventObserver obs, Event e) {
|
||||
...
|
||||
}
|
||||
|
||||
protected void notifyObservers(Event e) {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`KingJoffrey` is listening to events from `KingsHand`.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class KingJoffrey implements EventObserver {
|
||||
@Override
|
||||
public void onEvent(Event e) {
|
||||
LOGGER.info("Received event from the King's Hand: {}", e.toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`.
|
||||
Whatever he hears from them, he delivers to `KingJoffrey`.
|
||||
|
||||
```java
|
||||
public class KingsHand extends EventEmitter implements EventObserver {
|
||||
|
||||
public KingsHand() {
|
||||
}
|
||||
|
||||
public KingsHand(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event e) {
|
||||
notifyObservers(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`.
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class LordVarys extends EventEmitter implements EventObserver {
|
||||
@Override
|
||||
public void timePasses(Weekday day) {
|
||||
if (day == Weekday.SATURDAY) {
|
||||
notifyObservers(Event.TRAITOR_DETECTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following snippet demonstrates how the objects are constructed and wired together.
|
||||
|
||||
```java
|
||||
var kingJoffrey = new KingJoffrey();
|
||||
|
||||
var kingsHand = new KingsHand();
|
||||
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
|
||||
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
|
||||
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
|
||||
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
|
||||
|
||||
var varys = new LordVarys();
|
||||
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
|
||||
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
|
||||
|
||||
var scout = new Scout();
|
||||
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
|
||||
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
|
||||
|
||||
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
|
||||
|
||||
var emitters = List.of(
|
||||
kingsHand,
|
||||
baelish,
|
||||
varys,
|
||||
scout
|
||||
);
|
||||
|
||||
Arrays.stream(Weekday.values())
|
||||
.<Consumer<? super EventEmitter>>map(day -> emitter -> emitter.timePasses(day))
|
||||
.forEachOrdered(emitters::forEach);
|
||||
```
|
||||
|
||||
The console output after running the example.
|
||||
|
||||
```
|
||||
18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching
|
||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted
|
||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted
|
||||
18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected
|
||||
```
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
||||
@ -160,13 +26,9 @@ Use the Event Aggregator pattern when
|
||||
* Event Aggregator is a good choice when you have lots of objects that are
|
||||
potential event sources. Rather than have the observer deal with registering
|
||||
with them all, you can centralize the registration logic to the Event
|
||||
Aggregator. As well as simplifying registration, an Event Aggregator also
|
||||
Aggregator. As well as simplifying registration, a Event Aggregator also
|
||||
simplifies the memory management issues in using observers.
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Observer](https://java-design-patterns.com/patterns/observer/)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)
|
||||
|
@ -17,10 +17,10 @@ the user to specify only what to do with the resource.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> A class needs to be provided for writing text strings to files. To make it easy for
|
||||
> the user, the service class opens and closes the file automatically. The user only has to
|
||||
> We need to provide a class that can be used to write text strings to files. To make it easy for
|
||||
> the user we let our service class open and close the file automatically, the user only has to
|
||||
> specify what is written into which file.
|
||||
|
||||
In plain words
|
||||
@ -35,50 +35,35 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
`SimpleFileWriter` class implements the Execute Around idiom. It takes `FileWriterAction` as a
|
||||
constructor argument allowing the user to specify what gets written into the file.
|
||||
Let's introduce our file writer class.
|
||||
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface FileWriterAction {
|
||||
|
||||
void writeFile(FileWriter writer) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class SimpleFileWriter {
|
||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||
LOGGER.info("Opening the file");
|
||||
try (var writer = new FileWriter(filename)) {
|
||||
LOGGER.info("Executing the action");
|
||||
action.writeFile(writer);
|
||||
LOGGER.info("Closing the file");
|
||||
}
|
||||
|
||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||
try (var writer = new FileWriter(filename)) {
|
||||
action.writeFile(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following code demonstrates how `SimpleFileWriter` is used. `Scanner` is used to print the file
|
||||
contents after the writing finishes.
|
||||
To utilize the file writer the following code is needed.
|
||||
|
||||
```java
|
||||
FileWriterAction writeHello = writer -> {
|
||||
writer.write("Gandalf was here");
|
||||
};
|
||||
new SimpleFileWriter("testfile.txt", writeHello);
|
||||
|
||||
var scanner = new Scanner(new File("testfile.txt"));
|
||||
while (scanner.hasNextLine()) {
|
||||
LOGGER.info(scanner.nextLine());
|
||||
}
|
||||
```
|
||||
|
||||
Here's the console output.
|
||||
|
||||
```
|
||||
21:18:07.185 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Opening the file
|
||||
21:18:07.188 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Executing the action
|
||||
21:18:07.189 [main] INFO com.iluwatar.execute.around.SimpleFileWriter - Closing the file
|
||||
21:18:07.199 [main] INFO com.iluwatar.execute.around.App - Gandalf was here
|
||||
FileWriterAction writeHello = writer -> {
|
||||
writer.write("Hello");
|
||||
writer.append(" ");
|
||||
writer.append("there!");
|
||||
};
|
||||
new SimpleFileWriter("testfile.txt", writeHello);
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -89,7 +74,8 @@ Here's the console output.
|
||||
|
||||
Use the Execute Around idiom when
|
||||
|
||||
* An API requires methods to be called in pairs such as open/close or allocate/deallocate.
|
||||
* You use an API that requires methods to be called in pairs such as open/close or
|
||||
allocate/deallocate.
|
||||
|
||||
## Credits
|
||||
|
||||
|
@ -23,14 +23,10 @@
|
||||
|
||||
package com.iluwatar.execute.around;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The Execute Around idiom specifies executable code before and after a method. Typically
|
||||
* The Execute Around idiom specifies some code to be executed before and after a method. Typically
|
||||
* the idiom is used when the API has methods to be executed in pairs, such as resource
|
||||
* allocation/deallocation or lock acquisition/release.
|
||||
*
|
||||
@ -38,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* the user. The user specifies only what to do with the file by providing the {@link
|
||||
* FileWriterAction} implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
/**
|
||||
@ -46,16 +41,11 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
// create the file writer and execute the custom action
|
||||
FileWriterAction writeHello = writer -> {
|
||||
writer.write("Gandalf was here");
|
||||
writer.write("Hello");
|
||||
writer.append(" ");
|
||||
writer.append("there!");
|
||||
};
|
||||
new SimpleFileWriter("testfile.txt", writeHello);
|
||||
|
||||
// print the file contents
|
||||
var scanner = new Scanner(new File("testfile.txt"));
|
||||
while (scanner.hasNextLine()) {
|
||||
LOGGER.info(scanner.nextLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,24 +26,18 @@ package com.iluwatar.execute.around;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* SimpleFileWriter handles opening and closing file for the user. The user only has to specify what
|
||||
* to do with the file resource through {@link FileWriterAction} parameter.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SimpleFileWriter {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public SimpleFileWriter(String filename, FileWriterAction action) throws IOException {
|
||||
LOGGER.info("Opening the file");
|
||||
try (var writer = new FileWriter(filename)) {
|
||||
LOGGER.info("Executing the action");
|
||||
action.writeFile(writer);
|
||||
LOGGER.info("Closing the file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,115 +10,19 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Define a factory of immutable content with separated builder and factory interfaces.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> Imagine a magical weapon factory that can create any type of weapon wished for. When the factory
|
||||
> is unboxed, the master recites the weapon types needed to prepare it. After that, any of those
|
||||
> weapon types can be summoned in an instant.
|
||||
|
||||
In plain words
|
||||
|
||||
> Factory kit is a configurable object builder.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's first define the simple `Weapon` hierarchy.
|
||||
|
||||
```java
|
||||
public interface Weapon {
|
||||
}
|
||||
|
||||
public enum WeaponType {
|
||||
SWORD,
|
||||
AXE,
|
||||
BOW,
|
||||
SPEAR
|
||||
}
|
||||
|
||||
public class Sword implements Weapon {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Sword";
|
||||
}
|
||||
}
|
||||
|
||||
// Axe, Bow, and Spear are defined similarly
|
||||
```
|
||||
|
||||
Next, we define a functional interface that allows adding a builder with a name to the factory.
|
||||
|
||||
```java
|
||||
public interface Builder {
|
||||
void add(WeaponType name, Supplier<Weapon> supplier);
|
||||
}
|
||||
```
|
||||
|
||||
The meat of the example is the `WeaponFactory` interface that effectively implements the factory
|
||||
kit pattern. The method `#factory` is used to configure the factory with the classes it needs to
|
||||
be able to construct. The method `#create` is then used to create object instances.
|
||||
|
||||
```java
|
||||
public interface WeaponFactory {
|
||||
|
||||
static WeaponFactory factory(Consumer<Builder> consumer) {
|
||||
var map = new HashMap<WeaponType, Supplier<Weapon>>();
|
||||
consumer.accept(map::put);
|
||||
return name -> map.get(name).get();
|
||||
}
|
||||
|
||||
Weapon create(WeaponType name);
|
||||
}
|
||||
```
|
||||
|
||||
Now, we can show how `WeaponFactory` can be used.
|
||||
|
||||
```java
|
||||
var factory = WeaponFactory.factory(builder -> {
|
||||
builder.add(WeaponType.SWORD, Sword::new);
|
||||
builder.add(WeaponType.AXE, Axe::new);
|
||||
builder.add(WeaponType.SPEAR, Spear::new);
|
||||
builder.add(WeaponType.BOW, Bow::new);
|
||||
});
|
||||
var list = new ArrayList<Weapon>();
|
||||
list.add(factory.create(WeaponType.AXE));
|
||||
list.add(factory.create(WeaponType.SPEAR));
|
||||
list.add(factory.create(WeaponType.SWORD));
|
||||
list.add(factory.create(WeaponType.BOW));
|
||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
||||
```
|
||||
|
||||
Here is the console output when the example is run.
|
||||
|
||||
```
|
||||
21:15:49.709 [main] INFO com.iluwatar.factorykit.App - Axe
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Spear
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Sword
|
||||
21:15:49.713 [main] INFO com.iluwatar.factorykit.App - Bow
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Factory Kit pattern when
|
||||
|
||||
* The factory class can't anticipate the types of objects it must create
|
||||
* A new instance of a custom builder is needed instead of a global one
|
||||
* The types of objects that the factory can build need to be defined outside the class
|
||||
* The builder and creator interfaces need to be separated
|
||||
|
||||
## Related patterns
|
||||
|
||||
* [Builder](https://java-design-patterns.com/patterns/builder/)
|
||||
* [Factory](https://java-design-patterns.com/patterns/factory/)
|
||||
* a class can't anticipate the class of objects it must create
|
||||
* you just want a new instance of a custom builder instead of the global one
|
||||
* you explicitly want to define types of objects, that factory can build
|
||||
* you want a separated builder and creator interface
|
||||
|
||||
## Credits
|
||||
|
||||
* [Design Pattern Reloaded by Remi Forax](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)
|
||||
|
@ -23,16 +23,14 @@
|
||||
|
||||
package com.iluwatar.factorykit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Factory kit is a creational pattern that defines a factory of immutable content with separated
|
||||
* Factory-kit is a creational pattern which defines a factory of immutable content with separated
|
||||
* builder and factory interfaces to deal with the problem of creating one of the objects specified
|
||||
* directly in the factory kit instance.
|
||||
* directly in the factory-kit instance.
|
||||
*
|
||||
* <p>In the given example {@link WeaponFactory} represents the factory kit, that contains four
|
||||
* <p>In the given example {@link WeaponFactory} represents the factory-kit, that contains four
|
||||
* {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface.
|
||||
*
|
||||
* <p>Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with
|
||||
@ -54,11 +52,7 @@ public class App {
|
||||
builder.add(WeaponType.SPEAR, Spear::new);
|
||||
builder.add(WeaponType.BOW, Bow::new);
|
||||
});
|
||||
var list = new ArrayList<Weapon>();
|
||||
list.add(factory.create(WeaponType.AXE));
|
||||
list.add(factory.create(WeaponType.SPEAR));
|
||||
list.add(factory.create(WeaponType.SWORD));
|
||||
list.add(factory.create(WeaponType.BOW));
|
||||
list.stream().forEach(weapon -> LOGGER.info("{}", weapon.toString()));
|
||||
var axe = factory.create(WeaponType.AXE);
|
||||
LOGGER.info(axe.toString());
|
||||
}
|
||||
}
|
||||
|
@ -15,18 +15,18 @@ Registry
|
||||
|
||||
## Intent
|
||||
|
||||
Ensure a class only has a limited number of instances and provide a global point of access to them.
|
||||
Ensure a class only has limited number of instances and provide a global point of access to them.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> The Nazgûl, also called ringwraiths or the Nine Riders, are Sauron's most terrible servants. By
|
||||
> definition, there's always nine of them.
|
||||
> definition there's always nine of them.
|
||||
|
||||
In plain words
|
||||
|
||||
> Multiton pattern ensures there are a predefined amount of instances available globally.
|
||||
> Multiton pattern ensures there's predefined amount of instances available globally.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
@ -81,54 +81,29 @@ public final class Nazgul {
|
||||
And here's how we access the `Nazgul` instances.
|
||||
|
||||
```java
|
||||
// eagerly initialized multiton
|
||||
LOGGER.info("Printing out eagerly initialized multiton contents");
|
||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||
LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR));
|
||||
LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL));
|
||||
LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH));
|
||||
LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL));
|
||||
LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN));
|
||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||
|
||||
// enum multiton
|
||||
LOGGER.info("Printing out enum-based multiton contents");
|
||||
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
||||
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
||||
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
||||
LOGGER.info("JI_INDUR={}", NazgulEnum.JI_INDUR);
|
||||
LOGGER.info("AKHORAHIL={}", NazgulEnum.AKHORAHIL);
|
||||
LOGGER.info("HOARMURATH={}", NazgulEnum.HOARMURATH);
|
||||
LOGGER.info("ADUNAPHEL={}", NazgulEnum.ADUNAPHEL);
|
||||
LOGGER.info("REN={}", NazgulEnum.REN);
|
||||
LOGGER.info("UVATHA={}", NazgulEnum.UVATHA);
|
||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||
LOGGER.info("JI_INDUR={}", Nazgul.getInstance(NazgulName.JI_INDUR));
|
||||
LOGGER.info("AKHORAHIL={}", Nazgul.getInstance(NazgulName.AKHORAHIL));
|
||||
LOGGER.info("HOARMURATH={}", Nazgul.getInstance(NazgulName.HOARMURATH));
|
||||
LOGGER.info("ADUNAPHEL={}", Nazgul.getInstance(NazgulName.ADUNAPHEL));
|
||||
LOGGER.info("REN={}", Nazgul.getInstance(NazgulName.REN));
|
||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
20:35:07.413 [main] INFO com.iluwatar.multiton.App - Printing out eagerly initialized multiton contents
|
||||
20:35:07.417 [main] INFO com.iluwatar.multiton.App - KHAMUL=com.iluwatar.multiton.Nazgul@48cf768c
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - MURAZOR=com.iluwatar.multiton.Nazgul@7960847b
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - DWAR=com.iluwatar.multiton.Nazgul@6a6824be
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - JI_INDUR=com.iluwatar.multiton.Nazgul@5c8da962
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=com.iluwatar.multiton.Nazgul@512ddf17
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - HOARMURATH=com.iluwatar.multiton.Nazgul@2c13da15
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=com.iluwatar.multiton.Nazgul@77556fd
|
||||
20:35:07.419 [main] INFO com.iluwatar.multiton.App - REN=com.iluwatar.multiton.Nazgul@368239c8
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - UVATHA=com.iluwatar.multiton.Nazgul@9e89d68
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - Printing out enum-based multiton contents
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - KHAMUL=KHAMUL
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - MURAZOR=MURAZOR
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - DWAR=DWAR
|
||||
20:35:07.420 [main] INFO com.iluwatar.multiton.App - JI_INDUR=JI_INDUR
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - AKHORAHIL=AKHORAHIL
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - HOARMURATH=HOARMURATH
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - ADUNAPHEL=ADUNAPHEL
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - REN=REN
|
||||
20:35:07.421 [main] INFO com.iluwatar.multiton.App - UVATHA=UVATHA
|
||||
KHAMUL=com.iluwatar.multiton.Nazgul@2b214b94
|
||||
MURAZOR=com.iluwatar.multiton.Nazgul@17814b1c
|
||||
DWAR=com.iluwatar.multiton.Nazgul@7ac9af2a
|
||||
JI_INDUR=com.iluwatar.multiton.Nazgul@7bb004b8
|
||||
AKHORAHIL=com.iluwatar.multiton.Nazgul@78e89bfe
|
||||
HOARMURATH=com.iluwatar.multiton.Nazgul@652ce654
|
||||
ADUNAPHEL=com.iluwatar.multiton.Nazgul@522ba524
|
||||
REN=com.iluwatar.multiton.Nazgul@29c5ee1d
|
||||
UVATHA=com.iluwatar.multiton.Nazgul@15cea7b0
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -139,5 +114,5 @@ Program output:
|
||||
|
||||
Use the Multiton pattern when
|
||||
|
||||
* There must be a specific number of instances of a class, and they must be accessible to clients from
|
||||
* There must be specific number of instances of a class, and they must be accessible to clients from
|
||||
a well-known access point.
|
||||
|
@ -26,13 +26,13 @@ package com.iluwatar.multiton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Whereas Singleton design pattern introduces single globally accessible object, the Multiton
|
||||
* Whereas Singleton design pattern introduces single globally accessible object the Multiton
|
||||
* pattern defines many globally accessible objects. The client asks for the correct instance from
|
||||
* the Multiton by passing an enumeration as a parameter.
|
||||
* the Multiton by passing an enumeration as parameter.
|
||||
*
|
||||
* <p>There is more than one way to implement the multiton design pattern. In the first example
|
||||
* {@link Nazgul} is the Multiton and we can ask single {@link Nazgul} from it using {@link
|
||||
* NazgulName}. The {@link Nazgul}s are statically initialized and stored in a concurrent hash map.
|
||||
* NazgulName}. The {@link Nazgul}s are statically initialized and stored in concurrent hash map.
|
||||
*
|
||||
* <p>In the enum implementation {@link NazgulEnum} is the multiton. It is static and mutable
|
||||
* because of the way java supports enums.
|
||||
@ -47,7 +47,6 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// eagerly initialized multiton
|
||||
LOGGER.info("Printing out eagerly initialized multiton contents");
|
||||
LOGGER.info("KHAMUL={}", Nazgul.getInstance(NazgulName.KHAMUL));
|
||||
LOGGER.info("MURAZOR={}", Nazgul.getInstance(NazgulName.MURAZOR));
|
||||
LOGGER.info("DWAR={}", Nazgul.getInstance(NazgulName.DWAR));
|
||||
@ -59,7 +58,6 @@ public class App {
|
||||
LOGGER.info("UVATHA={}", Nazgul.getInstance(NazgulName.UVATHA));
|
||||
|
||||
// enum multiton
|
||||
LOGGER.info("Printing out enum-based multiton contents");
|
||||
LOGGER.info("KHAMUL={}", NazgulEnum.KHAMUL);
|
||||
LOGGER.info("MURAZOR={}", NazgulEnum.MURAZOR);
|
||||
LOGGER.info("DWAR={}", NazgulEnum.DWAR);
|
||||
|
@ -48,4 +48,5 @@ public class NazgulTest {
|
||||
assertEquals(name, nazgul.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
|
||||
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
|
||||
> we establish a thread pool.
|
||||
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||
> establish a thread pool.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
|
||||
}
|
||||
```
|
||||
|
||||
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
|
||||
peeling and coffee making.
|
||||
|
||||
```java
|
||||
|
@ -16,12 +16,10 @@ Ensure that a given client is not able to access service resources more than the
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
||||
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
||||
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
||||
> be higher.
|
||||
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||
> customer can only make certain amount of calls per second.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -35,25 +33,30 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
||||
calls per `BarCustomer`.
|
||||
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
|
||||
|
||||
```java
|
||||
public class BarCustomer {
|
||||
public class Tenant {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
private final String name;
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@ -73,14 +76,14 @@ public final class CallsCount {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
||||
timer is used.
|
||||
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||
throttler timer.
|
||||
|
||||
```java
|
||||
public interface Throttler {
|
||||
@ -108,103 +111,71 @@ public class ThrottleTimerImpl implements Throttler {
|
||||
}, 0, throttlePeriod);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
||||
know that the beer serving rate is limited by their appearances.
|
||||
class B2BService {
|
||||
|
||||
```java
|
||||
class Bartender {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
||||
calls per second and the old dwarf to 4.
|
||||
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||
second and Nike to 6.
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.shutdown();
|
||||
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An excerpt from the example's console output:
|
||||
|
||||
```
|
||||
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
|
||||
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
|
||||
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
|
||||
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
|
||||
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
|
||||
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
|
||||
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
|
||||
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
|
||||
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||
@ -214,7 +185,7 @@ An excerpt from the example's console output:
|
||||
|
||||
The Throttling pattern should be used:
|
||||
|
||||
* When service access needs to be restricted not to have high impact on the performance of the service.
|
||||
* When a service access needs to be restricted to not have high impacts on the performance of the service.
|
||||
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
|
||||
|
||||
## Credits
|
||||
|
@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
* complete service by users or a particular tenant. This can allow systems to continue to function
|
||||
* and meet service level agreements, even when an increase in demand places load on resources.
|
||||
* <p>
|
||||
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
|
||||
* In this example we have ({@link App}) as the initiating point of the service. This is a time
|
||||
* based throttling, i.e. only a certain number of calls are allowed per second.
|
||||
* </p>
|
||||
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
|
||||
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
|
||||
* ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
|
||||
* B2BService}) is the service which is consumed by the tenants and is throttled.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
@ -50,35 +50,33 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make calls to the bartender.
|
||||
* Make calls to the B2BService dummy API.
|
||||
*/
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
|
@ -29,32 +29,33 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
|
||||
* the resource based on the time given to the tenant.
|
||||
* A service which accepts a tenant and throttles the resource based on the time given to the
|
||||
* tenant.
|
||||
*/
|
||||
class Bartender {
|
||||
class B2BService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders a drink from the bartender.
|
||||
* Calls dummy customer api.
|
||||
*
|
||||
* @return customer id which is randomly generated
|
||||
*/
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public final class CallsCount {
|
||||
* Resets the count of all the tenants in the map.
|
||||
*/
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
|
@ -25,26 +25,22 @@ package com.iluwatar.throttling;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BarCustomer is a tenant with a name and a number of allowed calls per second.
|
||||
* A Pojo class to create a basic Tenant with the allowed calls per second.
|
||||
*/
|
||||
public class BarCustomer {
|
||||
public class Tenant {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the BarCustomer
|
||||
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
|
||||
* @param name Name of the tenant
|
||||
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
|
||||
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
||||
*/
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
@ -52,4 +48,12 @@ public class BarCustomer {
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
@ -32,18 +32,19 @@ import org.junit.jupiter.api.Test;
|
||||
/**
|
||||
* B2BServiceTest class to test the B2BService
|
||||
*/
|
||||
public class BartenderTest {
|
||||
public class B2BServiceTest {
|
||||
|
||||
private final CallsCount callsCount = new CallsCount();
|
||||
|
||||
@Test
|
||||
void dummyCustomerApiTest() {
|
||||
var tenant = new BarCustomer("pirate", 2, callsCount);
|
||||
var tenant = new Tenant("testTenant", 2, callsCount);
|
||||
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
||||
var timer = (Throttler) () -> {};
|
||||
var service = new Bartender(timer, callsCount);
|
||||
var timer = (Throttler) () -> {
|
||||
};
|
||||
var service = new B2BService(timer, callsCount);
|
||||
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
|
||||
var counter = callsCount.getCount(tenant.getName());
|
||||
assertEquals(2, counter, "Counter limit must be reached");
|
||||
}
|
@ -23,21 +23,20 @@
|
||||
|
||||
package com.iluwatar.throttling;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* TenantTest to test the creation of Tenant with valid parameters.
|
||||
*/
|
||||
public class BarCustomerTest {
|
||||
public class TenantTest {
|
||||
|
||||
@Test
|
||||
void constructorTest() {
|
||||
assertThrows(InvalidParameterException.class, () -> {
|
||||
new BarCustomer("sirBrave", -1, new CallsCount());
|
||||
new Tenant("FailTenant", -1, new CallsCount());
|
||||
});
|
||||
}
|
||||
}
|
@ -17,19 +17,19 @@ and to interleave the execution of functions without hard coding them together.
|
||||
## Explanation
|
||||
|
||||
Recursion is a frequently adopted technique for solving algorithmic problems in a divide and conquer
|
||||
style. For example, calculating Fibonacci accumulating sum and factorials. In these kinds of
|
||||
problems, recursion is more straightforward than its loop counterpart. Furthermore, recursion may
|
||||
need less code and looks more concise. There is a saying that every recursion problem can be solved
|
||||
using a loop with the cost of writing code that is more difficult to understand.
|
||||
style. For example calculating fibonacci accumulating sum and factorials. In these kinds of problems
|
||||
recursion is more straightforward than their loop counterpart. Furthermore recursion may need less
|
||||
code and looks more concise. There is a saying that every recursion problem can be solved using
|
||||
a loop with the cost of writing code that is more difficult to understand.
|
||||
|
||||
However, recursion-type solutions have one big caveat. For each recursive call, it typically needs
|
||||
However recursion type solutions have one big caveat. For each recursive call it typically needs
|
||||
an intermediate value stored and there is a limited amount of stack memory available. Running out of
|
||||
stack memory creates a stack overflow error and halts the program execution.
|
||||
|
||||
Trampoline pattern is a trick that allows defining recursive algorithms in Java without blowing the
|
||||
Trampoline pattern is a trick that allows us define recursive algorithms in Java without blowing the
|
||||
stack.
|
||||
|
||||
Real-world example
|
||||
Real world example
|
||||
|
||||
> A recursive Fibonacci calculation without the stack overflow problem using the Trampoline pattern.
|
||||
|
||||
@ -105,26 +105,24 @@ public interface Trampoline<T> {
|
||||
Using the `Trampoline` to get Fibonacci values.
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
LOGGER.info("Start calculating war casualties");
|
||||
var result = loop(10, 1).result();
|
||||
LOGGER.info("The number of orcs perished in the war: {}", result);
|
||||
}
|
||||
|
||||
public static Trampoline<Integer> loop(int times, int prod) {
|
||||
public static Trampoline<Integer> loop(int times, int prod) {
|
||||
if (times == 0) {
|
||||
return Trampoline.done(prod);
|
||||
return Trampoline.done(prod);
|
||||
} else {
|
||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("start pattern");
|
||||
var result = loop(10, 1).result();
|
||||
log.info("result {}", result);
|
||||
```
|
||||
|
||||
Program output:
|
||||
|
||||
```
|
||||
19:22:24.462 [main] INFO com.iluwatar.trampoline.TrampolineApp - Start calculating war casualties
|
||||
19:22:24.472 [main] INFO com.iluwatar.trampoline.TrampolineApp - The number of orcs perished in the war: 3628800
|
||||
start pattern
|
||||
result 3628800
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
@ -135,8 +133,8 @@ Program output:
|
||||
|
||||
Use the Trampoline pattern when
|
||||
|
||||
* For implementing tail-recursive functions. This pattern allows to switch on a stackless operation.
|
||||
* For interleaving execution of two or more functions on the same thread.
|
||||
* For implementing tail recursive function. This pattern allows to switch on a stackless operation.
|
||||
* For interleaving the execution of two or more functions on the same thread.
|
||||
|
||||
## Known uses
|
||||
|
||||
|
@ -107,4 +107,6 @@ public interface Trampoline<T> {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ public class TrampolineApp {
|
||||
* Main program for showing pattern. It does loop with factorial function.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
LOGGER.info("Start calculating war casualties");
|
||||
LOGGER.info("start pattern");
|
||||
var result = loop(10, 1).result();
|
||||
LOGGER.info("The number of orcs perished in the war: {}", result);
|
||||
LOGGER.info("result {}", result);
|
||||
|
||||
}
|
||||
|
||||
@ -55,4 +55,5 @@ public class TrampolineApp {
|
||||
return Trampoline.more(() -> loop(times - 1, prod * times));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,80 +10,19 @@ tags:
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
Provide objects which follow value semantics rather than reference semantics.
|
||||
This means value objects' equality is not based on identity. Two value objects are
|
||||
This means value objects' equality are not based on identity. Two value objects are
|
||||
equal when they have the same value, not necessarily being the same object.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> There is a class for hero statistics in a role-playing game. The statistics contain attributes
|
||||
> such as strength, intelligence, and luck. The statistics of different heroes should be equal
|
||||
> when all the attributes are equal.
|
||||
|
||||
In plain words
|
||||
|
||||
> Value objects are equal when their attributes have the same value
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In computer science, a value object is a small object that represents a simple entity whose
|
||||
> equality is not based on identity: i.e. two value objects are equal when they have the same
|
||||
> value, not necessarily being the same object.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Here is the `HeroStat` class that is the value object. Notice the use of
|
||||
[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation.
|
||||
|
||||
```java
|
||||
@Value(staticConstructor = "valueOf")
|
||||
class HeroStat {
|
||||
|
||||
int strength;
|
||||
int intelligence;
|
||||
int luck;
|
||||
}
|
||||
```
|
||||
|
||||
The example creates three different `HeroStat`s and compares their equality.
|
||||
|
||||
```java
|
||||
var statA = HeroStat.valueOf(10, 5, 0);
|
||||
var statB = HeroStat.valueOf(10, 5, 0);
|
||||
var statC = HeroStat.valueOf(5, 1, 8);
|
||||
|
||||
LOGGER.info(statA.toString());
|
||||
LOGGER.info(statB.toString());
|
||||
LOGGER.info(statC.toString());
|
||||
|
||||
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
||||
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
||||
```
|
||||
|
||||
Here's the console output.
|
||||
|
||||
```
|
||||
20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
|
||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
|
||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8)
|
||||
20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true
|
||||
20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Value Object when
|
||||
|
||||
* The object's equality needs to be based on the object's value
|
||||
* You need to measure the objects' equality based on the objects' value
|
||||
|
||||
## Known uses
|
||||
## Real world examples
|
||||
|
||||
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
|
||||
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
|
||||
@ -92,7 +31,6 @@ Use the Value Object when
|
||||
## Credits
|
||||
|
||||
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
|
||||
* [ValueObject](https://martinfowler.com/bliki/ValueObject.html)
|
||||
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
|
||||
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
|
||||
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94)
|
||||
|
@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class App {
|
||||
|
||||
/**
|
||||
* This example creates three HeroStats (value objects) and checks equality between those.
|
||||
* This practice creates three HeroStats(Value object) and checks equality between those.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var statA = HeroStat.valueOf(10, 5, 0);
|
||||
@ -51,8 +51,6 @@ public class App {
|
||||
var statC = HeroStat.valueOf(5, 1, 8);
|
||||
|
||||
LOGGER.info(statA.toString());
|
||||
LOGGER.info(statB.toString());
|
||||
LOGGER.info(statC.toString());
|
||||
|
||||
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
|
||||
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
|
||||
|
@ -23,7 +23,10 @@
|
||||
|
||||
package com.iluwatar.value.object;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* HeroStat is a value object.
|
||||
@ -32,10 +35,23 @@ import lombok.Value;
|
||||
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
|
||||
* </a>
|
||||
*/
|
||||
@Value(staticConstructor = "valueOf")
|
||||
class HeroStat {
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
public class HeroStat {
|
||||
|
||||
// Stats for a hero
|
||||
|
||||
private final int strength;
|
||||
private final int intelligence;
|
||||
private final int luck;
|
||||
|
||||
// Static factory method to create new instances.
|
||||
public static HeroStat valueOf(int strength, int intelligence, int luck) {
|
||||
return new HeroStat(strength, intelligence, luck);
|
||||
}
|
||||
|
||||
// The clone() method should not be public. Just don't override it.
|
||||
|
||||
int strength;
|
||||
int intelligence;
|
||||
int luck;
|
||||
}
|
||||
|
Reference in New Issue
Block a user