Compare commits
2 Commits
all-contri
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
bea39206ed | |||
c3228fd6d2 |
@ -1460,69 +1460,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zWeBrain",
|
||||
"name": "zWeBrain",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/46642512?v=4",
|
||||
"profile": "https://github.com/zWeBrain",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Al-assad",
|
||||
"name": "余林颖",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22493821?v=4",
|
||||
"profile": "https://al-assad.github.io/notion/",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "STudio26",
|
||||
"name": "Alain",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6988911?v=4",
|
||||
"profile": "https://github.com/STudio26",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "DEV-VRUPER",
|
||||
"name": "VR",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30525467?v=4",
|
||||
"profile": "https://github.com/DEV-VRUPER",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JackieNim",
|
||||
"name": "JackieNim",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4138836?v=4",
|
||||
"profile": "https://github.com/JackieNim",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "EdisonE3",
|
||||
"name": "EdisonE3",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/52118917?v=4",
|
||||
"profile": "https://github.com/EdisonE3",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tao-sun2",
|
||||
"name": "Tao",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/66189688?v=4",
|
||||
"profile": "https://github.com/tao-sun2",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 4,
|
||||
|
8
.github/workflows/maven-ci.yml
vendored
8
.github/workflows/maven-ci.yml
vendored
@ -40,26 +40,26 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
# Disabling shallow clone for improving relevancy of SonarQube reporting
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@master
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
|
||||
- name: Cache Maven dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
|
6
.github/workflows/maven-pr-builder.yml
vendored
6
.github/workflows/maven-pr-builder.yml
vendored
@ -38,16 +38,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@master
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Cache Maven Dependecies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@master
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
|
13
README.md
13
README.md
@ -10,7 +10,7 @@
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
<br/>
|
||||
@ -315,17 +315,6 @@ This project is licensed under the terms of the MIT license.
|
||||
<td align="center"><a href="https://www.linkedin.com/in/jinisha-vora"><img src="https://avatars.githubusercontent.com/u/40777762?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jinishavora</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3Ajinishavora" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=jinishavora" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/eas5"><img src="https://avatars.githubusercontent.com/u/50836521?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Elvys Soares</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=eas5" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/zWeBrain"><img src="https://avatars.githubusercontent.com/u/46642512?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zWeBrain</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=zWeBrain" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://al-assad.github.io/notion/"><img src="https://avatars.githubusercontent.com/u/22493821?v=4?s=100" width="100px;" alt=""/><br /><sub><b>余林颖</b></sub></a><br /><a href="#translation-Al-assad" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/STudio26"><img src="https://avatars.githubusercontent.com/u/6988911?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alain</b></sub></a><br /><a href="#translation-STudio26" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/DEV-VRUPER"><img src="https://avatars.githubusercontent.com/u/30525467?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VR</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=DEV-VRUPER" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/JackieNim"><img src="https://avatars.githubusercontent.com/u/4138836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>JackieNim</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=JackieNim" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/EdisonE3"><img src="https://avatars.githubusercontent.com/u/52118917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>EdisonE3</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=EdisonE3" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tao-sun2"><img src="https://avatars.githubusercontent.com/u/66189688?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tao</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=tao-sun2" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
@ -1,122 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Composite Entity
|
||||
folder: composite-entity
|
||||
permalink: /patterns/composite-entity/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Enterprise Integration Pattern
|
||||
---
|
||||
|
||||
## Intent
|
||||
|
||||
It is used to model, represent, and manage a set of persistent objects that are interrelated, rather than representing them as individual fine-grained entities.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> For a console, there may be many interfaces that need to be managed and controlled. Using the composite entity pattern, dependent objects such as messages and signals can be combined together and controlled using a single object.
|
||||
|
||||
In plain words
|
||||
|
||||
> Composite entity pattern allows a set of related objects to be represented and managed by a unified object.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
We need a generic solution for the problem. To achieve this, let's introduce a generic
|
||||
Composite Entity Pattern.
|
||||
|
||||
```java
|
||||
public abstract class DependentObject<T> {
|
||||
|
||||
T data;
|
||||
|
||||
public void setData(T message) {
|
||||
this.data = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class CoarseGrainedObject<T> {
|
||||
|
||||
DependentObject<T>[] dependentObjects;
|
||||
|
||||
public void setData(T... data) {
|
||||
IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i]));
|
||||
}
|
||||
|
||||
public T[] getData() {
|
||||
return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The specialized composite entity `console` inherit from this base class as follows.
|
||||
|
||||
```java
|
||||
public class MessageDependentObject extends DependentObject<String> {
|
||||
|
||||
}
|
||||
|
||||
public class SignalDependentObject extends DependentObject<String> {
|
||||
|
||||
}
|
||||
|
||||
public class ConsoleCoarseGrainedObject extends CoarseGrainedObject<String> {
|
||||
|
||||
@Override
|
||||
public String[] getData() {
|
||||
super.getData();
|
||||
return new String[]{
|
||||
dependentObjects[0].getData(), dependentObjects[1].getData()
|
||||
};
|
||||
}
|
||||
|
||||
public void init() {
|
||||
dependentObjects = new DependentObject[]{
|
||||
new MessageDependentObject(), new SignalDependentObject()};
|
||||
}
|
||||
}
|
||||
|
||||
public class CompositeEntity {
|
||||
|
||||
private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject();
|
||||
|
||||
public void setData(String message, String signal) {
|
||||
console.setData(message, signal);
|
||||
}
|
||||
|
||||
public String[] getData() {
|
||||
return console.getData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now managing the assignment of message and signal objects with the composite entity `console`.
|
||||
|
||||
```java
|
||||
var console = new CompositeEntity();
|
||||
console.init();
|
||||
console.setData("No Danger", "Green Light");
|
||||
Arrays.stream(console.getData()).forEach(LOGGER::info);
|
||||
console.setData("Danger", "Red Light");
|
||||
Arrays.stream(console.getData()).forEach(LOGGER::info);
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Composite Entity Pattern in the following situation:
|
||||
|
||||
* You want to manage multiple dependency objects through one object to adjust the degree of granularity between objects. At the same time, the lifetime of dependency objects depends on a coarse-grained object.
|
||||
## Credits
|
||||
|
||||
* [Composite Entity Pattern in wikipedia](https://en.wikipedia.org/wiki/Composite_entity_pattern)
|
@ -1,45 +0,0 @@
|
||||
@startuml
|
||||
package com.iluwatar.compositeentity {
|
||||
class App {
|
||||
+ App(message: String, signal: String)
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
class CompositeEntity{
|
||||
- console : ConsoleCoarseGrainedObject
|
||||
+ CompositeEntity()
|
||||
+ setData(message: String, signal: String)
|
||||
+ getData()
|
||||
+ init()
|
||||
}
|
||||
abstract CoarseGrainedObject{
|
||||
- dependentObjects : DependentObject[]
|
||||
+ CoarseGrainedObject()
|
||||
+ setData(data: T[])
|
||||
+ getData()
|
||||
}
|
||||
abstract DependentObject{
|
||||
- data : T
|
||||
+ DependentObject()
|
||||
+ setData(data: T)
|
||||
+ getData()
|
||||
}
|
||||
class ConsoleCoarseGrainedObject{
|
||||
+ ConsoleCoarseGrainedObject()
|
||||
+ getData()
|
||||
+ init()
|
||||
}
|
||||
class MessageDependentObject{
|
||||
+ MessageDependentObject()
|
||||
}
|
||||
class SignalDependentObject{
|
||||
+ SignalDependentObject()
|
||||
}
|
||||
|
||||
MessageDependentObject --|> DependentObject
|
||||
SignalDependentObject --|> DependentObject
|
||||
ConsoleCoarseGrainedObject --|> CoarseGrainedObject
|
||||
CompositeEntity -right-> ConsoleCoarseGrainedObject
|
||||
CoarseGrainedObject "1" o--> "0.." DependentObject
|
||||
App .right.> CompositeEntity
|
||||
}
|
||||
@enduml
|
Binary file not shown.
Before Width: | Height: | Size: 90 KiB |
@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<version>1.25.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>composite-entity</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.composite-entity.com.iluwatar.compositeentity.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -1,38 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
import java.util.Arrays;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* Composite entity is a Java EE Software design pattern and it is used to model, represent, and
|
||||
* manage a set of interrelated persistent objects rather than representing them as individual
|
||||
* fine-grained entity beans, and also a composite entity bean represents a graph of objects.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
|
||||
/**
|
||||
* An instance that a console manages two related objects.
|
||||
*/
|
||||
public App(String message, String signal) {
|
||||
var console = new CompositeEntity();
|
||||
console.init();
|
||||
console.setData(message, signal);
|
||||
Arrays.stream(console.getData()).forEach(LOGGER::info);
|
||||
console.setData("Danger", "Red Light");
|
||||
Arrays.stream(console.getData()).forEach(LOGGER::info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
new App("No Danger", "Green Light");
|
||||
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* A coarse-grained object is an object with its own life cycle manages its own relationships to
|
||||
* other objects. It can be an object contained in the composite entity, or, composite entity itself
|
||||
* can be the coarse-grained object which holds dependent objects.
|
||||
*/
|
||||
|
||||
public abstract class CoarseGrainedObject<T> {
|
||||
|
||||
DependentObject<T>[] dependentObjects;
|
||||
|
||||
public void setData(T... data) {
|
||||
IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i]));
|
||||
}
|
||||
|
||||
public T[] getData() {
|
||||
return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray();
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
/**
|
||||
* Composite entity is the coarse-grained entity bean which may be the coarse-grained object, or may
|
||||
* contain a reference to the coarse-grained object.
|
||||
*/
|
||||
|
||||
public class CompositeEntity {
|
||||
|
||||
private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject();
|
||||
|
||||
public void setData(String message, String signal) {
|
||||
console.setData(message, signal);
|
||||
}
|
||||
|
||||
public String[] getData() {
|
||||
return console.getData();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
console.init();
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
/**
|
||||
* A specific CoarseGrainedObject to implement a console.
|
||||
*/
|
||||
|
||||
public class ConsoleCoarseGrainedObject extends CoarseGrainedObject<String> {
|
||||
|
||||
@Override
|
||||
public String[] getData() {
|
||||
return new String[]{
|
||||
dependentObjects[0].getData(), dependentObjects[1].getData()
|
||||
};
|
||||
}
|
||||
|
||||
public void init() {
|
||||
dependentObjects = new DependentObject[]{
|
||||
new MessageDependentObject(), new SignalDependentObject()};
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
/**
|
||||
* It is an object, which can contain other dependent objects (there may be a tree of objects within
|
||||
* the composite entity), that depends on the coarse-grained object and has its life cycle managed
|
||||
* by the coarse-grained object.
|
||||
*/
|
||||
|
||||
public abstract class DependentObject<T> {
|
||||
|
||||
T data;
|
||||
|
||||
public void setData(T message) {
|
||||
this.data = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
/**
|
||||
* The first DependentObject to show message.
|
||||
*/
|
||||
|
||||
public class MessageDependentObject extends DependentObject<String> {
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
/**
|
||||
* The second DependentObject to show message.
|
||||
*/
|
||||
|
||||
public class SignalDependentObject extends DependentObject<String> {
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* com.iluwatar.compositeentity.App running test
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
* <p>
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link
|
||||
* App#main(String[])} throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package com.iluwatar.compositeentity;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PersistenceTest {
|
||||
|
||||
final static ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject();
|
||||
|
||||
@Test
|
||||
void dependentObjectChangedForPersistenceTest() {
|
||||
MessageDependentObject dependentObject = new MessageDependentObject();
|
||||
console.init();
|
||||
console.dependentObjects[0] = dependentObject;
|
||||
String message = "Danger";
|
||||
assertNull(console.dependentObjects[0].getData());
|
||||
dependentObject.setData(message);
|
||||
assertEquals(message, console.dependentObjects[0].getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void coarseGrainedObjectChangedForPersistenceTest() {
|
||||
MessageDependentObject dependentObject = new MessageDependentObject();
|
||||
console.init();
|
||||
console.dependentObjects[0] = dependentObject;
|
||||
String message = "Danger";
|
||||
assertNull(console.dependentObjects[0].getData());
|
||||
console.setData(message);
|
||||
assertEquals(message, dependentObject.getData());
|
||||
}
|
||||
}
|
@ -25,4 +25,4 @@ This pattern is one of those ones where you’ll know when you need it. If you h
|
||||
|
||||
## Credits
|
||||
|
||||
* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html)
|
||||
* [Game Programming Patterns - Double Buffer]([http://gameprogrammingpatterns.com/double-buffer.html](http://gameprogrammingpatterns.com/double-buffer.html))
|
||||
|
62
fr/README.md
62
fr/README.md
@ -1,12 +1,12 @@
|
||||
<!-- Cette ligne doit restée vide pour des raisons de formatage
|
||||
afin qu’on puisse avoir un affichage agréable comme sur un
|
||||
afin qu'on puisse avoir un affichage agréable comme sur un
|
||||
site web par exemple -->
|
||||
|
||||
# Les patrons de conception implémentés en Java
|
||||
|
||||

|
||||
[](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
@ -15,56 +15,46 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
Les patrons de conception (design patterns) sont les meilleures
|
||||
pratiques formalisées qu’un programmeur peut utiliser pour résoudre
|
||||
des problèmes courants lors de la conception d’une application
|
||||
ou d’un système.
|
||||
Le patron de conception est la meilleure formatalisation qu'un programmeur
|
||||
peut utiliser pour résoudre un problème lors d'une conception d'une application/system.
|
||||
|
||||
Les patrons de conception peuvent accélérer le processus de
|
||||
Le patron de conception (design pattern) est là pour accélérer le processus de
|
||||
développement en fournissant des paradigmes éprouvés.
|
||||
|
||||
La réutilisation de patrons de conception aide à se prémunir des problèmes subtils
|
||||
qui causent des problèmes majeurs, et cette pratique augmente la lisibilité
|
||||
du code pour les développeurs et architectes familiers de ces concepts.
|
||||
La réutilisation de patron de conception aide à prévenir des problèmes subtiles mais
|
||||
qui sont à l'origine de problèmes majeures, comme cette pratique augmente la lisibilitée
|
||||
du code par les développeurs/architectes familiers avec l'utilisation de ces concepts.
|
||||
|
||||
# Commencer
|
||||
|
||||
Ce site présente des patrons de conception Java. Les solutions ont été développées
|
||||
par des développeurs et architectes expérimentés de la communauté open source.
|
||||
Les modèles peuvent être parcourus par leurs descriptions de haut niveau ou
|
||||
en regardant leur code source.
|
||||
Les exemples de code source sont bien commentés et peuvent être considérés
|
||||
comme tutoriels de programmation sur la façon d’implémenter un modèle spécifique.
|
||||
Nous utilisons les technologies Java open source les plus populaires et éprouvées.
|
||||
Ce site présente des modèles de conception Java. Les solutions ont été développées par des
|
||||
développeurs et architectes expérimentés de la communauté open source. Les modèles peuvent être parcourus par leurs descriptions de haut niveau ou en regardant leur code source. Les exemples de code source sont bien commentés et peuvent être considérés comme tutoriels de programmation sur la façon d'implémenter un modèle spécifique. Nous utilisons le plus technologies Java open source éprouvées au combat.
|
||||
|
||||
Avant de vous plonger dans le contenu, vous devriez vous familiariser avec divers
|
||||
Avant de plonger dans le matériau, vous devez vous familiariser avec divers
|
||||
[Principes de conception de logiciels](https://java-design-patterns.com/principles/).
|
||||
|
||||
Tous les modèles doivent être aussi simples que possible.
|
||||
Vous devriez commencer par les principes KISS, YAGNI et
|
||||
Faites La Chose La Plus Simple Qui Fonctionne.
|
||||
La complexité et les modèles ne devraient être introduits
|
||||
que lorsqu’ils sont nécessaires pour une extensibilité pratique.
|
||||
Toutes les conceptions doivent être aussi simples que possible. Vous devriez commencer par KISS, YAGNI,
|
||||
et faire la chose la plus simple qui pourrait éventuellement fonctionner principes. Complexité et
|
||||
les modèles ne devraient être introduits que lorsqu'ils sont nécessaires pour
|
||||
extensibilité.
|
||||
|
||||
Une fois que vous êtes familiarisé avec ces concepts, vous pouvez commencer à explorer les
|
||||
[modèles de conception disponibles](https://java-design-patterns.com/patterns/)
|
||||
par n’importe laquelle les approches suivantes :
|
||||
Une fois que vous êtes familiarisé avec ces concepts, vous pouvez commencer à explorer
|
||||
[modèles de conception disponibles](https://java-design-patterns.com/patterns/) par tout
|
||||
des approches suivantes
|
||||
|
||||
- Recherchez un modèle spécifique par son nom.
|
||||
Vous n’en trouvez pas ? Veuillez signaler un nouveau modèle [ici](https://github.com/iluwatar/java-design-patterns/issues).
|
||||
- Utilisation de balises telles que `Performance`, `Gang of Four` ou `Data access`.
|
||||
- Utilisation des catégories de modèles, `Creational`, `Behavioral` et autres.
|
||||
- Recherchez un modèle spécifique par son nom. Vous n'en trouvez pas? Veuillez signaler un nouveau modèle [ici](https://github.com/iluwatar/java-design-patterns/issues).
|
||||
- Utilisation de balises telles que `Performance`, `Gang of Four` ou `Data access`.
|
||||
- Utilisation des catégories de modèles, `Créatif`, `Comportemental` et autres.
|
||||
|
||||
Nous espérons que vous trouverez les solutions orientées objet présentées
|
||||
sur ce site utiles dans vos architectures et que vous aurez autant
|
||||
de plaisir à les apprendre que nous en avons eu à les développer.
|
||||
J'espère que les solutions orientées objet présentées sur ce site vous seront utiles
|
||||
dans vos architectures et ayez autant de plaisir à les apprendre que nous en avons eu à les développer.
|
||||
|
||||
# Comment contribuer
|
||||
|
||||
Si vous souhaitez contribuer au projet, vous trouverez les informations pertinentes dans
|
||||
notre [wiki développeur](https://github.com/iluwatar/java-design-patterns/wiki).
|
||||
Nous vous aiderons répondrons à vos questions dans le [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns).
|
||||
notre [wiki développeur](https://github.com/iluwatar/java-design-patterns/wiki). Nous aiderons
|
||||
vous et répondez à vos questions dans le [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns).
|
||||
|
||||
# Licence
|
||||
|
||||
Ce projet est concédé sous les termes de la licence MIT.
|
||||
Ce projet est concédé sous les termes de la licence MIT.
|
||||
|
26
ko/README.md
26
ko/README.md
@ -14,35 +14,35 @@
|
||||
|
||||
# 소개
|
||||
|
||||
디자인 패턴은 개발자가 응용 프로그램이나 시스템을 디자인할 때 일반적인 문제를 해결하는 데 사용할 수 있는 가장 정석적인 방법입니다.
|
||||
디자인 패턴은 프로그래머가 응용 프로그램이나 시스템을 디자인 할 때 일반적인 문제를 해결하는 데 사용할 수있는 가장 공식화 된 방법입니다.
|
||||
|
||||
디자인 패턴은 테스트되고 입증된 개발 패러다임을 제공하여 개발 프로세스 속도를 높일 수 있습니다.
|
||||
디자인 패턴은 테스트되고 입증 된 개발 패러다임을 제공하여 개발 프로세스 속도를 높일 수 있습니다.
|
||||
|
||||
디자인 패턴을 사용하면 주요 문제를 유발하는 미묘한 이슈들을 방지하는 데 도움이 되며 또한 패턴에 익숙한 개발자와 아키텍트의 코드 가독성을 향상시킵니다.
|
||||
디자인 패턴을 재사용하면 주요 문제를 유발하는 미묘한 이슈들을 방지하는데 도움이 되며 또한 패턴에 익숙한 코더와 아키텍트의 코드 가독성도 향상됩니다.
|
||||
|
||||
# 시작하기
|
||||
|
||||
이 사이트는 Java 디자인 패턴을 설명합니다. 이 솔루션은 오픈 소스 커뮤니티의 경험이 많은 개발자와 설계자가 개발했습니다. 패턴은 높은 수준의 설명이나 소스 코드를 통해 찾아볼 수 있습니다. 소스 코드 예제는 잘 설명되어 있으며 특정 패턴을 구현하는 방법을 알려주는 프로그래밍 튜토리얼로 사용할 수 있습니다. 우리는 가장 널리 알려지고 실무에서 입증된 오픈 소스 Java 기술을 사용합니다.
|
||||
이 사이트는 Java 디자인 패턴을 보여줍니다. 이 솔루션은 오픈 소스 커뮤니티의 경험이 많은 프로그래머와 설계자가 개발했습니다. 패턴은 높은 수준의 설명이나 소스 코드를 통해 찾아 볼 수 있습니다. 소스 코드 예제는 잘 설명되어 있으며 특정 패턴을 구현하는 방법을 알려주는 프로그래밍 튜토리얼로 생각할 수 있습니다. 우리는 가장 널리 알려지고 실무에서 입증된 오픈 소스 Java 기술을 사용합니다.
|
||||
|
||||
자료를 살펴보기 전에 다양한 [소프트웨어 설계 원칙](https://java-design-patterns.com/principles/)을 숙지해야 합니다.
|
||||
자료를 살펴보기 전에 다양한 [소프트웨어 설계 원칙](https://java-design-patterns.com/principles/)을 숙지해야합니다.
|
||||
|
||||
모든 디자인 패턴은 가능한 한 심플해야 합니다. 소프트웨어 개발의 KISS, YAGNI 원칙을 지켜야 하며, 문제를 해결할 수 있는 가장 심플한 일을 해야 합니다. 복잡한 디자인 패턴은 실용적이고 확장성을 위해 필요할 때만 도입되어야 합니다.
|
||||
모든 디자인은 가능한 한 단순해야합니다. 당신은 KISS, YAGNI로 시작해야하며, 원칙을 작동 할 수 있는 가장 단순한 일을 해야합니다. 복잡성과 패턴은 실용적인 확장성을 위해 필요할 때만 도입되어야합니다.
|
||||
|
||||
이러한 개념에 익숙해지면 아래와 같은 방법으로 [사용 가능한 디자인 패턴](https://java-design-patterns.com/patterns/) 중 하나를 선택하여 문제를 해결할 수 있습니다.
|
||||
이러한 개념에 익숙해지면 다음 접근 방식 중 하나를 이용하여 [사용 가능한 디자인 패턴](https://java-design-patterns.com/patterns/)으로 드릴다운 할 수 있습니다.
|
||||
|
||||
- 이름으로 특정 패턴을 검색합니다. 찾는 패턴이 없다면 [여기](https://github.com/iluwatar/java-design-patterns/issues)에서 새 패턴을 요청하세요.
|
||||
- `Performance`, `Gang of Four` 또는 `Data access` 등의 태그로 검색하세요.
|
||||
- `Creational`, `Behavioral` 등의 패턴 카테고리로 검색하세요.
|
||||
- 이름으로 특정 패턴을 검색합니다. 찾을 수 없습니까? [여기](https://github.com/iluwatar/java-design-patterns/issues)에서 새 패턴을 보고하십시오.
|
||||
- `Performance`, `Gang of Four` 또는 `Data access` 태그 사용.
|
||||
- 패턴 카테고리, `Creational`, `Behavioral` 및 기타 사용
|
||||
|
||||
이 사이트에서 제공하는 객체 지향 솔루션이 여러분의 아키텍처에 유용하고 사용되고 우리가 개발 한 것만큼 재미있게 배우기를 바랍니다.
|
||||
이 사이트에 제시된 객체 지향 솔루션이 여러분의 아키텍처에서 유용하고 우리가 개발 한 것만큼 재미있게 배우기를 바랍니다.
|
||||
|
||||
# 기여하는 방법
|
||||
|
||||
프로젝트에 기여할 의향이 있다면 [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki)에서 관련 정보를 찾을 수 있습니다. [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns)에서 귀하를 돕고 질문에 답변해 드리겠습니다.
|
||||
프로젝트에 기여할 의향이 있다면 [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki)에서 관련 정보를 찾을 수 있습니다. [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns)에서 귀하를 돕고 질문에 답변 해 드리겠습니다.
|
||||
|
||||
# 특허
|
||||
|
||||
이 프로젝트는 MIT 라이센스 조건에 따라 라이센스가 적용됩니다.
|
||||
이 프로젝트는 MIT 라이센스 조건에 따라 라이센스가 부여됩니다.
|
||||
|
||||
# 기여자
|
||||
|
||||
|
@ -1,185 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Strategy
|
||||
folder: strategy
|
||||
permalink: /patterns/strategy/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 동의어
|
||||
|
||||
정책(Policy) 패턴
|
||||
|
||||
## 의도
|
||||
|
||||
알고리즘군을 정의하고 각 알고리즘을 캡슐화하고 상호 변경 가능하게 만듭니다. 전략(Strategy) 패턴을 사용하면 알고리즘이 이를 사용하는 클라이언트와 독립적일 수 있습니다.
|
||||
|
||||
## 설명
|
||||
|
||||
실제 예제
|
||||
|
||||
> 드래곤을 사냥하는 것은 위험한 일입니다. 경험이 쌓이면 쉬워집니다. 베테랑 사냥꾼들은 서로 다른 유형의 드래곤에 대해 서로 다른 전투 전략을 개발했습니다.
|
||||
|
||||
평범한 말로는
|
||||
|
||||
> 전략(Strategy) 패턴을 사용하면 런타임에 가장 적합한 알고리즘을 선택할 수 있습니다.
|
||||
|
||||
Wikipedia는
|
||||
|
||||
> 컴퓨터 프로그래밍에서 전략 패턴(정책 패턴이라고도 함)은 런타임에 알고리즘을 선택할 수 있는 행동 소프트웨어 디자인 패턴입니다.
|
||||
|
||||
**프로그래밍 예**
|
||||
|
||||
먼저 드래곤 사냥 전략 인터페이스와 그 구현을 살펴봅니다.
|
||||
|
||||
```java
|
||||
@FunctionalInterface
|
||||
public interface DragonSlayingStrategy {
|
||||
|
||||
void execute();
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class MeleeStrategy implements DragonSlayingStrategy {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
LOGGER.info("With your Excalibur you sever the dragon's head!");
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class ProjectileStrategy implements DragonSlayingStrategy {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!");
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class SpellStrategy implements DragonSlayingStrategy {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
그리고 여기 상대하는 드래곤에 따라 자신의 전투 전략을 선택할 수 있는 강력한 드래곤 슬레이어가 있습니다.
|
||||
|
||||
```java
|
||||
public class DragonSlayer {
|
||||
|
||||
private DragonSlayingStrategy strategy;
|
||||
|
||||
public DragonSlayer(DragonSlayingStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public void changeStrategy(DragonSlayingStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public void goToBattle() {
|
||||
strategy.execute();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
마지막으로 여기 드래곤 슬레이어가 행동합니다.
|
||||
|
||||
```java
|
||||
LOGGER.info("Green dragon spotted ahead!");
|
||||
var dragonSlayer = new DragonSlayer(new MeleeStrategy());
|
||||
dragonSlayer.goToBattle();
|
||||
LOGGER.info("Red dragon emerges.");
|
||||
dragonSlayer.changeStrategy(new ProjectileStrategy());
|
||||
dragonSlayer.goToBattle();
|
||||
LOGGER.info("Black dragon lands before you.");
|
||||
dragonSlayer.changeStrategy(new SpellStrategy());
|
||||
dragonSlayer.goToBattle();
|
||||
```
|
||||
|
||||
프로그램 출력:
|
||||
|
||||
```
|
||||
Green dragon spotted ahead!
|
||||
With your Excalibur you sever the dragon's head!
|
||||
Red dragon emerges.
|
||||
You shoot the dragon with the magical crossbow and it falls dead on the ground!
|
||||
Black dragon lands before you.
|
||||
You cast the spell of disintegration and the dragon vaporizes in a pile of dust!
|
||||
```
|
||||
|
||||
또한 Java 8의 새로운 기능인 Lambda Expressions은 구현을 위한 또 다른 접근 방식을 제공합니다:
|
||||
|
||||
```java
|
||||
public class LambdaStrategy {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LambdaStrategy.class);
|
||||
|
||||
public enum Strategy implements DragonSlayingStrategy {
|
||||
MeleeStrategy(() -> LOGGER.info(
|
||||
"With your Excalibur you severe the dragon's head!")),
|
||||
ProjectileStrategy(() -> LOGGER.info(
|
||||
"You shoot the dragon with the magical crossbow and it falls dead on the ground!")),
|
||||
SpellStrategy(() -> LOGGER.info(
|
||||
"You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"));
|
||||
|
||||
private final DragonSlayingStrategy dragonSlayingStrategy;
|
||||
|
||||
Strategy(DragonSlayingStrategy dragonSlayingStrategy) {
|
||||
this.dragonSlayingStrategy = dragonSlayingStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
dragonSlayingStrategy.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
그리고 여기 드래곤 슬레이어가 행동합니다.
|
||||
|
||||
```java
|
||||
LOGGER.info("Green dragon spotted ahead!");
|
||||
dragonSlayer.changeStrategy(LambdaStrategy.Strategy.MeleeStrategy);
|
||||
dragonSlayer.goToBattle();
|
||||
LOGGER.info("Red dragon emerges.");
|
||||
dragonSlayer.changeStrategy(LambdaStrategy.Strategy.ProjectileStrategy);
|
||||
dragonSlayer.goToBattle();
|
||||
LOGGER.info("Black dragon lands before you.");
|
||||
dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SpellStrategy);
|
||||
dragonSlayer.goToBattle();
|
||||
```
|
||||
|
||||
프로그램 출력은 위와 동일합니다.
|
||||
|
||||
## 클래스 다이어그램
|
||||
|
||||

|
||||
|
||||
## 적용 가능성
|
||||
|
||||
다음과 같은 경우 전략(Strategy) 패턴을 사용합니다.
|
||||
|
||||
* 비슷한 클래스들이 동작 만 다른 경우가 많이 있습니다. 전략 패턴은 여러 동작 중 하나를 클래스로 구성하는 방법을 제공합니다.
|
||||
* 알고리즘의 다양한 변형이 필요합니다. 예를 들어 다양한 공간 / 시간 절충을 반영하는 알고리즘을 정의할 수 있습니다. 이러한 변형이 알고리즘의 클래스 계층 구조로 구현될 때 전략 패턴을 사용할 수 있습니다.
|
||||
* 알고리즘은 클라이언트가 알 필요 없는 데이터를 사용합니다. 전략 패턴을 사용하여 복잡한 알고리즘 별 데이터 구조가 노출되지 않도록 합니다.
|
||||
* 클래스는 많은 동작을 정의하며 이러한 동작은 작업에서 여러 조건문으로 나타납니다. 많은 조건부 대신 관련 조건부 분기를 자체 전략 클래스로 이동하세요.
|
||||
|
||||
## 튜토리얼
|
||||
|
||||
* [전략 패턴 튜토리얼](https://www.journaldev.com/1754/strategy-design-pattern-in-java-example-tutorial)
|
||||
|
||||
## 크레딧
|
||||
|
||||
* [디자인 패턴: 재사용 가능한 객체 지향 소프트웨어의 요소](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [자바의 함수형 프로그래밍: Java 8 Lambda 표현식의 강력한 기능 활용](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b)
|
||||
* [헤드 퍼스트 디자인 패턴: 두뇌 친화적인 가이드](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
||||
* [패턴으로 리팩토링](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7)
|
@ -1,285 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Lockable Object
|
||||
folder: lockable-object
|
||||
permalink: /patterns/lockable-object/
|
||||
categories: Concurrency
|
||||
tags:
|
||||
- Performance
|
||||
---
|
||||
|
||||
|
||||
## Intent
|
||||
|
||||
The lockable object design pattern ensures that there is only one user using the target object. Compared to the built-in synchronization mechanisms such as using the `synchronized` keyword, this pattern can lock objects for an undetermined time and is not tied to the duration of the request.
|
||||
|
||||
## Explanation
|
||||
|
||||
|
||||
Real-world example
|
||||
|
||||
>The Sword Of Aragorn is a legendary object that only one creature can possess at the time.
|
||||
>Every creature in the middle earth wants to possess is, so as long as it's not locked, every creature will fight for it.
|
||||
|
||||
Under the hood
|
||||
|
||||
>In this particular module, the SwordOfAragorn.java is a class that implements the Lockable interface.
|
||||
It reaches the goal of the Lockable-Object pattern by implementing unlock() and unlock() methods using
|
||||
thread-safety logic. The thread-safety logic is implemented with the built-in monitor mechanism of Java.
|
||||
The SwordOfAaragorn.java has an Object property called "synchronizer". In every crucial concurrency code block,
|
||||
it's synchronizing the block by using the synchronizer.
|
||||
|
||||
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
```java
|
||||
/** This interface describes the methods to be supported by a lockable object. */
|
||||
public interface Lockable {
|
||||
|
||||
/**
|
||||
* Checks if the object is locked.
|
||||
*
|
||||
* @return true if it is locked.
|
||||
*/
|
||||
boolean isLocked();
|
||||
|
||||
/**
|
||||
* locks the object with the creature as the locker.
|
||||
*
|
||||
* @param creature as the locker.
|
||||
* @return true if the object was locked successfully.
|
||||
*/
|
||||
boolean lock(Creature creature);
|
||||
|
||||
/**
|
||||
* Unlocks the object.
|
||||
*
|
||||
* @param creature as the locker.
|
||||
*/
|
||||
void unlock(Creature creature);
|
||||
|
||||
/**
|
||||
* Gets the locker.
|
||||
*
|
||||
* @return the Creature that holds the object. Returns null if no one is locking.
|
||||
*/
|
||||
Creature getLocker();
|
||||
|
||||
/**
|
||||
* Returns the name of the object.
|
||||
*
|
||||
* @return the name of the object.
|
||||
*/
|
||||
String getName();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We have defined that according to our context, the object must implement the Lockable interface.
|
||||
|
||||
For example, the SwordOfAragorn class:
|
||||
|
||||
```java
|
||||
public class SwordOfAragorn implements Lockable {
|
||||
|
||||
private Creature locker;
|
||||
private final Object synchronizer;
|
||||
private static final String NAME = "The Sword of Aragorn";
|
||||
|
||||
public SwordOfAragorn() {
|
||||
this.locker = null;
|
||||
this.synchronizer = new Object();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocked() {
|
||||
return this.locker != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lock(@NonNull Creature creature) {
|
||||
synchronized (synchronizer) {
|
||||
LOGGER.info("{} is now trying to acquire {}!", creature.getName(), this.getName());
|
||||
if (!isLocked()) {
|
||||
locker = creature;
|
||||
return true;
|
||||
} else {
|
||||
if (!locker.getName().equals(creature.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(@NonNull Creature creature) {
|
||||
synchronized (synchronizer) {
|
||||
if (locker != null && locker.getName().equals(creature.getName())) {
|
||||
locker = null;
|
||||
LOGGER.info("{} is now free!", this.getName());
|
||||
}
|
||||
if (locker != null) {
|
||||
throw new LockingException("You cannot unlock an object you are not the owner of.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Creature getLocker() {
|
||||
return this.locker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
According to our context, there are creatures that are looking for the sword, so must define the parent class:
|
||||
|
||||
```java
|
||||
public abstract class Creature {
|
||||
|
||||
private String name;
|
||||
private CreatureType type;
|
||||
private int health;
|
||||
private int damage;
|
||||
Set<Lockable> instruments;
|
||||
|
||||
protected Creature(@NonNull String name) {
|
||||
this.name = name;
|
||||
this.instruments = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaches for the Lockable and tried to hold it.
|
||||
*
|
||||
* @param lockable as the Lockable to lock.
|
||||
* @return true of Lockable was locked by this creature.
|
||||
*/
|
||||
public boolean acquire(@NonNull Lockable lockable) {
|
||||
if (lockable.lock(this)) {
|
||||
instruments.add(lockable);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Terminates the Creature and unlocks all of the Lockable that it posses. */
|
||||
public synchronized void kill() {
|
||||
LOGGER.info("{} {} has been slayed!", type, name);
|
||||
for (Lockable lockable : instruments) {
|
||||
lockable.unlock(this);
|
||||
}
|
||||
this.instruments.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks a foe.
|
||||
*
|
||||
* @param creature as the foe to be attacked.
|
||||
*/
|
||||
public synchronized void attack(@NonNull Creature creature) {
|
||||
creature.hit(getDamage());
|
||||
}
|
||||
|
||||
/**
|
||||
* When a creature gets hit it removed the amount of damage from the creature's life.
|
||||
*
|
||||
* @param damage as the damage that was taken.
|
||||
*/
|
||||
public synchronized void hit(int damage) {
|
||||
if (damage < 0) {
|
||||
throw new IllegalArgumentException("Damage cannot be a negative number");
|
||||
}
|
||||
if (isAlive()) {
|
||||
setHealth(getHealth() - damage);
|
||||
if (!isAlive()) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the creature is still alive.
|
||||
*
|
||||
* @return true of creature is alive.
|
||||
*/
|
||||
public synchronized boolean isAlive() {
|
||||
return getHealth() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
As mentioned before, we have classes that extend the Creature class, such as Elf, Orc, and Human.
|
||||
|
||||
Finally, the following program will simulate a battle for the sword:
|
||||
|
||||
```java
|
||||
public class App implements Runnable {
|
||||
|
||||
private static final int WAIT_TIME = 3;
|
||||
private static final int WORKERS = 2;
|
||||
private static final int MULTIPLICATION_FACTOR = 3;
|
||||
|
||||
/**
|
||||
* main method.
|
||||
*
|
||||
* @param args as arguments for the main method.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var app = new App();
|
||||
app.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// The target object for this example.
|
||||
var sword = new SwordOfAragorn();
|
||||
// Creation of creatures.
|
||||
List<Creature> creatures = new ArrayList<>();
|
||||
for (var i = 0; i < WORKERS; i++) {
|
||||
creatures.add(new Elf(String.format("Elf %s", i)));
|
||||
creatures.add(new Orc(String.format("Orc %s", i)));
|
||||
creatures.add(new Human(String.format("Human %s", i)));
|
||||
}
|
||||
int totalFiends = WORKERS * MULTIPLICATION_FACTOR;
|
||||
ExecutorService service = Executors.newFixedThreadPool(totalFiends);
|
||||
// Attach every creature and the sword is a Fiend to fight for the sword.
|
||||
for (var i = 0; i < totalFiends; i = i + MULTIPLICATION_FACTOR) {
|
||||
service.submit(new Feind(creatures.get(i), sword));
|
||||
service.submit(new Feind(creatures.get(i + 1), sword));
|
||||
service.submit(new Feind(creatures.get(i + 2), sword));
|
||||
}
|
||||
// Wait for program to terminate.
|
||||
try {
|
||||
if (!service.awaitTermination(WAIT_TIME, TimeUnit.SECONDS)) {
|
||||
LOGGER.info("The master of the sword is now {}.", sword.getLocker().getName());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
service.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Applicability
|
||||
|
||||
The Lockable Object pattern is ideal for non distributed applications, that needs to be thread-safe
|
||||
and keeping their domain models in memory(in contrast to persisted models such as databases).
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* [Lockable Object - Chapter 10.3, J2EE Design Patterns, O'Reilly](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf)
|
Binary file not shown.
Before Width: | Height: | Size: 113 KiB |
@ -1,94 +0,0 @@
|
||||
@startuml
|
||||
package com.iluwatar.lockableobject.domain {
|
||||
abstract class Creature {
|
||||
- LOGGER : Logger {static}
|
||||
- damage : int
|
||||
- health : int
|
||||
~ instruments : Set<Lockable>
|
||||
- name : String
|
||||
- type : CreatureType
|
||||
+ Creature(name : String)
|
||||
+ acquire(lockable : Lockable) : boolean
|
||||
+ attack(creature : Creature)
|
||||
# canEqual(other : Object) : boolean
|
||||
+ equals(o : Object) : boolean
|
||||
+ getDamage() : int
|
||||
+ getHealth() : int
|
||||
+ getInstruments() : Set<Lockable>
|
||||
+ getName() : String
|
||||
+ getType() : CreatureType
|
||||
+ hashCode() : int
|
||||
+ hit(damage : int)
|
||||
+ isAlive() : boolean
|
||||
+ kill()
|
||||
+ setDamage(damage : int)
|
||||
+ setHealth(health : int)
|
||||
+ setInstruments(instruments : Set<Lockable>)
|
||||
+ setName(name : String)
|
||||
+ setType(type : CreatureType)
|
||||
+ toString() : String
|
||||
}
|
||||
enum CreatureType {
|
||||
+ ELF {static}
|
||||
+ HUMAN {static}
|
||||
+ ORC {static}
|
||||
+ valueOf(name : String) : CreatureType {static}
|
||||
+ values() : CreatureType[] {static}
|
||||
}
|
||||
class Elf {
|
||||
+ Elf(name : String)
|
||||
}
|
||||
class Feind {
|
||||
- LOGGER : Logger {static}
|
||||
- feind : Creature
|
||||
- target : Lockable
|
||||
+ Feind(feind : Creature, target : Lockable)
|
||||
- fightForTheSword(reacher : Creature, holder : Creature, sword : Lockable)
|
||||
+ run()
|
||||
}
|
||||
class Human {
|
||||
+ Human(name : String)
|
||||
}
|
||||
class Orc {
|
||||
+ Orc(name : String)
|
||||
}
|
||||
}
|
||||
package com.iluwatar.lockableobject {
|
||||
class App {
|
||||
- LOGGER : Logger {static}
|
||||
- MULTIPLICATION_FACTOR : int {static}
|
||||
- WAIT_TIME : int {static}
|
||||
- WORKERS : int {static}
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
interface Lockable {
|
||||
+ getLocker() : Creature {abstract}
|
||||
+ getName() : String {abstract}
|
||||
+ isLocked() : boolean {abstract}
|
||||
+ lock(Creature) : boolean {abstract}
|
||||
+ unlock(Creature) {abstract}
|
||||
}
|
||||
class SwordOfAragorn {
|
||||
- LOGGER : Logger {static}
|
||||
- NAME : String {static}
|
||||
- locker : Creature
|
||||
- synchronizer : Object
|
||||
+ SwordOfAragorn()
|
||||
+ getLocker() : Creature
|
||||
+ getName() : String
|
||||
+ isLocked() : boolean
|
||||
+ lock(creature : Creature) : boolean
|
||||
+ unlock(creature : Creature)
|
||||
}
|
||||
}
|
||||
Creature --> "-type" CreatureType
|
||||
Creature --> "-instruments" Lockable
|
||||
Feind --> "-feind" Creature
|
||||
Feind --> "-target" Lockable
|
||||
SwordOfAragorn --> "-locker" Creature
|
||||
SwordOfAragorn ..|> Lockable
|
||||
Elf --|> Creature
|
||||
Human --|> Creature
|
||||
Orc --|> Creature
|
||||
@enduml
|
@ -1,60 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
The MIT License
|
||||
Copyright © 2014-2021 Ilkka Seppälä
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.25.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>lockable-object</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Maven assembly plugin is invoked with default setting which we have
|
||||
in parent pom and specifying the class having main method -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.lockableobject.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,77 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.Creature;
|
||||
import com.iluwatar.lockableobject.domain.Elf;
|
||||
import com.iluwatar.lockableobject.domain.Feind;
|
||||
import com.iluwatar.lockableobject.domain.Human;
|
||||
import com.iluwatar.lockableobject.domain.Orc;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The Lockable Object pattern is a concurrency pattern. Instead of using the "synchronized" word
|
||||
* upon the methods to be synchronized, the object which implements the Lockable interface handles
|
||||
* the request.
|
||||
*
|
||||
* <p>In this example, we create a new Lockable object with the SwordOfAragorn implementation of it.
|
||||
* Afterwards we create 6 Creatures with the Elf, Orc and Human implementations and assign them each
|
||||
* to a Fiend object and the Sword is the target object. Because there is only one Sword and it uses
|
||||
* the Lockable Object pattern, only one creature can hold the sword at a given time. When the sword
|
||||
* is locked, any other alive Fiends will try to lock, which will result in a race to lock the
|
||||
* sword.
|
||||
*
|
||||
* @author Noam Greenshtain
|
||||
*/
|
||||
@Slf4j
|
||||
public class App implements Runnable {
|
||||
|
||||
private static final int WAIT_TIME = 3;
|
||||
private static final int WORKERS = 2;
|
||||
private static final int MULTIPLICATION_FACTOR = 3;
|
||||
|
||||
/**
|
||||
* main method.
|
||||
*
|
||||
* @param args as arguments for the main method.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var app = new App();
|
||||
app.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// The target object for this example.
|
||||
var sword = new SwordOfAragorn();
|
||||
// Creation of creatures.
|
||||
List<Creature> creatures = new ArrayList<>();
|
||||
for (var i = 0; i < WORKERS; i++) {
|
||||
creatures.add(new Elf(String.format("Elf %s", i)));
|
||||
creatures.add(new Orc(String.format("Orc %s", i)));
|
||||
creatures.add(new Human(String.format("Human %s", i)));
|
||||
}
|
||||
int totalFiends = WORKERS * MULTIPLICATION_FACTOR;
|
||||
ExecutorService service = Executors.newFixedThreadPool(totalFiends);
|
||||
// Attach every creature and the sword is a Fiend to fight for the sword.
|
||||
for (var i = 0; i < totalFiends; i = i + MULTIPLICATION_FACTOR) {
|
||||
service.submit(new Feind(creatures.get(i), sword));
|
||||
service.submit(new Feind(creatures.get(i + 1), sword));
|
||||
service.submit(new Feind(creatures.get(i + 2), sword));
|
||||
}
|
||||
// Wait for program to terminate.
|
||||
try {
|
||||
if (!service.awaitTermination(WAIT_TIME, TimeUnit.SECONDS)) {
|
||||
LOGGER.info("The master of the sword is now {}.", sword.getLocker().getName());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
service.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.Creature;
|
||||
|
||||
/** This interface describes the methods to be supported by a lockable-object. */
|
||||
public interface Lockable {
|
||||
|
||||
/**
|
||||
* Checks if the object is locked.
|
||||
*
|
||||
* @return true if it is locked.
|
||||
*/
|
||||
boolean isLocked();
|
||||
|
||||
/**
|
||||
* locks the object with the creature as the locker.
|
||||
*
|
||||
* @param creature as the locker.
|
||||
* @return true if the object was locked successfully.
|
||||
*/
|
||||
boolean lock(Creature creature);
|
||||
|
||||
/**
|
||||
* Unlocks the object.
|
||||
*
|
||||
* @param creature as the locker.
|
||||
*/
|
||||
void unlock(Creature creature);
|
||||
|
||||
/**
|
||||
* Gets the locker.
|
||||
*
|
||||
* @return the Creature that holds the object. Returns null if no one is locking.
|
||||
*/
|
||||
Creature getLocker();
|
||||
|
||||
/**
|
||||
* Returns the name of the object.
|
||||
*
|
||||
* @return the name of the object.
|
||||
*/
|
||||
String getName();
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
/**
|
||||
* An exception regarding the locking process of a Lockable object.
|
||||
*/
|
||||
public class LockingException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 8556381044865867037L;
|
||||
|
||||
public LockingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.Creature;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* An implementation of a Lockable object. This is the the Sword of Aragorn and every creature wants
|
||||
* to posses it!
|
||||
*/
|
||||
@Slf4j
|
||||
public class SwordOfAragorn implements Lockable {
|
||||
|
||||
private Creature locker;
|
||||
private final Object synchronizer;
|
||||
private static final String NAME = "The Sword of Aragorn";
|
||||
|
||||
public SwordOfAragorn() {
|
||||
this.locker = null;
|
||||
this.synchronizer = new Object();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocked() {
|
||||
return this.locker != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lock(@NonNull Creature creature) {
|
||||
synchronized (synchronizer) {
|
||||
LOGGER.info("{} is now trying to acquire {}!", creature.getName(), this.getName());
|
||||
if (!isLocked()) {
|
||||
locker = creature;
|
||||
return true;
|
||||
} else {
|
||||
if (!locker.getName().equals(creature.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(@NonNull Creature creature) {
|
||||
synchronized (synchronizer) {
|
||||
if (locker != null && locker.getName().equals(creature.getName())) {
|
||||
locker = null;
|
||||
LOGGER.info("{} is now free!", this.getName());
|
||||
}
|
||||
if (locker != null) {
|
||||
throw new LockingException("You cannot unlock an object you are not the owner of.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Creature getLocker() {
|
||||
return this.locker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
import com.iluwatar.lockableobject.Lockable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* An abstract class of a creature that wanders across the wasteland. It can attack, get hit and
|
||||
* acquire a Lockable object.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Slf4j
|
||||
public abstract class Creature {
|
||||
|
||||
private String name;
|
||||
private CreatureType type;
|
||||
private int health;
|
||||
private int damage;
|
||||
Set<Lockable> instruments;
|
||||
|
||||
protected Creature(@NonNull String name) {
|
||||
this.name = name;
|
||||
this.instruments = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaches for the Lockable and tried to hold it.
|
||||
*
|
||||
* @param lockable as the Lockable to lock.
|
||||
* @return true of Lockable was locked by this creature.
|
||||
*/
|
||||
public boolean acquire(@NonNull Lockable lockable) {
|
||||
if (lockable.lock(this)) {
|
||||
instruments.add(lockable);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Terminates the Creature and unlocks all of the Lockable that it posses. */
|
||||
public synchronized void kill() {
|
||||
LOGGER.info("{} {} has been slayed!", type, name);
|
||||
for (Lockable lockable : instruments) {
|
||||
lockable.unlock(this);
|
||||
}
|
||||
this.instruments.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks a foe.
|
||||
*
|
||||
* @param creature as the foe to be attacked.
|
||||
*/
|
||||
public synchronized void attack(@NonNull Creature creature) {
|
||||
creature.hit(getDamage());
|
||||
}
|
||||
|
||||
/**
|
||||
* When a creature gets hit it removed the amount of damage from the creature's life.
|
||||
*
|
||||
* @param damage as the damage that was taken.
|
||||
*/
|
||||
public synchronized void hit(int damage) {
|
||||
if (damage < 0) {
|
||||
throw new IllegalArgumentException("Damage cannot be a negative number");
|
||||
}
|
||||
if (isAlive()) {
|
||||
setHealth(getHealth() - damage);
|
||||
if (!isAlive()) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the creature is still alive.
|
||||
*
|
||||
* @return true of creature is alive.
|
||||
*/
|
||||
public synchronized boolean isAlive() {
|
||||
return getHealth() > 0;
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
/** Attribute constants of each Creature implementation. */
|
||||
public enum CreatureStats {
|
||||
ELF_HEALTH(90),
|
||||
ELF_DAMAGE(40),
|
||||
ORC_HEALTH(70),
|
||||
ORC_DAMAGE(50),
|
||||
HUMAN_HEALTH(60),
|
||||
HUMAN_DAMAGE(60);
|
||||
|
||||
int value;
|
||||
|
||||
private CreatureStats(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
/** Constants of supported creatures. */
|
||||
public enum CreatureType {
|
||||
ORC,
|
||||
HUMAN,
|
||||
ELF
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
/** An Elf implementation of a Creature. */
|
||||
public class Elf extends Creature {
|
||||
|
||||
/**
|
||||
* A constructor that initializes the attributes of an elf.
|
||||
*
|
||||
* @param name as the name of the creature.
|
||||
*/
|
||||
public Elf(String name) {
|
||||
super(name);
|
||||
setType(CreatureType.ELF);
|
||||
setDamage(CreatureStats.ELF_DAMAGE.getValue());
|
||||
setHealth(CreatureStats.ELF_HEALTH.getValue());
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
import com.iluwatar.lockableobject.Lockable;
|
||||
import java.security.SecureRandom;
|
||||
import lombok.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** A Feind is a creature that all it wants is to posses a Lockable object. */
|
||||
public class Feind implements Runnable {
|
||||
|
||||
private final Creature creature;
|
||||
private final Lockable target;
|
||||
private final SecureRandom random;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Feind.class.getName());
|
||||
|
||||
/**
|
||||
* public constructor.
|
||||
*
|
||||
* @param feind as the creature to lock to he lockable.
|
||||
* @param target as the target object.
|
||||
*/
|
||||
public Feind(@NonNull Creature feind, @NonNull Lockable target) {
|
||||
this.creature = feind;
|
||||
this.target = target;
|
||||
this.random = new SecureRandom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!creature.acquire(target)) {
|
||||
try {
|
||||
fightForTheSword(creature, target.getLocker(), target);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("{} has acquired the sword!", target.getLocker().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps on fighting until the Lockable is possessed.
|
||||
*
|
||||
* @param reacher as the source creature.
|
||||
* @param holder as the foe.
|
||||
* @param sword as the Lockable to posses.
|
||||
* @throws InterruptedException in case of interruption.
|
||||
*/
|
||||
private void fightForTheSword(Creature reacher, @NonNull Creature holder, Lockable sword)
|
||||
throws InterruptedException {
|
||||
LOGGER.info("A duel between {} and {} has been started!", reacher.getName(), holder.getName());
|
||||
boolean randBool;
|
||||
while (this.target.isLocked() && reacher.isAlive() && holder.isAlive()) {
|
||||
randBool = random.nextBoolean();
|
||||
if (randBool) {
|
||||
reacher.attack(holder);
|
||||
} else {
|
||||
holder.attack(reacher);
|
||||
}
|
||||
}
|
||||
if (reacher.isAlive()) {
|
||||
if (!reacher.acquire(sword)) {
|
||||
fightForTheSword(reacher, sword.getLocker(), sword);
|
||||
} else {
|
||||
LOGGER.info("{} has acquired the sword!", reacher.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
/** A human implementation of a Creature. */
|
||||
public class Human extends Creature {
|
||||
|
||||
/**
|
||||
* A constructor that initializes the attributes of an human.
|
||||
*
|
||||
* @param name as the name of the creature.
|
||||
*/
|
||||
public Human(String name) {
|
||||
super(name);
|
||||
setType(CreatureType.HUMAN);
|
||||
setDamage(CreatureStats.HUMAN_DAMAGE.getValue());
|
||||
setHealth(CreatureStats.HUMAN_HEALTH.getValue());
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package com.iluwatar.lockableobject.domain;
|
||||
|
||||
/** A Orc implementation of a Creature. */
|
||||
public class Orc extends Creature {
|
||||
/**
|
||||
* A constructor that initializes the attributes of an orc.
|
||||
*
|
||||
* @param name as the name of the creature.
|
||||
*/
|
||||
public Orc(String name) {
|
||||
super(name);
|
||||
setType(CreatureType.ORC);
|
||||
setDamage(CreatureStats.ORC_DAMAGE.getValue());
|
||||
setHealth(CreatureStats.ORC_HEALTH.getValue());
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[] {}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationAsRunnableWithoutException() {
|
||||
assertDoesNotThrow(() -> (new App()).run());
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.Creature;
|
||||
import com.iluwatar.lockableobject.domain.CreatureStats;
|
||||
import com.iluwatar.lockableobject.domain.CreatureType;
|
||||
import com.iluwatar.lockableobject.domain.Elf;
|
||||
import com.iluwatar.lockableobject.domain.Orc;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CreatureTest {
|
||||
|
||||
private Creature orc;
|
||||
private Creature elf;
|
||||
private Lockable sword;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
elf = new Elf("Elf test");
|
||||
orc = new Orc("Orc test");
|
||||
sword = new SwordOfAragorn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void baseTest() {
|
||||
Assertions.assertEquals("Elf test", elf.getName());
|
||||
Assertions.assertEquals(CreatureType.ELF, elf.getType());
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new Elf(null));
|
||||
Assertions.assertThrows(NullPointerException.class, () -> elf.acquire(null));
|
||||
Assertions.assertThrows(NullPointerException.class, () -> elf.attack(null));
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> elf.hit(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hitTest() {
|
||||
elf.hit(CreatureStats.ELF_HEALTH.getValue() / 2);
|
||||
Assertions.assertEquals(CreatureStats.ELF_HEALTH.getValue() / 2, elf.getHealth());
|
||||
elf.hit(CreatureStats.ELF_HEALTH.getValue() / 2);
|
||||
Assertions.assertFalse(elf.isAlive());
|
||||
|
||||
Assertions.assertEquals(0, orc.getInstruments().size());
|
||||
Assertions.assertTrue(orc.acquire(sword));
|
||||
Assertions.assertEquals(1, orc.getInstruments().size());
|
||||
orc.kill();
|
||||
Assertions.assertEquals(0, orc.getInstruments().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFight() throws InterruptedException {
|
||||
killCreature(elf, orc);
|
||||
Assertions.assertTrue(elf.isAlive());
|
||||
Assertions.assertFalse(orc.isAlive());
|
||||
Assertions.assertTrue(elf.getHealth() > 0);
|
||||
Assertions.assertTrue(orc.getHealth() <= 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAcqusition() throws InterruptedException {
|
||||
Assertions.assertTrue(elf.acquire(sword));
|
||||
Assertions.assertEquals(elf.getName(), sword.getLocker().getName());
|
||||
Assertions.assertTrue(elf.getInstruments().contains(sword));
|
||||
Assertions.assertFalse(orc.acquire(sword));
|
||||
killCreature(orc, elf);
|
||||
Assertions.assertTrue(orc.acquire(sword));
|
||||
Assertions.assertEquals(orc, sword.getLocker());
|
||||
}
|
||||
|
||||
void killCreature(Creature source, Creature target) throws InterruptedException {
|
||||
while (target.isAlive()) {
|
||||
source.attack(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidDamageTest(){
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> elf.hit(-50));
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ExceptionsTest {
|
||||
|
||||
private String msg = "test";
|
||||
|
||||
@Test
|
||||
void testException(){
|
||||
Exception e;
|
||||
try{
|
||||
throw new LockingException(msg);
|
||||
}
|
||||
catch(LockingException ex){
|
||||
e = ex;
|
||||
}
|
||||
Assertions.assertEquals(msg, e.getMessage());
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.Creature;
|
||||
import com.iluwatar.lockableobject.domain.Elf;
|
||||
import com.iluwatar.lockableobject.domain.Feind;
|
||||
import com.iluwatar.lockableobject.domain.Orc;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeindTest {
|
||||
|
||||
private Creature elf;
|
||||
private Creature orc;
|
||||
private Lockable sword;
|
||||
|
||||
@BeforeEach
|
||||
void init(){
|
||||
elf = new Elf("Nagdil");
|
||||
orc = new Orc("Ghandar");
|
||||
sword = new SwordOfAragorn();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullTests(){
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new Feind(null, null));
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new Feind(elf, null));
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new Feind(null, sword));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBaseCase() throws InterruptedException {
|
||||
var base = new Thread(new Feind(orc, sword));
|
||||
Assertions.assertNull(sword.getLocker());
|
||||
base.start();
|
||||
base.join();
|
||||
Assertions.assertEquals(orc, sword.getLocker());
|
||||
var extend = new Thread(new Feind(elf, sword));
|
||||
extend.start();
|
||||
extend.join();
|
||||
Assertions.assertTrue(sword.isLocked());
|
||||
|
||||
sword.unlock(elf.isAlive() ? elf : orc);
|
||||
Assertions.assertNull(sword.getLocker());
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.CreatureStats;
|
||||
import com.iluwatar.lockableobject.domain.Elf;
|
||||
import com.iluwatar.lockableobject.domain.Human;
|
||||
import com.iluwatar.lockableobject.domain.Orc;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SubCreaturesTests {
|
||||
|
||||
@Test
|
||||
void statsTest(){
|
||||
var elf = new Elf("Limbar");
|
||||
var orc = new Orc("Dargal");
|
||||
var human = new Human("Jerry");
|
||||
Assertions.assertEquals(CreatureStats.ELF_HEALTH.getValue(), elf.getHealth());
|
||||
Assertions.assertEquals(CreatureStats.ELF_DAMAGE.getValue(), elf.getDamage());
|
||||
Assertions.assertEquals(CreatureStats.ORC_DAMAGE.getValue(), orc.getDamage());
|
||||
Assertions.assertEquals(CreatureStats.ORC_HEALTH.getValue(), orc.getHealth());
|
||||
Assertions.assertEquals(CreatureStats.HUMAN_DAMAGE.getValue(), human.getDamage());
|
||||
Assertions.assertEquals(CreatureStats.HUMAN_HEALTH.getValue(), human.getHealth());
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.iluwatar.lockableobject;
|
||||
|
||||
import com.iluwatar.lockableobject.domain.Human;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class TheSwordOfAragornTest {
|
||||
|
||||
@Test
|
||||
void basicSwordTest() {
|
||||
var sword = new SwordOfAragorn();
|
||||
Assertions.assertNotNull(sword.getName());
|
||||
Assertions.assertNull(sword.getLocker());
|
||||
Assertions.assertFalse(sword.isLocked());
|
||||
var human = new Human("Tupac");
|
||||
Assertions.assertTrue(human.acquire(sword));
|
||||
Assertions.assertEquals(human, sword.getLocker());
|
||||
Assertions.assertTrue(sword.isLocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidLockerTest(){
|
||||
var sword = new SwordOfAragorn();
|
||||
Assertions.assertThrows(NullPointerException.class, () -> sword.lock(null));
|
||||
Assertions.assertThrows(NullPointerException.class, () -> sword.unlock(null));
|
||||
}
|
||||
}
|
10
pom.xml
10
pom.xml
@ -39,7 +39,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<sonar-maven-plugin.version>3.8.0.2131</sonar-maven-plugin.version>
|
||||
<hibernate.version>5.2.18.Final</hibernate.version>
|
||||
<spring.version>5.0.17.RELEASE</spring.version>
|
||||
<spring.version>5.0.13.RELEASE</spring.version>
|
||||
<spring-boot.version>2.0.9.RELEASE</spring-boot.version>
|
||||
<spring-data.version>2.0.14.RELEASE</spring-data.version>
|
||||
<h2.version>1.4.190</h2.version>
|
||||
@ -60,7 +60,7 @@
|
||||
<aws-lambda-core.version>1.1.0</aws-lambda-core.version>
|
||||
<aws-java-sdk-dynamodb.version>1.11.289</aws-java-sdk-dynamodb.version>
|
||||
<aws-lambda-java-events.version>2.0.1</aws-lambda-java-events.version>
|
||||
<jackson.version>2.12.3</jackson.version>
|
||||
<jackson.version>2.10.2</jackson.version>
|
||||
<jaxb-api.version>2.3.1</jaxb-api.version>
|
||||
<jaxb-impl.version>2.3.2</jaxb-impl.version>
|
||||
<annotation-api.version>1.3.2</annotation-api.version>
|
||||
@ -75,7 +75,7 @@
|
||||
<directory-maven-plugin.version>0.3.1</directory-maven-plugin.version>
|
||||
<license-maven-plugin.version>3.0</license-maven-plugin.version>
|
||||
<urm-maven-plugin.version>1.4.8</urm-maven-plugin.version>
|
||||
<commons-io.version>2.7</commons-io.version>
|
||||
<commons-io.version>2.6</commons-io.version>
|
||||
|
||||
<!-- SonarCloud -->
|
||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||
@ -224,10 +224,6 @@
|
||||
<module>parameter-object</module>
|
||||
<module>active-object</module>
|
||||
<module>model-view-viewmodel</module>
|
||||
<module>composite-entity</module>
|
||||
<module>table-module</module>
|
||||
<module>presentation</module>
|
||||
<module>lockable-object</module>
|
||||
</modules>
|
||||
|
||||
<repositories>
|
||||
|
@ -1,193 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Presentation
|
||||
folder: presentation
|
||||
permalink: /patterns/presentation/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
## Also known as
|
||||
Application Model
|
||||
|
||||
## Intent
|
||||
Presentation Model pulls the state and behavior of the view out into a model class that is part of the presentation.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> When we need to write a program with GUI, there is no need for us to put all presentation behavior in the view class. Because it will test become harder. So we can use Presentation Model Pattern to separate the behavior and view. The view only need to load the data and states from other class and show these data on the screen according to the states.
|
||||
|
||||
In plain words
|
||||
|
||||
> a pattern that used to divide the presentation and controlling.
|
||||
|
||||
Code Example
|
||||
|
||||
Class `view` is the GUI of albums. Methods `saveToPMod` and `loadFromPMod` are used to achieve synchronization.
|
||||
|
||||
```java
|
||||
public class View {
|
||||
/**
|
||||
* the model that controls this view.
|
||||
*/
|
||||
private final PresentationModel model;
|
||||
|
||||
private TextField txtTitle;
|
||||
private TextField txtArtist;
|
||||
private JCheckBox chkClassical;
|
||||
private TextField txtComposer;
|
||||
private JList<String> albumList;
|
||||
private JButton apply;
|
||||
private JButton cancel;
|
||||
|
||||
public View() {
|
||||
model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* save the data to PresentationModel.
|
||||
*/
|
||||
public void saveToPMod() {
|
||||
LOGGER.info("Save data to PresentationModel");
|
||||
model.setArtist(txtArtist.getText());
|
||||
model.setTitle(txtTitle.getText());
|
||||
model.setIsClassical(chkClassical.isSelected());
|
||||
model.setComposer(txtComposer.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* load the data from PresentationModel.
|
||||
*/
|
||||
public void loadFromPMod() {
|
||||
LOGGER.info("Load data from PresentationModel");
|
||||
txtArtist.setText(model.getArtist());
|
||||
txtTitle.setText(model.getTitle());
|
||||
chkClassical.setSelected(model.getIsClassical());
|
||||
txtComposer.setEditable(model.getIsClassical());
|
||||
txtComposer.setText(model.getComposer());
|
||||
}
|
||||
|
||||
public void createView() {
|
||||
// the detail of GUI information like size, listenser and so on.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Class `Album` is to store information of a album.
|
||||
|
||||
```java
|
||||
public class Album {
|
||||
|
||||
private String title;
|
||||
private String artist;
|
||||
private boolean isClassical;
|
||||
/**
|
||||
* only when the album is classical,
|
||||
* composer can have content.
|
||||
*/
|
||||
private String composer;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Class `DisplatedAlbums` is store the information of all the albums that will be displayed on GUI.
|
||||
|
||||
```java
|
||||
public class DisplayedAlbums {
|
||||
private final List<Album> albums;
|
||||
|
||||
public DisplayedAlbums() {
|
||||
this.albums = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addAlbums(final String title,
|
||||
final String artist, final boolean isClassical,
|
||||
final String composer) {
|
||||
if (isClassical) {
|
||||
this.albums.add(new Album(title, artist, true, composer));
|
||||
} else {
|
||||
this.albums.add(new Album(title, artist, false, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Class `PresentationMod` is used to control all the action of GUI.
|
||||
|
||||
```java
|
||||
public class PresentationModel {
|
||||
private final DisplayedAlbums data;
|
||||
|
||||
private int selectedAlbumNumber;
|
||||
private Album selectedAlbum;
|
||||
|
||||
public PresentationModel(final DisplayedAlbums dataOfAlbums) {
|
||||
this.data = dataOfAlbums;
|
||||
this.selectedAlbumNumber = 1;
|
||||
this.selectedAlbum = this.data.getAlbums().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of selectedAlbumNumber.
|
||||
*
|
||||
* @param albumNumber the number of album which is shown on the view.
|
||||
*/
|
||||
public void setSelectedAlbumNumber(final int albumNumber) {
|
||||
LOGGER.info("Change select number from {} to {}",
|
||||
this.selectedAlbumNumber, albumNumber);
|
||||
this.selectedAlbumNumber = albumNumber;
|
||||
this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return selectedAlbum.getTitle();
|
||||
}
|
||||
// other get methods are like this, which are used to get information of selected album.
|
||||
|
||||
public void setTitle(final String value) {
|
||||
LOGGER.info("Change album title from {} to {}",
|
||||
selectedAlbum.getTitle(), value);
|
||||
selectedAlbum.setTitle(value);
|
||||
}
|
||||
// other set methods are like this, which are used to get information of selected album.
|
||||
|
||||
/**
|
||||
* Gets a list of albums.
|
||||
*
|
||||
* @return the names of all the albums.
|
||||
*/
|
||||
public String[] getAlbumList() {
|
||||
var result = new String[data.getAlbums().size()];
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
result[i] = data.getAlbums().get(i).getTitle();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can run class `App` to start this demo. the checkbox is the album classical; the first text field is the name of album artist; the second is the name of album title; the last one is the name of the composer:
|
||||
|
||||

|
||||
|
||||
|
||||
## Class diagram
|
||||

|
||||
|
||||
## Applicability
|
||||
Use the Presentation Model Pattern when
|
||||
|
||||
* Testing a presentation through a GUI window is often awkward, and in some cases impossible.
|
||||
* Do not determine which GUI will be used.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- [Supervising Controller](https://martinfowler.com/eaaDev/SupervisingPresenter.html)
|
||||
- [Passive View](https://martinfowler.com/eaaDev/PassiveScreen.html)
|
||||
|
||||
## Credits
|
||||
|
||||
* [Presentation Model Patterns](https://martinfowler.com/eaaDev/PresentationModel.html)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 121 KiB |
@ -1,59 +0,0 @@
|
||||
@startuml
|
||||
package com.iluwatar.presentation {
|
||||
class Album {
|
||||
~ artist : String
|
||||
~ composer : String
|
||||
~ isClassical : boolean
|
||||
~ rowId : int
|
||||
~ title : String
|
||||
+ Album(rowId : int, title : String, artist : String, isClassical : boolean, composer : String)
|
||||
}
|
||||
class App {
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
class DsAlbum {
|
||||
+ albums : List<Album>
|
||||
+ albumsCache : List<Album>
|
||||
+ DsAlbum()
|
||||
+ acceptChanges()
|
||||
+ addAlbums(rowId : int, title : String, artist : String, isClassical : boolean, composer : String)
|
||||
}
|
||||
class PresentationMod {
|
||||
- data : DsAlbum
|
||||
- selectedAlbum : Album
|
||||
- selectedAlbumNumber : int
|
||||
+ PresentationMod(data : DsAlbum)
|
||||
+ albumDataSet() : DsAlbum {static}
|
||||
+ getAlbumList() : String[]
|
||||
+ getArtist() : String
|
||||
+ getComposer() : String
|
||||
+ getIsClassical() : boolean
|
||||
+ getTitle() : String
|
||||
+ setArtist(value : String)
|
||||
+ setComposer(value : String)
|
||||
+ setIsClassical(value : boolean)
|
||||
+ setSelectedAlbumNumber(selectedAlbumNumber : int)
|
||||
+ setTitle(value : String)
|
||||
}
|
||||
class View {
|
||||
~ albumList : JList<String>
|
||||
~ apply : JButton
|
||||
~ cancel : JButton
|
||||
~ chkClassical : JCheckBox
|
||||
~ model : PresentationMod
|
||||
~ notLoadView : boolean
|
||||
~ txtArtist : TextField
|
||||
~ txtComposer : TextField
|
||||
~ txtTitle : TextField
|
||||
+ View()
|
||||
+ createView()
|
||||
+ loadFromPMod()
|
||||
+ saveToPMod()
|
||||
}
|
||||
}
|
||||
PresentationMod --> "-selectedAlbum" Album
|
||||
View --> "-model" PresentationMod
|
||||
DsAlbum --> "-albums" Album
|
||||
PresentationMod --> "-data" DsAlbum
|
||||
@enduml
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
@ -1,63 +0,0 @@
|
||||
<!--
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2021 Ilkka Seppälä
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.25.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>presentation</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Maven assembly plugin is invoked with default setting which we have
|
||||
in parent pom and specifying the class having main method -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.presentation.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -1,31 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
*A class used to store the information of album.
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class Album {
|
||||
/**
|
||||
* the title of the album.
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* the artist name of the album.
|
||||
*/
|
||||
private String artist;
|
||||
/**
|
||||
* is the album classical, true or false.
|
||||
*/
|
||||
private boolean isClassical;
|
||||
/**
|
||||
* only when the album is classical,
|
||||
* composer can have content.
|
||||
*/
|
||||
private String composer;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The Presentation model pattern is used to divide the presentation and controlling.
|
||||
* This demo is a used to information of some albums with GUI.
|
||||
*/
|
||||
@Slf4j
|
||||
public final class App {
|
||||
/**
|
||||
* the constructor.
|
||||
*/
|
||||
private App() {
|
||||
}
|
||||
|
||||
/**
|
||||
* main method.
|
||||
*
|
||||
* @param args args
|
||||
*/
|
||||
public static void main(final String[] args) {
|
||||
var view = new View();
|
||||
view.createView();
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* a class used to deal with albums.
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class DisplayedAlbums {
|
||||
/**
|
||||
* albums a list of albums.
|
||||
*/
|
||||
private final List<Album> albums;
|
||||
|
||||
/**
|
||||
* a constructor method.
|
||||
*/
|
||||
public DisplayedAlbums() {
|
||||
this.albums = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* a method used to add a new album to album list.
|
||||
*
|
||||
* @param title the title of the album.
|
||||
* @param artist the artist name of the album.
|
||||
* @param isClassical is the album classical, true or false.
|
||||
* @param composer only when the album is classical,
|
||||
* composer can have content.
|
||||
*/
|
||||
public void addAlbums(final String title,
|
||||
final String artist, final boolean isClassical,
|
||||
final String composer) {
|
||||
if (isClassical) {
|
||||
this.albums.add(new Album(title, artist, true, composer));
|
||||
} else {
|
||||
this.albums.add(new Album(title, artist, false, ""));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* The class between view and albums, it is used to control the data.
|
||||
*/
|
||||
@Slf4j
|
||||
public class PresentationModel {
|
||||
/**
|
||||
* the data of all albums that will be shown.
|
||||
*/
|
||||
private final DisplayedAlbums data;
|
||||
/**
|
||||
* the no of selected album.
|
||||
*/
|
||||
private int selectedAlbumNumber;
|
||||
/**
|
||||
* the selected album.
|
||||
*/
|
||||
private Album selectedAlbum;
|
||||
|
||||
/**
|
||||
* Generates a set of data for testing.
|
||||
*
|
||||
* @return a instance of DsAlbum which store the data.
|
||||
*/
|
||||
public static DisplayedAlbums albumDataSet() {
|
||||
var titleList = new String[]{"HQ", "The Rough Dancer and Cyclical Night",
|
||||
"The Black Light", "Symphony No.5"};
|
||||
var artistList = new String[]{"Roy Harper", "Astor Piazzola",
|
||||
"The Black Light", "CBSO"};
|
||||
var isClassicalList = new boolean[]{false, false, false, true};
|
||||
var composerList = new String[]{null, null, null, "Sibelius"};
|
||||
|
||||
var result = new DisplayedAlbums();
|
||||
for (var i = 1; i <= titleList.length; i++) {
|
||||
result.addAlbums(titleList[i - 1], artistList[i - 1],
|
||||
isClassicalList[i - 1], composerList[i - 1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor method.
|
||||
*
|
||||
* @param dataOfAlbums the data of all the albums
|
||||
*/
|
||||
public PresentationModel(final DisplayedAlbums dataOfAlbums) {
|
||||
this.data = dataOfAlbums;
|
||||
this.selectedAlbumNumber = 1;
|
||||
this.selectedAlbum = this.data.getAlbums().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value of selectedAlbumNumber.
|
||||
*
|
||||
* @param albumNumber the number of album which is shown on the view.
|
||||
*/
|
||||
public void setSelectedAlbumNumber(final int albumNumber) {
|
||||
LOGGER.info("Change select number from {} to {}",
|
||||
this.selectedAlbumNumber, albumNumber);
|
||||
this.selectedAlbumNumber = albumNumber;
|
||||
this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the title of selected album.
|
||||
*
|
||||
* @return the tile of selected album.
|
||||
*/
|
||||
public String getTitle() {
|
||||
return selectedAlbum.getTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* set the title of selected album.
|
||||
*
|
||||
* @param value the title which user want to user.
|
||||
*/
|
||||
public void setTitle(final String value) {
|
||||
LOGGER.info("Change album title from {} to {}",
|
||||
selectedAlbum.getTitle(), value);
|
||||
selectedAlbum.setTitle(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the artist of selected album.
|
||||
*
|
||||
* @return the artist of selected album.
|
||||
*/
|
||||
public String getArtist() {
|
||||
return selectedAlbum.getArtist();
|
||||
}
|
||||
|
||||
/**
|
||||
* set the name of artist.
|
||||
*
|
||||
* @param value the name want artist to be.
|
||||
*/
|
||||
public void setArtist(final String value) {
|
||||
LOGGER.info("Change album artist from {} to {}",
|
||||
selectedAlbum.getArtist(), value);
|
||||
selectedAlbum.setArtist(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean value which represents whether the album is classical.
|
||||
*
|
||||
* @return is the album classical.
|
||||
*/
|
||||
public boolean getIsClassical() {
|
||||
return selectedAlbum.isClassical();
|
||||
}
|
||||
|
||||
/**
|
||||
* set the isClassical of album.
|
||||
*
|
||||
* @param value is the album classical.
|
||||
*/
|
||||
public void setIsClassical(final boolean value) {
|
||||
LOGGER.info("Change album isClassical from {} to {}",
|
||||
selectedAlbum.isClassical(), value);
|
||||
selectedAlbum.setClassical(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* get is classical of the selected album.
|
||||
*
|
||||
* @return is the album classical.
|
||||
*/
|
||||
public String getComposer() {
|
||||
return selectedAlbum.isClassical() ? selectedAlbum.getComposer() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of composer when the album is classical.
|
||||
*
|
||||
* @param value the name of composer.
|
||||
*/
|
||||
public void setComposer(final String value) {
|
||||
if (selectedAlbum.isClassical()) {
|
||||
LOGGER.info("Change album composer from {} to {}",
|
||||
selectedAlbum.getComposer(), value);
|
||||
selectedAlbum.setComposer(value);
|
||||
} else {
|
||||
LOGGER.info("Composer can not be changed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of albums.
|
||||
*
|
||||
* @return the names of all the albums.
|
||||
*/
|
||||
public String[] getAlbumList() {
|
||||
var result = new String[data.getAlbums().size()];
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
result[i] = data.getAlbums().get(i).getTitle();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import java.awt.TextField;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JList;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Generates the GUI of albums.
|
||||
*/
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class View {
|
||||
/**
|
||||
* the model that controls this view.
|
||||
*/
|
||||
private final PresentationModel model;
|
||||
|
||||
/**
|
||||
* the filed to show and modify title.
|
||||
*/
|
||||
private TextField txtTitle;
|
||||
/**
|
||||
* the filed to show and modify the name of artist.
|
||||
*/
|
||||
private TextField txtArtist;
|
||||
/**
|
||||
* the checkbox for is classical.
|
||||
*/
|
||||
private JCheckBox chkClassical;
|
||||
/**
|
||||
* the filed to show and modify composer.
|
||||
*/
|
||||
private TextField txtComposer;
|
||||
/**
|
||||
* a list to show all the name of album.
|
||||
*/
|
||||
private JList<String> albumList;
|
||||
/**
|
||||
* a button to apply of all the change.
|
||||
*/
|
||||
private JButton apply;
|
||||
/**
|
||||
* roll back the change.
|
||||
*/
|
||||
private JButton cancel;
|
||||
|
||||
/**
|
||||
* the value of the text field size.
|
||||
*/
|
||||
static final int WIDTH_TXT = 200;
|
||||
static final int HEIGHT_TXT = 50;
|
||||
|
||||
/**
|
||||
* the value of the GUI size and location.
|
||||
*/
|
||||
static final int LOCATION_X = 200;
|
||||
static final int LOCATION_Y = 200;
|
||||
static final int WIDTH = 500;
|
||||
static final int HEIGHT = 300;
|
||||
|
||||
/**
|
||||
* constructor method.
|
||||
*/
|
||||
public View() {
|
||||
model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* save the data to PresentationModel.
|
||||
*/
|
||||
public void saveToPMod() {
|
||||
LOGGER.info("Save data to PresentationModel");
|
||||
model.setArtist(txtArtist.getText());
|
||||
model.setTitle(txtTitle.getText());
|
||||
model.setIsClassical(chkClassical.isSelected());
|
||||
model.setComposer(txtComposer.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* load the data from PresentationModel.
|
||||
*/
|
||||
public void loadFromPMod() {
|
||||
LOGGER.info("Load data from PresentationModel");
|
||||
txtArtist.setText(model.getArtist());
|
||||
txtTitle.setText(model.getTitle());
|
||||
chkClassical.setSelected(model.getIsClassical());
|
||||
txtComposer.setEditable(model.getIsClassical());
|
||||
txtComposer.setText(model.getComposer());
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize the GUI.
|
||||
*/
|
||||
public void createView() {
|
||||
var frame = new JFrame("Album");
|
||||
var b1 = Box.createHorizontalBox();
|
||||
|
||||
frame.add(b1);
|
||||
albumList = new JList<>(model.getAlbumList());
|
||||
albumList.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent e) {
|
||||
model.setSelectedAlbumNumber(albumList.getSelectedIndex() + 1);
|
||||
loadFromPMod();
|
||||
}
|
||||
});
|
||||
b1.add(albumList);
|
||||
|
||||
var b2 = Box.createVerticalBox();
|
||||
b1.add(b2);
|
||||
|
||||
txtArtist = new TextField();
|
||||
txtTitle = new TextField();
|
||||
|
||||
txtArtist.setSize(WIDTH_TXT, HEIGHT_TXT);
|
||||
txtTitle.setSize(WIDTH_TXT, HEIGHT_TXT);
|
||||
|
||||
chkClassical = new JCheckBox();
|
||||
txtComposer = new TextField();
|
||||
chkClassical.addActionListener(itemEvent -> {
|
||||
txtComposer.setEditable(chkClassical.isSelected());
|
||||
if (!chkClassical.isSelected()) {
|
||||
txtComposer.setText("");
|
||||
}
|
||||
});
|
||||
txtComposer.setSize(WIDTH_TXT, HEIGHT_TXT);
|
||||
txtComposer.setEditable(model.getIsClassical());
|
||||
|
||||
apply = new JButton("Apply");
|
||||
apply.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent e) {
|
||||
saveToPMod();
|
||||
loadFromPMod();
|
||||
}
|
||||
});
|
||||
cancel = new JButton("Cancel");
|
||||
cancel.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent e) {
|
||||
loadFromPMod();
|
||||
}
|
||||
});
|
||||
|
||||
b2.add(txtArtist);
|
||||
b2.add(txtTitle);
|
||||
|
||||
b2.add(chkClassical);
|
||||
b2.add(txtComposer);
|
||||
|
||||
b2.add(apply);
|
||||
b2.add(cancel);
|
||||
|
||||
frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
||||
frame.setBounds(LOCATION_X, LOCATION_Y, WIDTH, HEIGHT);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class AlbumTest {
|
||||
@Test
|
||||
void testSetTitle(){
|
||||
Album album = new Album("a", "b", false, "");
|
||||
album.setTitle("b");
|
||||
assertEquals("b", album.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetArtist(){
|
||||
Album album = new Album("a", "b", false, "");
|
||||
album.setArtist("c");
|
||||
assertEquals("c", album.getArtist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetClassical(){
|
||||
Album album = new Album("a", "b", false, "");
|
||||
album.setClassical(true);
|
||||
assertTrue(album.isClassical());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetComposer(){
|
||||
Album album = new Album("a", "b", false, "");
|
||||
album.setClassical(true);
|
||||
album.setComposer("w");
|
||||
assertEquals("w", album.getComposer());
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
*
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App}
|
||||
* throws an exception.
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DisplayedAlbumsTest {
|
||||
@Test
|
||||
void testAdd_true(){
|
||||
DisplayedAlbums displayedAlbums = new DisplayedAlbums();
|
||||
displayedAlbums.addAlbums("title", "artist", true, "composer");
|
||||
assertEquals("composer", displayedAlbums.getAlbums().get(0).getComposer());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdd_false(){
|
||||
DisplayedAlbums displayedAlbums = new DisplayedAlbums();
|
||||
displayedAlbums.addAlbums("title", "artist", false, "composer");
|
||||
assertEquals("", displayedAlbums.getAlbums().get(0).getComposer());
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2021 Ilkka Seppälä
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PresentationTest {
|
||||
String[] albumList = {"HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5"};
|
||||
|
||||
@Test
|
||||
void testCreateAlbumList() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String[] list = model.getAlbumList();
|
||||
assertEquals(Arrays.toString(albumList), Arrays.toString(list));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetSelectedAlbumNumber_1() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
final int selectId = 2;
|
||||
model.setSelectedAlbumNumber(selectId);
|
||||
assertEquals(albumList[selectId - 1], model.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetSelectedAlbumNumber_2() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
final int selectId = 4;
|
||||
model.setSelectedAlbumNumber(selectId);
|
||||
assertEquals(albumList[selectId - 1], model.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetTitle_1() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String testTitle = "TestTile";
|
||||
model.setTitle(testTitle);
|
||||
assertEquals(testTitle, model.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetTitle_2() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String testTitle = "";
|
||||
model.setTitle(testTitle);
|
||||
assertEquals(testTitle, model.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetArtist_1() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String testArtist = "TestArtist";
|
||||
model.setArtist(testArtist);
|
||||
assertEquals(testArtist, model.getArtist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetArtist_2() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String testArtist = "";
|
||||
model.setArtist(testArtist);
|
||||
assertEquals(testArtist, model.getArtist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetIsClassical() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
model.setIsClassical(true);
|
||||
assertTrue(model.getIsClassical());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetComposer_false() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String testComposer = "TestComposer";
|
||||
|
||||
model.setIsClassical(false);
|
||||
model.setComposer(testComposer);
|
||||
assertEquals("", model.getComposer());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetComposer_true() {
|
||||
PresentationModel model = new PresentationModel(PresentationModel.albumDataSet());
|
||||
String testComposer = "TestComposer";
|
||||
|
||||
model.setIsClassical(true);
|
||||
model.setComposer(testComposer);
|
||||
assertEquals(testComposer, model.getComposer());
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package com.iluwatar.presentation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ViewTest {
|
||||
String[] albumList = {"HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5"};
|
||||
|
||||
@Test
|
||||
void testSave_setArtistAndTitle(){
|
||||
View view = new View();
|
||||
view.createView();
|
||||
String testTitle = "testTitle";
|
||||
String testArtist = "testArtist";
|
||||
view.getTxtArtist().setText(testArtist);
|
||||
view.getTxtTitle().setText(testTitle);
|
||||
view.saveToPMod();
|
||||
view.loadFromPMod();
|
||||
assertEquals(testTitle, view.getModel().getTitle());
|
||||
assertEquals(testArtist, view.getModel().getArtist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSave_setClassicalAndComposer(){
|
||||
View view = new View();
|
||||
view.createView();
|
||||
boolean isClassical = true;
|
||||
String testComposer = "testComposer";
|
||||
view.getChkClassical().setSelected(isClassical);
|
||||
view.getTxtComposer().setText(testComposer);
|
||||
view.saveToPMod();
|
||||
view.loadFromPMod();
|
||||
assertTrue(view.getModel().getIsClassical());
|
||||
assertEquals(testComposer, view.getModel().getComposer());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_1(){
|
||||
View view = new View();
|
||||
view.createView();
|
||||
view.getModel().setSelectedAlbumNumber(2);
|
||||
view.loadFromPMod();
|
||||
assertEquals(albumList[1], view.getModel().getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_2(){
|
||||
View view = new View();
|
||||
view.createView();
|
||||
view.getModel().setSelectedAlbumNumber(4);
|
||||
view.loadFromPMod();
|
||||
assertEquals(albumList[3], view.getModel().getTitle());
|
||||
}
|
||||
}
|
@ -131,10 +131,9 @@ public class App {
|
||||
* log requests.
|
||||
*/
|
||||
reactor
|
||||
.registerChannel(tcpChannel(16666, loggingHandler))
|
||||
.registerChannel(tcpChannel(16667, loggingHandler))
|
||||
.registerChannel(udpChannel(16668, loggingHandler))
|
||||
.registerChannel(udpChannel(16669, loggingHandler))
|
||||
.registerChannel(tcpChannel(6666, loggingHandler))
|
||||
.registerChannel(tcpChannel(6667, loggingHandler))
|
||||
.registerChannel(udpChannel(6668, loggingHandler))
|
||||
.start();
|
||||
}
|
||||
|
||||
|
@ -63,10 +63,10 @@ public class AppClient {
|
||||
*/
|
||||
public void start() throws IOException {
|
||||
LOGGER.info("Starting logging clients");
|
||||
service.execute(new TcpLoggingClient("Client 1", 16666));
|
||||
service.execute(new TcpLoggingClient("Client 2", 16667));
|
||||
service.execute(new UdpLoggingClient("Client 3", 16668));
|
||||
service.execute(new UdpLoggingClient("Client 4", 16669));
|
||||
service.execute(new TcpLoggingClient("Client 1", 6666));
|
||||
service.execute(new TcpLoggingClient("Client 2", 6667));
|
||||
service.execute(new UdpLoggingClient("Client 3", 6668));
|
||||
service.execute(new UdpLoggingClient("Client 4", 6668));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,7 +114,7 @@ public class AppClient {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (var socket = new Socket(InetAddress.getLocalHost(), serverPort)) {
|
||||
try (Socket socket = new Socket(InetAddress.getLocalHost(), serverPort)) {
|
||||
var outputStream = socket.getOutputStream();
|
||||
var writer = new PrintWriter(outputStream);
|
||||
sendLogRequests(writer, socket.getInputStream());
|
||||
|
@ -1,134 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Table Module
|
||||
folder: table-module
|
||||
permalink: /patterns/table-module/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Data access
|
||||
---
|
||||
## Intent
|
||||
Table Module organizes domain logic with one class per table in the database, and a single instance of a class contains the various procedures that will act on the data.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> When dealing with a user system, we need some operations on the user table. We can use the table module pattern in this scenario. We can create a class named UserTableModule and initialize a instance of that class to handle the business logic for all rows in the user table.
|
||||
|
||||
In plain words
|
||||
|
||||
> A single instance that handles the business logic for all rows in a database table or view.
|
||||
|
||||
Programmatic Example
|
||||
|
||||
In the example of the user system, we need to deal with the domain logic of user login and user registration. We can use the table module pattern and create an instance of the class `UserTableModule` to handle the business logic for all rows in the user table.
|
||||
|
||||
Here is the basic `User` entity.
|
||||
|
||||
```java
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@AllArgsConstructor
|
||||
public class User {
|
||||
private int id;
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
```
|
||||
|
||||
Here is the `UserTableModule` class.
|
||||
|
||||
```java
|
||||
public class UserTableModule {
|
||||
private final DataSource dataSource;
|
||||
private Connection connection = null;
|
||||
private ResultSet resultSet = null;
|
||||
private PreparedStatement preparedStatement = null;
|
||||
|
||||
public UserTableModule(final DataSource userDataSource) {
|
||||
this.dataSource = userDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login using username and password.
|
||||
*
|
||||
* @param username the username of a user
|
||||
* @param password the password of a user
|
||||
* @return the execution result of the method
|
||||
* @throws SQLException if any error
|
||||
*/
|
||||
public int login(final String username, final String password) throws SQLException {
|
||||
// Method implementation.
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user.
|
||||
*
|
||||
* @param user a user instance
|
||||
* @return the execution result of the method
|
||||
* @throws SQLException if any error
|
||||
*/
|
||||
public int registerUser(final User user) throws SQLException {
|
||||
// Method implementation.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the class `App`, we use an instance of the `UserTableModule` to handle user login and registration.
|
||||
|
||||
```java
|
||||
// Create data source and create the user table.
|
||||
final var dataSource = createDataSource();
|
||||
createSchema(dataSource);
|
||||
userTableModule = new UserTableModule(dataSource);
|
||||
|
||||
//Initialize two users.
|
||||
var user1 = new User(1, "123456", "123456");
|
||||
var user2 = new User(2, "test", "password");
|
||||
|
||||
//Login and register using the instance of userTableModule.
|
||||
userTableModule.registerUser(user1);
|
||||
userTableModule.login(user1.getUsername(), user1.getPassword());
|
||||
userTableModule.login(user2.getUsername(), user2.getPassword());
|
||||
userTableModule.registerUser(user2);
|
||||
userTableModule.login(user2.getUsername(), user2.getPassword());
|
||||
|
||||
deleteSchema(dataSource);
|
||||
```
|
||||
|
||||
The program output:
|
||||
|
||||
```java
|
||||
12:22:13.095 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully!
|
||||
12:22:13.117 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully!
|
||||
12:22:13.128 [main] INFO com.iluwatar.tablemodule.UserTableModule - Fail to login!
|
||||
12:22:13.136 [main] INFO com.iluwatar.tablemodule.UserTableModule - Register successfully!
|
||||
12:22:13.144 [main] INFO com.iluwatar.tablemodule.UserTableModule - Login successfully!
|
||||
```
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Table Module Pattern when
|
||||
|
||||
- Domain logic is simple and data is in tabular form.
|
||||
- The application only uses a few shared common table-oriented data structures.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- [Transaction Script](https://java-design-patterns.com/patterns/transaction-script/)
|
||||
|
||||
- Domain Model
|
||||
|
||||
## Credits
|
||||
|
||||
* [Table Module Pattern](http://wiki3.cosc.canterbury.ac.nz/index.php/Table_module_pattern)
|
||||
* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321127420&linkId=18acc13ba60d66690009505577c45c04)
|
||||
* [Architecture patterns: domain model and friends](https://inviqa.com/blog/architecture-patterns-domain-model-and-friends)
|
Binary file not shown.
Before Width: | Height: | Size: 76 KiB |
@ -1,38 +0,0 @@
|
||||
@startuml
|
||||
package com.iluwatar.tablemodule {
|
||||
class App {
|
||||
- DB_URL : String {static}
|
||||
- LOGGER : Logger {static}
|
||||
- App()
|
||||
- createDataSource() : DataSource {static}
|
||||
- createSchema(dataSource : DataSource) {static}
|
||||
- deleteSchema(dataSource : DataSource) {static}
|
||||
+ main(args : String[]) {static}
|
||||
}
|
||||
class User {
|
||||
- id : int
|
||||
- password : String
|
||||
- username : String
|
||||
+ User(id : int, username : String, password : String)
|
||||
# canEqual(other : Object) : boolean
|
||||
+ equals(o : Object) : boolean
|
||||
+ getId() : int
|
||||
+ getPassword() : String
|
||||
+ getUsername() : String
|
||||
+ hashCode() : int
|
||||
+ setId(id : int)
|
||||
+ setPassword(password : String)
|
||||
+ setUsername(username : String)
|
||||
+ toString() : String
|
||||
}
|
||||
class UserTableModule {
|
||||
+ CREATE_SCHEMA_SQL : String {static}
|
||||
+ DELETE_SCHEMA_SQL : String {static}
|
||||
- LOGGER : Logger {static}
|
||||
- dataSource : DataSource
|
||||
+ UserTableModule(userDataSource : DataSource)
|
||||
+ login(username : String, password : String) : int
|
||||
+ registerUser(user : User) : int
|
||||
}
|
||||
}
|
||||
@enduml
|
@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2021 Ilkka Seppälä
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<version>1.25.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>table-module</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.tablemodule.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -1,81 +0,0 @@
|
||||
package com.iluwatar.tablemodule;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
|
||||
|
||||
/**
|
||||
* Table Module pattern is a domain logic pattern.
|
||||
* In Table Module a single class encapsulates all the domain logic for all
|
||||
* records stored in a table or view. It's important to note that there is no
|
||||
* translation of data between objects and rows, as it happens in Domain Model,
|
||||
* hence implementation is relatively simple when compared to the Domain
|
||||
* Model pattern.
|
||||
*
|
||||
* <p>In this example we will use the Table Module pattern to implement register
|
||||
* and login methods for the records stored in the user table. The main
|
||||
* method will initialise an instance of {@link UserTableModule} and use it to
|
||||
* handle the domain logic for the user table.</p>
|
||||
*/
|
||||
@Slf4j
|
||||
public final class App {
|
||||
private static final String DB_URL = "jdbc:h2:~/test";
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private App() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args.
|
||||
* @throws SQLException if any error occurs.
|
||||
*/
|
||||
public static void main(final String[] args) throws SQLException {
|
||||
// Create data source and create the user table.
|
||||
final var dataSource = createDataSource();
|
||||
createSchema(dataSource);
|
||||
var userTableModule = new UserTableModule(dataSource);
|
||||
|
||||
// Initialize two users.
|
||||
var user1 = new User(1, "123456", "123456");
|
||||
var user2 = new User(2, "test", "password");
|
||||
|
||||
// Login and register using the instance of userTableModule.
|
||||
userTableModule.registerUser(user1);
|
||||
userTableModule.login(user1.getUsername(), user1.getPassword());
|
||||
userTableModule.login(user2.getUsername(), user2.getPassword());
|
||||
userTableModule.registerUser(user2);
|
||||
userTableModule.login(user2.getUsername(), user2.getPassword());
|
||||
|
||||
deleteSchema(dataSource);
|
||||
}
|
||||
|
||||
private static void deleteSchema(final DataSource dataSource)
|
||||
throws SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(UserTableModule.DELETE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createSchema(final DataSource dataSource)
|
||||
throws SQLException {
|
||||
try (var connection = dataSource.getConnection();
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(UserTableModule.CREATE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
private static DataSource createDataSource() {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setURL(DB_URL);
|
||||
return dataSource;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.iluwatar.tablemodule;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
|
||||
/**
|
||||
* A user POJO that represents the data that will be read from the data source.
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@AllArgsConstructor
|
||||
public class User {
|
||||
private int id;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package com.iluwatar.tablemodule;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* This class organizes domain logic with the user table in the
|
||||
* database. A single instance of this class contains the various
|
||||
* procedures that will act on the data.
|
||||
*/
|
||||
@Slf4j
|
||||
public class UserTableModule {
|
||||
/**
|
||||
* Public element for creating schema.
|
||||
*/
|
||||
public static final String CREATE_SCHEMA_SQL =
|
||||
"CREATE TABLE IF NOT EXISTS USERS (ID NUMBER, USERNAME VARCHAR(30) "
|
||||
+ "UNIQUE,PASSWORD VARCHAR(30))";
|
||||
/**
|
||||
* Public element for deleting schema.
|
||||
*/
|
||||
public static final String DELETE_SCHEMA_SQL = "DROP TABLE USERS IF EXISTS";
|
||||
private final DataSource dataSource;
|
||||
|
||||
|
||||
/**
|
||||
* Public constructor.
|
||||
*
|
||||
* @param userDataSource the data source in the database
|
||||
*/
|
||||
public UserTableModule(final DataSource userDataSource) {
|
||||
this.dataSource = userDataSource;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Login using username and password.
|
||||
*
|
||||
* @param username the username of a user
|
||||
* @param password the password of a user
|
||||
* @return the execution result of the method
|
||||
* @throws SQLException if any error
|
||||
*/
|
||||
public int login(final String username, final String password)
|
||||
throws SQLException {
|
||||
var sql = "select count(*) from USERS where username=? and password=?";
|
||||
ResultSet resultSet = null;
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement =
|
||||
connection.prepareStatement(sql)
|
||||
) {
|
||||
var result = 0;
|
||||
preparedStatement.setString(1, username);
|
||||
preparedStatement.setString(2, password);
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
while (resultSet.next()) {
|
||||
result = resultSet.getInt(1);
|
||||
}
|
||||
if (result == 1) {
|
||||
LOGGER.info("Login successfully!");
|
||||
} else {
|
||||
LOGGER.info("Fail to login!");
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
resultSet.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user.
|
||||
*
|
||||
* @param user a user instance
|
||||
* @return the execution result of the method
|
||||
* @throws SQLException if any error
|
||||
*/
|
||||
public int registerUser(final User user) throws SQLException {
|
||||
var sql = "insert into USERS (username, password) values (?,?)";
|
||||
try (var connection = dataSource.getConnection();
|
||||
var preparedStatement =
|
||||
connection.prepareStatement(sql)
|
||||
) {
|
||||
preparedStatement.setString(1, user.getUsername());
|
||||
preparedStatement.setString(2, user.getPassword());
|
||||
var result = preparedStatement.executeUpdate();
|
||||
LOGGER.info("Register successfully!");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package com.iluwatar.tablemodule;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that the table module example runs without errors.
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
@Test
|
||||
void shouldExecuteWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package com.iluwatar.tablemodule;
|
||||
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class UserTableModuleTest {
|
||||
private static final String DB_URL = "jdbc:h2:~/test";
|
||||
|
||||
private static DataSource createDataSource() {
|
||||
var dataSource = new JdbcDataSource();
|
||||
dataSource.setURL(DB_URL);
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws SQLException {
|
||||
try (var connection = DriverManager.getConnection(DB_URL);
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(UserTableModule.DELETE_SCHEMA_SQL);
|
||||
statement.execute(UserTableModule.CREATE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws SQLException {
|
||||
try (var connection = DriverManager.getConnection(DB_URL);
|
||||
var statement = connection.createStatement()) {
|
||||
statement.execute(UserTableModule.DELETE_SCHEMA_SQL);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginShouldFail() throws SQLException {
|
||||
var dataSource = createDataSource();
|
||||
var userTableModule = new UserTableModule(dataSource);
|
||||
var user = new User(1, "123456", "123456");
|
||||
assertEquals(0, userTableModule.login(user.getUsername(),
|
||||
user.getPassword()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginShouldSucceed() throws SQLException {
|
||||
var dataSource = createDataSource();
|
||||
var userTableModule = new UserTableModule(dataSource);
|
||||
var user = new User(1, "123456", "123456");
|
||||
userTableModule.registerUser(user);
|
||||
assertEquals(1, userTableModule.login(user.getUsername(),
|
||||
user.getPassword()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerShouldFail() throws SQLException {
|
||||
var dataSource = createDataSource();
|
||||
var userTableModule = new UserTableModule(dataSource);
|
||||
var user = new User(1, "123456", "123456");
|
||||
userTableModule.registerUser(user);
|
||||
assertThrows(SQLException.class, () -> {
|
||||
userTableModule.registerUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerShouldSucceed() throws SQLException {
|
||||
var dataSource = createDataSource();
|
||||
var userTableModule = new UserTableModule(dataSource);
|
||||
var user = new User(1, "123456", "123456");
|
||||
assertEquals(1, userTableModule.registerUser(user));
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package com.iluwatar.tablemodule;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class UserTest {
|
||||
@Test
|
||||
void testCanEqual() {
|
||||
assertFalse((new User(1, "janedoe", "iloveyou"))
|
||||
.canEqual("Other"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanEqual2() {
|
||||
var user = new User(1, "janedoe", "iloveyou");
|
||||
assertTrue(user.canEqual(new User(1, "janedoe",
|
||||
"iloveyou")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals1() {
|
||||
var user = new User(1, "janedoe", "iloveyou");
|
||||
assertNotEquals("42", user);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals2() {
|
||||
var user = new User(1, "janedoe", "iloveyou");
|
||||
assertEquals(user, new User(1, "janedoe",
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals3() {
|
||||
var user = new User(123, "janedoe", "iloveyou");
|
||||
assertNotEquals(user, new User(1, "janedoe",
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals4() {
|
||||
var user = new User(1, null, "iloveyou");
|
||||
assertNotEquals(user, new User(1, "janedoe",
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals5() {
|
||||
var user = new User(1, "iloveyou", "iloveyou");
|
||||
assertNotEquals(user, new User(1, "janedoe",
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals6() {
|
||||
var user = new User(1, "janedoe", "janedoe");
|
||||
assertNotEquals(user, new User(1, "janedoe",
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals7() {
|
||||
var user = new User(1, "janedoe", null);
|
||||
assertNotEquals(user, new User(1, "janedoe",
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals8() {
|
||||
var user = new User(1, null, "iloveyou");
|
||||
assertEquals(user, new User(1, null,
|
||||
"iloveyou"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals9() {
|
||||
var user = new User(1, "janedoe", null);
|
||||
assertEquals(user, new User(1, "janedoe",
|
||||
null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode1() {
|
||||
assertEquals(-1758941372, (new User(1, "janedoe",
|
||||
"iloveyou")).hashCode());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode2() {
|
||||
assertEquals(-1332207447, (new User(1, null,
|
||||
"iloveyou")).hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode3() {
|
||||
assertEquals(-426522485, (new User(1, "janedoe",
|
||||
null)).hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetId() {
|
||||
var user = new User(1, "janedoe", "iloveyou");
|
||||
user.setId(2);
|
||||
assertEquals(2, user.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetPassword() {
|
||||
var user = new User(1, "janedoe", "tmp");
|
||||
user.setPassword("iloveyou");
|
||||
assertEquals("iloveyou", user.getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsername() {
|
||||
var user = new User(1, "tmp", "iloveyou");
|
||||
user.setUsername("janedoe");
|
||||
assertEquals("janedoe", user.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
var user = new User(1, "janedoe", "iloveyou");
|
||||
assertEquals(String.format("User(id=%s, username=%s, password=%s)",
|
||||
user.getId(), user.getUsername(), user.getPassword()),
|
||||
user.toString());
|
||||
}
|
||||
}
|
||||
|
@ -1,182 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Abstract Document
|
||||
folder: abstract-document
|
||||
permalink: /patterns/abstract-document/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
使用动态属性,并在保持类型安全的同时实现非类型化语言的灵活性。
|
||||
|
||||
## 解释
|
||||
|
||||
抽象文档模式使您能够处理其他非静态属性。 此模式使用特征的概念来实现类型安全,并将不同类的属性分离为一组接口。
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 考虑由多个部分组成的汽车。 但是,我们不知道特定汽车是否真的拥有所有零件,或者仅仅是零件中的一部分。 我们的汽车是动态而且非常灵活的。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 抽象文档模式允许在对象不知道的情况下将属性附加到对象。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 面向对象的结构设计模式,用于组织松散类型的键值存储中的对象并使用类型化的视图公开数据。 该模式的目的是在强类型语言中实现组件之间的高度灵活性,在这种语言中,可以在不丢失类型安全支持的情况下,将新属性动态地添加到对象树中。 该模式利用特征将类的不同属性分成不同的接口。
|
||||
|
||||
**程序示例**
|
||||
|
||||
让我们首先定义基类`Document`和`AbstractDocument`。 它们基本上使对象拥有属性映射和任意数量的子对象。
|
||||
|
||||
```java
|
||||
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);
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
接下来,我们定义一个枚举“属性”和一组类型,价格,模型和零件的接口。 这使我们能够为Car类创建静态外观的界面。
|
||||
|
||||
```java
|
||||
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<Part> getParts() {
|
||||
return children(Property.PARTS.toString(), Part::new);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在我们准备介绍`Car`。
|
||||
|
||||
```java
|
||||
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
|
||||
|
||||
public Car(Map<String, Object> properties) {
|
||||
super(properties);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后是完整示例中的`Car`构造和使用方式。
|
||||
|
||||
```java
|
||||
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))
|
||||
);
|
||||
|
||||
// Constructing parts and car
|
||||
// Here is our car:
|
||||
// model: 300SL
|
||||
// price: 10000
|
||||
// parts:
|
||||
// wheel/15C/100
|
||||
// door/Lambo/300
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
使用抽象文档模式当
|
||||
|
||||
* 需要即时添加新属性
|
||||
* 你想要一种灵活的方式来以树状结构组织域
|
||||
* 你想要更宽松的耦合系统
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern)
|
||||
* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf)
|
||||
* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492)
|
@ -1,218 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Abstract Factory
|
||||
folder: abstract-factory
|
||||
permalink: /patterns/abstract-factory/
|
||||
categories: Creational
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 或称
|
||||
|
||||
工具包
|
||||
|
||||
## 目的
|
||||
|
||||
提供一个用于创建相关对象家族的接口,而无需指定其具体类。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 要创建一个王国,我们需要具有共同主题的对象。 精灵王国需要精灵王,精灵城堡和精灵军队,而兽人王国需要兽王,精灵城堡和兽人军队。 王国中的对象之间存在依赖性。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 工厂的工厂; 一个将单个但相关/从属的工厂分组在一起而没有指定其具体类别的工厂。
|
||||
|
||||
维基百科上说
|
||||
|
||||
> 抽象工厂模式提供了一种封装一组具有共同主题的单个工厂而无需指定其具体类的方法
|
||||
|
||||
**程序示例**
|
||||
|
||||
翻译上面的王国示例。 首先,我们为王国中的对象提供了一些接口和实现。
|
||||
|
||||
```java
|
||||
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 -> ...
|
||||
|
||||
```
|
||||
|
||||
然后我们有了王国工厂的抽象和实现
|
||||
|
||||
```java
|
||||
public interface KingdomFactory {
|
||||
Castle createCastle();
|
||||
King createKing();
|
||||
Army createArmy();
|
||||
}
|
||||
|
||||
public class ElfKingdomFactory implements KingdomFactory {
|
||||
public Castle createCastle() {
|
||||
return new ElfCastle();
|
||||
}
|
||||
public King createKing() {
|
||||
return new ElfKing();
|
||||
}
|
||||
public Army createArmy() {
|
||||
return new ElfArmy();
|
||||
}
|
||||
}
|
||||
|
||||
public class OrcKingdomFactory implements KingdomFactory {
|
||||
public Castle createCastle() {
|
||||
return new OrcCastle();
|
||||
}
|
||||
public King createKing() {
|
||||
return new OrcKing();
|
||||
}
|
||||
public Army createArmy() {
|
||||
return new OrcArmy();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在我们有了抽象工厂,使我们可以制作相关对象的系列,即精灵王国工厂创建了精灵城堡,国王和军队等。
|
||||
|
||||
```java
|
||||
var factory = new ElfKingdomFactory();
|
||||
var castle = factory.createCastle();
|
||||
var king = factory.createKing();
|
||||
var army = factory.createArmy();
|
||||
|
||||
castle.getDescription();
|
||||
king.getDescription();
|
||||
army.getDescription();
|
||||
```
|
||||
|
||||
程序输出:
|
||||
|
||||
```java
|
||||
This is the Elven castle!
|
||||
This is the Elven king!
|
||||
This is the Elven Army!
|
||||
```
|
||||
|
||||
现在,我们可以为不同的王国工厂设计工厂。 在此示例中,我们创建了FactoryMaker,负责返回ElfKingdomFactory或OrcKingdomFactory的实例。 客户可以使用FactoryMaker来创建所需的具体工厂,该工厂随后将生产不同的具体对象(军队,国王,城堡)。 在此示例中,我们还使用了一个枚举来参数化客户要求的王国工厂类型。
|
||||
|
||||
```java
|
||||
public static class FactoryMaker {
|
||||
|
||||
public enum KingdomType {
|
||||
ELF, ORC
|
||||
}
|
||||
|
||||
public static KingdomFactory makeFactory(KingdomType type) {
|
||||
switch (type) {
|
||||
case ELF:
|
||||
return new ElfKingdomFactory();
|
||||
case ORC:
|
||||
return new OrcKingdomFactory();
|
||||
default:
|
||||
throw new IllegalArgumentException("KingdomType not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
var app = new App();
|
||||
|
||||
LOGGER.info("Elf Kingdom");
|
||||
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF));
|
||||
LOGGER.info(app.getArmy().getDescription());
|
||||
LOGGER.info(app.getCastle().getDescription());
|
||||
LOGGER.info(app.getKing().getDescription());
|
||||
|
||||
LOGGER.info("Orc Kingdom");
|
||||
app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC));
|
||||
-- similar use of the orc factory
|
||||
}
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
|
||||
## 适用性
|
||||
|
||||
在以下情况下使用抽象工厂模式
|
||||
|
||||
* 该系统应独立于其产品的创建,组成和表示方式
|
||||
* 系统应配置有多个产品系列之一
|
||||
* 相关产品对象系列旨在一起使用,你需要强制执行此约束
|
||||
* 你想提供产品的类库,并且只想暴露它们的接口,而不是它们的实现。
|
||||
* 从概念上讲,依赖项的生存期比使用者的生存期短。
|
||||
* 你需要一个运行时值来构建特定的依赖关系
|
||||
* 你想决定在运行时从系列中调用哪种产品。
|
||||
* 你需要提供一个或更多仅在运行时才知道的参数,然后才能解决依赖关系。
|
||||
* 当你需要产品之间的一致性时
|
||||
* 在向程序添加新产品或产品系列时,您不想更改现有代码。
|
||||
|
||||
示例场景
|
||||
|
||||
* 在运行时在FileSystemAcmeService ,DatabaseAcmeService 或NetworkAcmeService中选择并调用一个
|
||||
* 单元测试用例的编写变得更加容易
|
||||
* 适用于不同操作系统的UI工具
|
||||
|
||||
## 后果:
|
||||
|
||||
* Java中的依赖注入会隐藏服务类的依赖关系,这些依赖关系可能导致运行时错误,而这些错误在编译时会被捕获。
|
||||
* 虽然在创建预定义对象时模式很好,但是添加新对象可能会很困难。
|
||||
* 由于引入了许多新的接口和类,因此代码变得比应有的复杂。
|
||||
|
||||
## 教程
|
||||
|
||||
* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java)
|
||||
|
||||
## 已知使用
|
||||
|
||||
* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
|
||||
* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--)
|
||||
* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--)
|
||||
|
||||
## 相关模式
|
||||
|
||||
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
||||
* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59)
|
||||
* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b)
|
@ -1,124 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Active Object
|
||||
folder: active-object
|
||||
permalink: /patterns/active-object/
|
||||
categories: Concurrency
|
||||
tags:
|
||||
- Performance
|
||||
---
|
||||
|
||||
|
||||
## 目的
|
||||
活动对象设计模式使每个驻留在其控制线程中的对象的方法执行与方法调用脱钩。 目的是通过使用异步方法调用和用于处理请求的调度程序来引入并发。
|
||||
|
||||
## 解释
|
||||
|
||||
实现活动对象模式的类将包含自同步机制,而无需使用“同步”方法。
|
||||
|
||||
真实世界例子
|
||||
|
||||
>兽人以其野性和顽强的灵魂而著称。 似乎他们有基于先前行为的控制线程。
|
||||
|
||||
要实现具有自己的控制机制线程并仅公开其API而不公开自己的执行,我们可以使用活动对象模式。
|
||||
|
||||
**程序示例**
|
||||
|
||||
```java
|
||||
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 and the wastelands.",name());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我们可以看到,任何将扩展ActiveCreature的类都将具有自己的控制线程来执行和调用方法。
|
||||
|
||||
例如,兽人类:
|
||||
|
||||
```java
|
||||
public class Orc extends ActiveCreature {
|
||||
|
||||
public Orc(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
现在,我们可以创建多个生物,例如兽人,告诉他们吃东西和散步,然后他们将在自己的控制线程上执行它:
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
var app = new App();
|
||||
app.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ActiveCreature creature;
|
||||
try {
|
||||
for (int i = 0;i < creatures;i++) {
|
||||
creature = new Orc(Orc.class.getSimpleName().toString() + i);
|
||||
creature.eat();
|
||||
creature.roam();
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
Runtime.getRuntime().exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
@ -1,157 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Acyclic Visitor
|
||||
folder: acyclic-visitor
|
||||
permalink: /patterns/acyclic-visitor/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
允许将新功能添加到现有的类层次结构中,而不会影响这些层次结构,也不会有四人帮访客模式中那样循环依赖的问题。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 我们有一个调制解调器类的层次结构。 需要使用基于过滤条件的外部算法(是Unix或DOS兼容的调制解调器)来访问此层次结构中的调制解调器。
|
||||
|
||||
通俗地说
|
||||
|
||||
> 非循环访问者允许将功能添加到现有的类层次结构中,而无需修改层次结构
|
||||
|
||||
[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) 上说
|
||||
|
||||
> 非循环访客模式允许将新功能添加到现有的类层次结构中,而不会影响这些层次结构,也不会创建四人帮访客模式中固有的循环依赖问题。
|
||||
|
||||
**程序示例**
|
||||
|
||||
这是调制解调器的层次结构。
|
||||
|
||||
```java
|
||||
public abstract class Modem {
|
||||
public abstract void accept(ModemVisitor modemVisitor);
|
||||
}
|
||||
|
||||
public class Zoom extends Modem {
|
||||
...
|
||||
@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 {
|
||||
...
|
||||
@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");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面我们介绍`调制解调器访问者`类结构。
|
||||
|
||||
```java
|
||||
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 {
|
||||
...
|
||||
@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 {
|
||||
...
|
||||
@Override
|
||||
public void visit(Zoom zoom) {
|
||||
LOGGER.info(zoom + " used with Unix configurator.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后,这里是访问者的实践。
|
||||
|
||||
```java
|
||||
var conUnix = new ConfigureForUnixVisitor();
|
||||
var conDos = new ConfigureForDosVisitor();
|
||||
var zoom = new Zoom();
|
||||
var hayes = new Hayes();
|
||||
hayes.accept(conDos);
|
||||
zoom.accept(conDos);
|
||||
hayes.accept(conUnix);
|
||||
zoom.accept(conUnix);
|
||||
```
|
||||
|
||||
程序输出:
|
||||
|
||||
```
|
||||
// Hayes modem used with Dos configurator.
|
||||
// Zoom modem used with Dos configurator.
|
||||
// Only HayesVisitor is allowed to visit Hayes modem
|
||||
// Zoom modem used with Unix configurator.
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
以下情况可以使用此模式:
|
||||
|
||||
* 需要在现有层次结构中添加新功能而无需更改或影响该层次结构时。
|
||||
* 当某些功能在层次结构上运行,但不属于层次结构本身时。 例如 ConfigureForDOS / ConfigureForUnix / ConfigureForX问题。
|
||||
* 当您需要根据对象的类型对对象执行非常不同的操作时。
|
||||
* 当访问的类层次结构将经常使用元素类的新派生进行扩展时。
|
||||
* 当重新编译,重新链接,重新测试或重新分发派生元素非常昂贵时。
|
||||
|
||||
## 结果
|
||||
|
||||
好处:
|
||||
|
||||
* 类层次结构之间没有依赖关系循环。
|
||||
* 如果添加了新访客,则无需重新编译所有访客。
|
||||
* 如果类层次结构具有新成员,则不会导致现有访问者中的编译失败。
|
||||
|
||||
坏处:
|
||||
|
||||
* 通过证明它可以接受所有访客,但实际上仅对特定访客感兴趣,从而违反了[Liskov的替代原则](https://java-design-patterns.com/principles/#liskov-substitution-principle)
|
||||
* 必须为可访问的类层次结构中的所有成员创建访问者的并行层次结构。
|
||||
|
||||
## 相关的模式
|
||||
|
||||
* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
|
||||
* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor)
|
@ -1,107 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Aggregator Microservices
|
||||
folder: aggregator-microservices
|
||||
permalink: /patterns/aggregator-microservices/
|
||||
categories: Architectural
|
||||
tags:
|
||||
- Cloud distributed
|
||||
- Decoupling
|
||||
- Microservices
|
||||
---
|
||||
|
||||
## 意图
|
||||
|
||||
用户对聚合器服务进行一次调用,然后聚合器将调用每个相关的微服务。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 我们的网络市场需要有关产品及其当前库存的信息。 它调用聚合服务,聚合服务依次调用产品信息微服务和产品库存微服务,返回组合信息。
|
||||
|
||||
通俗地说
|
||||
|
||||
> 聚合器微服务从各种微服务中收集数据,并返回一个聚合数据以进行处理。
|
||||
|
||||
Stack Overflow上说
|
||||
|
||||
> 聚合器微服务调用多个服务以实现应用程序所需的功能。
|
||||
|
||||
**程序示例**
|
||||
|
||||
让我们从数据模型开始。 这是我们的`产品`。
|
||||
|
||||
```java
|
||||
public class Product {
|
||||
private String title;
|
||||
private int productInventories;
|
||||
// getters and setters ->
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
接下来,我们将介绍我们的聚合器微服务。 它包含用于调用相应微服务的客户端`ProductInformationClient`和` ProductInventoryClient`。
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class Aggregator {
|
||||
|
||||
@Resource
|
||||
private ProductInformationClient informationClient;
|
||||
|
||||
@Resource
|
||||
private ProductInventoryClient inventoryClient;
|
||||
|
||||
@RequestMapping(path = "/product", method = RequestMethod.GET)
|
||||
public Product getProduct() {
|
||||
|
||||
var product = new Product();
|
||||
var productTitle = informationClient.getProductTitle();
|
||||
var productInventory = inventoryClient.getProductInventories();
|
||||
|
||||
//Fallback to error message
|
||||
product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed"));
|
||||
|
||||
//Fallback to default error inventory
|
||||
product.setProductInventories(requireNonNullElse(productInventory, -1));
|
||||
|
||||
return product;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是产品信息微服务的精华实现。 库存微服务类似,它只返回库存计数。
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class InformationController {
|
||||
@RequestMapping(value = "/information", method = RequestMethod.GET)
|
||||
public String getProductTitle() {
|
||||
return "The Product Title.";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now calling our `Aggregator` REST API returns the product information.
|
||||
|
||||
现在调用我们的聚合器 REST API会返回产品信息。
|
||||
|
||||
```bash
|
||||
curl http://localhost:50004/product
|
||||
{"title":"The Product Title.","productInventories":5}
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
当需要各种微服务的统一API时,无论客户端设备如何,都可以使用Aggregator微服务模式。
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Microservice Design Patterns](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/)
|
||||
* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60)
|
||||
* [Architectural Patterns: Uncover essential patterns in the most indispensable realm of enterprise architecture](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4)
|
@ -1,196 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Ambassador
|
||||
folder: ambassador
|
||||
permalink: /patterns/ambassador/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Decoupling
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
在客户端上提供帮助程序服务实例,并从共享资源上转移常用功能。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 远程服务有许多客户端访问它提供的功能。 该服务是旧版应用程序,无法更新。 用户的大量请求导致连接问题。新的请求频率规则需要同时实现延迟检测和客户端日志功能。
|
||||
|
||||
通俗的说
|
||||
|
||||
> 使用“大使”模式,我们可以实现来自客户端的频率较低的轮询以及延迟检查和日志记录。
|
||||
|
||||
微软文档做了如下阐述
|
||||
|
||||
> 可以将大使服务视为与客户端位于同一位置的进程外代理。 此模式对于以语言不可知的方式减轻常见的客户端连接任务(例如监视,日志记录,路由,安全性(如TLS)和弹性模式)的工作很有用。 它通常与旧版应用程序或其他难以修改的应用程序一起使用,以扩展其网络功能。 它还可以使专业团队实现这些功能。
|
||||
|
||||
**程序示例**
|
||||
|
||||
有了上面的介绍我们将在这个例子中模仿功能。我们有一个用远程服务实现的接口,同时也是大使服务。
|
||||
|
||||
```java
|
||||
interface RemoteServiceInterface {
|
||||
long doRemoteFunction(int value) throws Exception;
|
||||
}
|
||||
```
|
||||
|
||||
表示为单例的远程服务。
|
||||
|
||||
```java
|
||||
public class RemoteService implements RemoteServiceInterface {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
服务大使添加了像日志和延迟检测的额外功能
|
||||
|
||||
```java
|
||||
public class ServiceAmbassador implements RemoteServiceInterface {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
客户端具有用于与远程服务进行交互的本地服务大使:
|
||||
|
||||
```java
|
||||
public class Client {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
|
||||
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
|
||||
|
||||
long useService(int value) {
|
||||
var result = serviceAmbassador.doRemoteFunction(value);
|
||||
LOGGER.info("Service result: " + result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是两个使用该服务的客户端。
|
||||
|
||||
```java
|
||||
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:
|
||||
|
||||
```java
|
||||
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: -1
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
大使适用于无法修改或极难修改的旧式远程服务。 可以在客户端上实现连接性的功能,而无需更改远程服务。
|
||||
|
||||
* 大使提供了用于远程服务的本地接口。
|
||||
* 大使在客户端上提供日志记录,断路,重试和安全性。
|
||||
|
||||
## 典型用例
|
||||
|
||||
* 控制对另一个对象的访问
|
||||
* 实现日志
|
||||
* 卸载远程服务任务
|
||||
* 简化网络连接
|
||||
|
||||
## 已知使用
|
||||
|
||||
* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador)
|
||||
|
||||
## 相关模式
|
||||
|
||||
* [Proxy](https://java-design-patterns.com/patterns/proxy/)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Ambassador pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador)
|
||||
* [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://books.google.co.uk/books?id=6BJNDwAAQBAJ&pg=PT35&lpg=PT35&dq=ambassador+pattern+in+real+world&source=bl&ots=d2e7GhYdHi&sig=Lfl_MDnCgn6lUcjzOg4GXrN13bQ&hl=en&sa=X&ved=0ahUKEwjk9L_18rrbAhVpKcAKHX_KA7EQ6AEIWTAI#v=onepage&q=ambassador%20pattern%20in%20real%20world&f=false)
|
@ -1,138 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: API Gateway
|
||||
folder: api-gateway
|
||||
permalink: /patterns/api-gateway/
|
||||
categories: Architectural
|
||||
tags:
|
||||
- Cloud distributed
|
||||
- Decoupling
|
||||
- Microservices
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
API网关将所有对微服务的调用聚合到一起。用户对API网关进行一次调用,然后API网关调用每个相关的微服务。
|
||||
|
||||
## 解释
|
||||
|
||||
使用微服务模式,客户端可能需要来自多个不同微服务的数据。 如果客户端直接调用每个微服务,则可能会导致更长的加载时间,因为客户端将不得不为每个调用的微服务发出网络请求。此外,让客户端调用每个微服务会直接将客户端与该微服务相关联-如果微服务的内部实现发生了变化(例如,如果将来某个时候合并了两个微服务),或者微服务的位置(主机和端口) 更改,则必须更新使用这些微服务的每个客户端。
|
||||
|
||||
API网关模式的目的是缓解其中的一些问题。 在API网关模式中,在客户端和微服务之间放置了一个附加实体(API网关)。API网关的工作是将对微服务的调用进行聚合。 客户端不是一次单独调用每个微服务,而是一次调用API网关。 然后,API网关调用客户端所需的每个微服务。
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 我们正在为电子商务站点实现微服务和API网关模式。 在此系统中,API网关调用Image和Price微服务。
|
||||
|
||||
通俗地说
|
||||
|
||||
> 对于使用微服务架构实现的系统,API是聚合微服务调用的入口点。
|
||||
|
||||
维基百科说
|
||||
|
||||
> API网关是充当API前置,接收API请求,执行限制和安全策略,将请求传递到后端服务,然后将响应传递回请求者的服务器。网关通常包括一个转换引擎,以实时地编排和修改请求和响应。 网关可以提供收集分析数据和提供缓存等功能。网关还可以提供支持身份验证,授权,安全性,审计和法规遵从性的功能。
|
||||
|
||||
**程序示例**
|
||||
|
||||
此实现展示了电子商务站点的API网关模式。` ApiGateway`分别使用` ImageClientImpl`和` PriceClientImpl`来调用Image和Price微服务。 在桌面设备上查看该网站的客户可以看到价格信息和产品图片,因此` ApiGateway`会调用这两种微服务并在`DesktopProduct`模型中汇总数据。 但是,移动用户只能看到价格信息。 他们看不到产品图片。 对于移动用户,`ApiGateway`仅检索价格信息,并将其用于填充`MobileProduct`模型。
|
||||
|
||||
这个是图像微服务的实现。
|
||||
|
||||
```java
|
||||
public interface ImageClient {
|
||||
String getImagePath();
|
||||
}
|
||||
|
||||
public class ImageClientImpl implements ImageClient {
|
||||
@Override
|
||||
public String getImagePath() {
|
||||
var httpClient = HttpClient.newHttpClient();
|
||||
var httpGet = HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(URI.create("http://localhost:50005/image-path"))
|
||||
.build();
|
||||
|
||||
try {
|
||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||
return httpResponse.body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里是价格服务的实现。
|
||||
|
||||
```java
|
||||
public interface PriceClient {
|
||||
String getPrice();
|
||||
}
|
||||
|
||||
public class PriceClientImpl implements PriceClient {
|
||||
|
||||
@Override
|
||||
public String getPrice() {
|
||||
var httpClient = HttpClient.newHttpClient();
|
||||
var httpGet = HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(URI.create("http://localhost:50006/price"))
|
||||
.build();
|
||||
|
||||
try {
|
||||
var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString());
|
||||
return httpResponse.body();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这里,我们可以看到API网关如何将请求映射到微服务。
|
||||
|
||||
```java
|
||||
public class ApiGateway {
|
||||
|
||||
@Resource
|
||||
private ImageClient imageClient;
|
||||
|
||||
@Resource
|
||||
private PriceClient priceClient;
|
||||
|
||||
@RequestMapping(path = "/desktop", method = RequestMethod.GET)
|
||||
public DesktopProduct getProductDesktop() {
|
||||
var desktopProduct = new DesktopProduct();
|
||||
desktopProduct.setImagePath(imageClient.getImagePath());
|
||||
desktopProduct.setPrice(priceClient.getPrice());
|
||||
return desktopProduct;
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/mobile", method = RequestMethod.GET)
|
||||
public MobileProduct getProductMobile() {
|
||||
var mobileProduct = new MobileProduct();
|
||||
mobileProduct.setPrice(priceClient.getPrice());
|
||||
return mobileProduct;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
在以下情况下使用API网关模式
|
||||
|
||||
* 你正在使用微服务架构,并且需要聚合单点来进行微服务调用。
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [microservices.io - API Gateway](http://microservices.io/patterns/apigateway.html)
|
||||
* [NGINX - Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/)
|
||||
* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=ac7b6a57f866ac006a309d9086e8cfbd)
|
||||
* [Building Microservices: Designing Fine-Grained Systems](https://www.amazon.com/gp/product/1491950358/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1491950358&linkId=4c95ca9831e05e3f0dadb08841d77bf1)
|
@ -1,143 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Arrange/Act/Assert
|
||||
folder: arrange-act-assert
|
||||
permalink: /patterns/arrange-act-assert/
|
||||
categories: Idiom
|
||||
tags:
|
||||
- Testing
|
||||
---
|
||||
|
||||
## 或称
|
||||
|
||||
Given/When/Then
|
||||
|
||||
## 意图
|
||||
|
||||
安排/执行/断言(AAA)是组织单元测试的一种模式。
|
||||
|
||||
它将测试分为三个清晰而独特的步骤:
|
||||
|
||||
1. 安排:执行测试所需的设置和初始化。
|
||||
2. 执行:采取测试所需的行动。
|
||||
3. 断言:验证测试结果。
|
||||
|
||||
## 解释
|
||||
|
||||
这种模式有几个明显的好处。 它在测试的设置,操作和结果之间建立了清晰的分隔。 这种结构使代码更易于阅读和理解。 如果按顺序排列步骤并格式化代码以将它们分开,则可以扫描测试并快速了解其功能。
|
||||
|
||||
当您编写测试时,它还会强制执行一定程度的纪律。 您必须清楚地考虑您的测试将执行的三个步骤。 由于您已经有了大纲,因此可以使同时编写测试变得更加自然。
|
||||
|
||||
真实世界例子
|
||||
|
||||
> 我们需要为一个类编写全面而清晰的单元测试套件。
|
||||
|
||||
通俗地说
|
||||
|
||||
> 安排/执行/断言是一种测试模式,将测试分为三个清晰的步骤以方便维护。
|
||||
|
||||
WikiWikiWeb 上说
|
||||
|
||||
> 安排/执行/断言是用于在单元测试方法中排列和格式化代码的模式。
|
||||
|
||||
**程序示例**
|
||||
|
||||
让我们首先介绍要进行单元测试的`Cash`类。
|
||||
|
||||
```java
|
||||
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.
|
||||
|
||||
然后我们根据Arrange / Act / Assert模式编写单元测试。 注意每个单元测试的步骤是分开的清晰的。
|
||||
|
||||
```java
|
||||
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());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 适用性
|
||||
|
||||
使用 Arrange/Act/Assert 模式当
|
||||
|
||||
* You need to structure your unit tests so that they're easier to read, maintain, and enhance.
|
||||
* 你需要结构化你的单元测试代码这样它们可以更好的阅读,维护和增强。
|
||||
|
||||
## 鸣谢
|
||||
|
||||
* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx)
|
||||
* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/)
|
||||
* [Martin Fowler: GivenWhenThen](https://martinfowler.com/bliki/GivenWhenThen.html)
|
||||
* [xUnit Test Patterns: Refactoring Test Code](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f7e8dd50d720c9b63dbf)
|
||||
* [Unit Testing Principles, Practices, and Patterns](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cf22a63c3e4758ae08aa0a0cc35)
|
||||
* [Test Driven Development: By Example](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b84ca5087472ef0e05)
|
@ -1,161 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Async Method Invocation
|
||||
folder: async-method-invocation
|
||||
permalink: /patterns/async-method-invocation/
|
||||
categories: Concurrency
|
||||
tags:
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## 含义
|
||||
|
||||
异步方法是一种调用线程在等待任务结果时候不会被阻塞的模式。该模式提供了对多个任务的并行处理,并通过回调或等待,在所有任务完成后在提供结果读取。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界案例
|
||||
|
||||
> 发射太空火箭是一项令人兴奋的事业。在任务指挥部下达发射命令后, 经过一些未确定的时间,火箭要么成功发射,要么重演挑战者悲剧。
|
||||
|
||||
简而言之
|
||||
|
||||
> 异步方法调用开始任务处理并,在任务结果准备好之前立即返回。任务处理的结果会在稍后再返回给调用者。
|
||||
|
||||
维基百科的解释
|
||||
|
||||
> 在多线程计算机编程中,异步方法调用(AMI),也被称为异步方法调用或异步模式。这是一种设计模式,在这种模式下,调用点在等待被调用代码完成时不会被阻塞。相反,当返回点到达时,调用线程会得到通知。轮询结果是一种不受欢迎的选择。
|
||||
|
||||
**编程示例**
|
||||
|
||||
在这个例子中,我们正在发射太空火箭和部署月球车。
|
||||
|
||||
该应用演示了异步方法调用模式。该模式的关键部分是 `AsyncResult`,它是一个异步计算值的中间容器,`AsyncCallback` 可以在任务完成时提供执行行动作,`AsyncExecutor` 负责管理异步任务的执行。
|
||||
|
||||
```java
|
||||
public interface AsyncResult<T> {
|
||||
boolean isCompleted();
|
||||
T getValue() throws ExecutionException;
|
||||
void await() throws InterruptedException;
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public interface AsyncCallback<T> {
|
||||
void onComplete(T value, Optional<Exception> ex);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
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` 是 `AsyncExecutor` 的一个实现。接下来将着重说明它的一些关键部分。
|
||||
|
||||
```java
|
||||
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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后我们准备发射一些火箭,看看所有东西是如何一起运作的。
|
||||
|
||||
```java
|
||||
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("Space rocket <" + result1 + "> launch complete");
|
||||
log("Space rocket <" + result2 + "> launch complete");
|
||||
log("Space rocket <" + result3 + "> launch complete");
|
||||
}
|
||||
```
|
||||
|
||||
以下是控制台输出。
|
||||
|
||||
```java
|
||||
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 complete
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用场景
|
||||
|
||||
在以下场景可以使用异步调用模式
|
||||
|
||||
* 你有多有可以并行执行的独立任务
|
||||
* 你需要提高一组串行任务的性能
|
||||
* 你的处理能力有限、或者有长期运行的任务,调用者不应该等待任务所有任务运行结束
|
||||
|
||||
## 现实示例
|
||||
|
||||
* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html)
|
||||
* [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)
|
||||
* [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html)
|
||||
* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx)
|
||||
|
@ -1,129 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Balking
|
||||
folder: balking
|
||||
permalink: /patterns/balking/
|
||||
categories: Concurrency
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## 含义
|
||||
|
||||
阻止模式用于防止一个对象在不完整或不适当的状态下执行某段代码。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界的案例
|
||||
|
||||
> 洗衣机里有一个用于启动衣物洗涤的启动按钮。当洗衣机没有启动时,该按钮可以正常按下生效,但如果洗衣机已经在洗衣服了,再按下按钮就不生效了。
|
||||
|
||||
简而言之
|
||||
|
||||
> 使用阻止模式,只有当对象处于特定状态时,才会执行某段代码。
|
||||
|
||||
维基百科的解释
|
||||
|
||||
> 阻止模式是一种软件设计模式,它只在对象处于特定状态时对其执行动作。例如,如果一个对象读取 ZIP 文件,当 ZIP 文件没有打开时,如果一个方法在该对象上调用一个获取方法,该对象就会对阻止这个请求。
|
||||
|
||||
**编程示例**
|
||||
|
||||
在这个例子的实现中,`WashingMachine` 对象存在 2 种状态: `ENABLED` 和 `WASHING`。如果该对象处于 `ENABLED` 状态,则使用一个线程安全的方法可以其状态改变为 `WASHING`。在另一方面,如果它已经处于 `WASHING` 状态,而任何其他线程执行了 `wash()`,它不会执行该指令,而是什么都不做就返回。
|
||||
|
||||
以下是 `WashingMachine` 类的相关代码。
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class WashingMachine {
|
||||
|
||||
private final DelayProvider delayProvider;
|
||||
private WashingMachineState washingMachineState;
|
||||
|
||||
public WashingMachine(DelayProvider delayProvider) {
|
||||
this.delayProvider = delayProvider;
|
||||
this.washingMachineState = WashingMachineState.ENABLED;
|
||||
}
|
||||
|
||||
public WashingMachineState getWashingMachineState() {
|
||||
return washingMachineState;
|
||||
}
|
||||
|
||||
public void wash() {
|
||||
synchronized (this) {
|
||||
var machineState = getWashingMachineState();
|
||||
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
|
||||
if (this.washingMachineState == WashingMachineState.WASHING) {
|
||||
LOGGER.error("Cannot wash if the machine has been already washing!");
|
||||
return;
|
||||
}
|
||||
this.washingMachineState = WashingMachineState.WASHING;
|
||||
}
|
||||
LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
|
||||
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
|
||||
}
|
||||
|
||||
public synchronized void endOfWashing() {
|
||||
washingMachineState = WashingMachineState.ENABLED;
|
||||
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下是 `WashingMachine` 使用的简单 `DelayProvider` 接口。
|
||||
|
||||
```java
|
||||
public interface DelayProvider {
|
||||
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
|
||||
}
|
||||
```
|
||||
|
||||
现在我们介绍一下使用 `WashingMachine` 的应用。
|
||||
|
||||
```java
|
||||
public static void main(String... args) {
|
||||
final var washingMachine = new WashingMachine();
|
||||
var executorService = Executors.newFixedThreadPool(3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
executorService.execute(washingMachine::wash);
|
||||
}
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ie) {
|
||||
LOGGER.error("ERROR: Waiting on executor service shutdown!");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
以下是程序的控制台输出。
|
||||
|
||||
```
|
||||
14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED
|
||||
14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing
|
||||
14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING
|
||||
14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
|
||||
14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING
|
||||
14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
|
||||
14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed.
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用场景
|
||||
|
||||
在以下情况下可以使用阻止模式:
|
||||
|
||||
* 你想要在某个对象上调用一个动作,只有当该对象处于特定状态时才允许该调用。
|
||||
* 对象一般只处于容易暂时阻止的状态,只不过该时间是未知的。
|
||||
|
||||
## 教学
|
||||
|
||||
* [Guarded Suspension Pattern](https://java-design-patterns.com/patterns/guarded-suspension/)
|
||||
* [Double Checked Locking Pattern](https://java-design-patterns.com/patterns/double-checked-locking/)
|
||||
|
||||
## 引用
|
||||
|
||||
* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd Edition, Volume 1](https://www.amazon.com/gp/product/0471227293/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0471227293&linkId=0e39a59ffaab93fb476036fecb637b99)
|
@ -1,160 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Business Delegate
|
||||
folder: business-delegate
|
||||
permalink: /patterns/business-delegate/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## 含义
|
||||
|
||||
业务委托模式(译者:国内也有翻译成业务代表模式)在表现层和业务层之间增加了一个抽象层。通过使用该模式,我们获得了各层之间的松散耦合,并封装了关于如何定位、连接和与构成应用程序的业务对象进行交互的知识。
|
||||
|
||||
## 解释
|
||||
|
||||
真实世界的案例
|
||||
|
||||
> 一个手机应用程序承诺将现有的任何电影传输到你的手机上。它捕获了用户的搜索关键字内容,并将其传递给业务委托层。业务委托层选择最合适的视频流服务,并从该服务进行视频播放。
|
||||
|
||||
简而言之
|
||||
|
||||
> 业务委托模式在表现层和业务层之间增加了一个抽象层。
|
||||
|
||||
维基百科的解释
|
||||
|
||||
> Business delegate is a Java EE design pattern. This pattern is directing to reduce the coupling
|
||||
> in between business services and the connected presentation tier, and to hide the implementation
|
||||
> details of services (including lookup and accessibility of EJB architecture). Business delegates
|
||||
> acts as an adaptor to invoke business objects from the presentation tier.
|
||||
>
|
||||
> 业务委托模式是一种 Java EE 设计模式。这种模式旨在减少业务服务和所连接的表现层之间的耦合度,并隐藏服务的实现细节(包括 EJB 架构的查询和可访问性)。业务代表作为一个适配器,从表现层调用业务对象。
|
||||
|
||||
**编程示例**
|
||||
|
||||
首先,我们实现了一个视频流服务的抽象,和几个具体实现。
|
||||
|
||||
```java
|
||||
public interface VideoStreamingService {
|
||||
void doProcessing();
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class NetflixService implements VideoStreamingService {
|
||||
@Override
|
||||
public void doProcessing() {
|
||||
LOGGER.info("NetflixService is now processing");
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public class YouTubeService implements VideoStreamingService {
|
||||
@Override
|
||||
public void doProcessing() {
|
||||
LOGGER.info("YouTubeService is now processing");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接下来,我们实现一个查询服务,用于决定使用哪个视频流服务。
|
||||
|
||||
```java
|
||||
@Setter
|
||||
public class BusinessLookup {
|
||||
|
||||
private NetflixService netflixService;
|
||||
private YouTubeService youTubeService;
|
||||
|
||||
public VideoStreamingService getBusinessService(String movie) {
|
||||
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
|
||||
return netflixService;
|
||||
} else {
|
||||
return youTubeService;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
业务委托层使用业务查询,将电影播放请求路由到合适的视频流服务。
|
||||
|
||||
```java
|
||||
@Setter
|
||||
public class BusinessDelegate {
|
||||
|
||||
private BusinessLookup lookupService;
|
||||
|
||||
public void playbackMovie(String movie) {
|
||||
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
|
||||
videoStreamingService.doProcessing();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
移动客户端利用业务委托来调用业务层。
|
||||
|
||||
```java
|
||||
public class MobileClient {
|
||||
|
||||
private final BusinessDelegate businessDelegate;
|
||||
|
||||
public MobileClient(BusinessDelegate businessDelegate) {
|
||||
this.businessDelegate = businessDelegate;
|
||||
}
|
||||
|
||||
public void playbackMovie(String movie) {
|
||||
businessDelegate.playbackMovie(movie);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后,我们展示一下这个示例完整的操作。
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
|
||||
// prepare the objects
|
||||
var businessDelegate = new BusinessDelegate();
|
||||
var businessLookup = new BusinessLookup();
|
||||
businessLookup.setNetflixService(new NetflixService());
|
||||
businessLookup.setYouTubeService(new YouTubeService());
|
||||
businessDelegate.setLookupService(businessLookup);
|
||||
|
||||
// create the client and use the business delegate
|
||||
var client = new MobileClient(businessDelegate);
|
||||
client.playbackMovie("Die Hard 2");
|
||||
client.playbackMovie("Maradona: The Greatest Ever");
|
||||
}
|
||||
```
|
||||
|
||||
以下是终端输出的内容。
|
||||
|
||||
```
|
||||
21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing
|
||||
21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 相关模式
|
||||
|
||||
* [Service locator pattern](https://java-design-patterns.com/patterns/service-locator/)
|
||||
|
||||
## 适用场景
|
||||
|
||||
业务委托模式的适用场景:
|
||||
|
||||
* 你希望表现层和业务层之间是松耦合的。
|
||||
* 你想要协调对多个业务服务的调用。
|
||||
* 你想要对服务查询、服务调用进行封装。
|
||||
|
||||
## 教程
|
||||
|
||||
* [Business Delegate Pattern at TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm)
|
||||
|
||||
## 引用
|
||||
|
||||
* [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=48d37c67fb3d845b802fa9b619ad8f31)
|
||||
* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947)
|
@ -1,312 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Circuit Breaker
|
||||
folder: circuit-breaker
|
||||
permalink: /patterns/circuit-breaker/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Performance
|
||||
- Decoupling
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## 含义
|
||||
|
||||
以这样的方式(译者:指断路器方式)处理昂贵的远程服务调用,可以防止单个服务/组件的故障导致整个应用程序崩溃,同时我们可以尽快地进行服务重连。
|
||||
|
||||
## 解释
|
||||
|
||||
现实世界案例
|
||||
|
||||
> 设想一下,一个网络应用程序既有本地文件/图像,又有用于获取数据的远程服务。这些远程服务可能在某些时候是健康的、有反应的,也可能在某些时候由于各种原因而变得缓慢和无反应。因此,如果其中一个远程服务速度慢或不能成功响应,我们的应用程序将尝试使用多个线程/进程从远程服务中获取响应,很快所有的线程/进程都会挂起(也称为线程饥饿 thread starvation),从而导致我们整个 Web 应用程序崩溃。我们应该能够检测到这种情况,并向用户显示一个适当的信息,以便用户可以探索应用程序的其他部分,而不受远程服务故障的影响。同时,其他正常工作的服务应该保持运作,不受这次故障的影响。
|
||||
|
||||
简而言之
|
||||
|
||||
> 断路器允许优雅地处理失败的远程服务。当我们的应用程序的所有部分都高度解耦时,这种方式的效果会很好,一个组件的失败并不会导致其他部分停止工作。
|
||||
|
||||
维基百科的解释
|
||||
|
||||
> 断路器是现代软件开发中使用的一种设计模式。它用于检测故障,并封装了防止故障不断复发的逻辑,在维护期间,临时地处理外部系统故障或意外的系统问题。
|
||||
|
||||
## Programmatic Example
|
||||
|
||||
那么,这一切是如何实现的呢?考虑到上面的例子,我们将在一个简单的例子中模拟这个功能。一个监控服务(译者:下图的 Monitoring Service)模拟了网络应用,进行本地和远程调用。
|
||||
|
||||
该服务架构如下:
|
||||
|
||||

|
||||
|
||||
终端用户(译者:上图的 End User)应用的代码如下:
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
var serverStartTime = System.nanoTime();
|
||||
|
||||
var delayedService = new DelayedRemoteService(serverStartTime, 5);
|
||||
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
|
||||
2000 * 1000 * 1000);
|
||||
|
||||
var quickService = new QuickRemoteService();
|
||||
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
|
||||
2000 * 1000 * 1000);
|
||||
|
||||
//Create an object of monitoring service which makes both local and remote calls
|
||||
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
|
||||
quickServiceCircuitBreaker);
|
||||
|
||||
//Fetch response from local resource
|
||||
LOGGER.info(monitoringService.localResourceResponse());
|
||||
|
||||
//Fetch response from delayed service 2 times, to meet the failure threshold
|
||||
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||
|
||||
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
|
||||
//which is OPEN now
|
||||
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||
|
||||
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
|
||||
LOGGER.info(monitoringService.quickServiceResponse());
|
||||
LOGGER.info(quickServiceCircuitBreaker.getState());
|
||||
|
||||
//Wait for the delayed service to become responsive
|
||||
try {
|
||||
LOGGER.info("Waiting for delayed service to become responsive");
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Check the state of delayed circuit breaker, should be HALF_OPEN
|
||||
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||
|
||||
//Fetch response from delayed service, which should be healthy by now
|
||||
LOGGER.info(monitoringService.delayedServiceResponse());
|
||||
//As successful response is fetched, it should be CLOSED again.
|
||||
LOGGER.info(delayedServiceCircuitBreaker.getState());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
监控服务代码(译者:上图的 monitoring service):
|
||||
|
||||
```java
|
||||
public class MonitoringService {
|
||||
|
||||
private final CircuitBreaker delayedService;
|
||||
|
||||
private final CircuitBreaker quickService;
|
||||
|
||||
public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickService) {
|
||||
this.delayedService = delayedService;
|
||||
this.quickService = quickService;
|
||||
}
|
||||
|
||||
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
|
||||
public String localResourceResponse() {
|
||||
return "Local Service is working";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch response from the delayed service (with some simulated startup time).
|
||||
*
|
||||
* @return response string
|
||||
*/
|
||||
public String delayedServiceResponse() {
|
||||
try {
|
||||
return this.delayedService.attemptRequest();
|
||||
} catch (RemoteServiceException e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches response from a healthy service without any failure.
|
||||
*
|
||||
* @return response string
|
||||
*/
|
||||
public String quickServiceResponse() {
|
||||
try {
|
||||
return this.quickService.attemptRequest();
|
||||
} catch (RemoteServiceException e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
可以看出,它直接进行了获取本地资源的调用,但它把对远程(昂贵的)服务的调用包装在一个断路器对象中,这样可以防止出现如下故障:
|
||||
|
||||
```java
|
||||
public class DefaultCircuitBreaker implements CircuitBreaker {
|
||||
|
||||
private final long timeout;
|
||||
private final long retryTimePeriod;
|
||||
private final RemoteService service;
|
||||
long lastFailureTime;
|
||||
private String lastFailureResponse;
|
||||
int failureCount;
|
||||
private final int failureThreshold;
|
||||
private State state;
|
||||
private final long futureTime = 1000 * 1000 * 1000 * 1000;
|
||||
|
||||
/**
|
||||
* Constructor to create an instance of Circuit Breaker.
|
||||
*
|
||||
* @param timeout Timeout for the API request. Not necessary for this simple example
|
||||
* @param failureThreshold Number of failures we receive from the depended service before changing
|
||||
* state to 'OPEN'
|
||||
* @param retryTimePeriod Time period after which a new request is made to remote service for
|
||||
* status check.
|
||||
*/
|
||||
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
|
||||
long retryTimePeriod) {
|
||||
this.service = serviceToCall;
|
||||
// We start in a closed state hoping that everything is fine
|
||||
this.state = State.CLOSED;
|
||||
this.failureThreshold = failureThreshold;
|
||||
// Timeout for the API request.
|
||||
// Used to break the calls made to remote resource if it exceeds the limit
|
||||
this.timeout = timeout;
|
||||
this.retryTimePeriod = retryTimePeriod;
|
||||
//An absurd amount of time in future which basically indicates the last failure never happened
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.failureCount = 0;
|
||||
}
|
||||
|
||||
// Reset everything to defaults
|
||||
@Override
|
||||
public void recordSuccess() {
|
||||
this.failureCount = 0;
|
||||
this.lastFailureTime = System.nanoTime() + futureTime;
|
||||
this.state = State.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordFailure(String response) {
|
||||
failureCount = failureCount + 1;
|
||||
this.lastFailureTime = System.nanoTime();
|
||||
// Cache the failure response for returning on open state
|
||||
this.lastFailureResponse = response;
|
||||
}
|
||||
|
||||
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
|
||||
protected void evaluateState() {
|
||||
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
|
||||
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
|
||||
//We have waited long enough and should try checking if service is up
|
||||
state = State.HALF_OPEN;
|
||||
} else {
|
||||
//Service would still probably be down
|
||||
state = State.OPEN;
|
||||
}
|
||||
} else {
|
||||
//Everything is working fine
|
||||
state = State.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
evaluateState();
|
||||
return state.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Break the circuit beforehand if it is known service is down Or connect the circuit manually if
|
||||
* service comes online before expected.
|
||||
*
|
||||
* @param state State at which circuit is in
|
||||
*/
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
switch (state) {
|
||||
case OPEN:
|
||||
this.failureCount = failureThreshold;
|
||||
this.lastFailureTime = System.nanoTime();
|
||||
break;
|
||||
case HALF_OPEN:
|
||||
this.failureCount = failureThreshold;
|
||||
this.lastFailureTime = System.nanoTime() - retryTimePeriod;
|
||||
break;
|
||||
default:
|
||||
this.failureCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes service call.
|
||||
*
|
||||
* @return Value from the remote resource, stale response or a custom exception
|
||||
*/
|
||||
@Override
|
||||
public String attemptRequest() throws RemoteServiceException {
|
||||
evaluateState();
|
||||
if (state == State.OPEN) {
|
||||
// return cached response if the circuit is in OPEN state
|
||||
return this.lastFailureResponse;
|
||||
} else {
|
||||
// Make the API request if the circuit is not OPEN
|
||||
try {
|
||||
//In a real application, this would be run in a thread and the timeout
|
||||
//parameter of the circuit breaker would be utilized to know if service
|
||||
//is working. Here, we simulate that based on server response itself
|
||||
var response = service.call();
|
||||
// Yay!! the API responded fine. Let's reset everything.
|
||||
recordSuccess();
|
||||
return response;
|
||||
} catch (RemoteServiceException ex) {
|
||||
recordFailure(ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上述模式是如何防止失败的呢?让我们通过它所实现的这个有限状态机来了解。
|
||||
|
||||

|
||||
|
||||
- 我们用 `timeout`(超时)、 `failureThreshold` (失败阈值)、`retryTimePeriod`(重试时间周期) 参数初始化断路器对象 ,用于确定 API 的适应性。
|
||||
- 最初,断路器处于 `closed` 关闭状态,没有发生对 API 的远程调用。
|
||||
- 每次调用成功,我们就把状态重置为开始时的样子。
|
||||
- 如果失败的次数超过了一定的阈值(`failureThreshold`),断路器就会进入 `open` 开启状态,它的作用就像一个开启的电路,阻止远程服务的调用,从而节省资源。
|
||||
- 一旦我们超过重试时间周期(`retryTimePeriod`),断路器就会转到 `half-open` 半启用状态,并再次调用远程服务,检查服务是否正常,以便我们可以提供最新的响应内容。如果远程服务调用失败会使断路器回到 `open` 状态,并在重试超时后进行另一次尝试;如果远程服务调用成功则使断路器进入 `closed` 状态,这样一切又开始正常工作。
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用场景
|
||||
|
||||
在以下场景下,可以使用断路器模式:
|
||||
|
||||
- 构建一个高可用的应用程序,某些些服务的失败不会导致整个应用程序的崩溃。
|
||||
- 构建一个持续运行(长期在线)的应用程序,以便其组件可以在不完全关闭的情况下进行升级。
|
||||
|
||||
## 相关模式
|
||||
|
||||
- [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry)
|
||||
|
||||
## 现实案例
|
||||
|
||||
* [Spring Circuit Breaker module](https://spring.io/guides/gs/circuit-breaker)
|
||||
* [Netflix Hystrix API](https://github.com/Netflix/Hystrix)
|
||||
|
||||
## 引用
|
||||
|
||||
* [Understanding Circuit Breaker Pattern](https://itnext.io/understand-circuitbreaker-design-pattern-with-simple-practical-example-92a752615b42)
|
||||
* [Martin Fowler on Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html)
|
||||
* [Fault tolerance in a high volume, distributed system](https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a)
|
||||
* [Circuit Breaker pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
|
@ -1,29 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Collection Pipeline
|
||||
folder: collection-pipeline
|
||||
permalink: /patterns/collection-pipeline/
|
||||
categories: Functional
|
||||
tags:
|
||||
- Reactive
|
||||
---
|
||||
|
||||
## 释义
|
||||
**集合管道(Collection Pipeline)**包含**函数组合(Function Composition)**和**集合管道(Collection Pipeline)**两组合概念,这是两种函数式编程模式,你可以在代码中结合这两种模式来进行集合迭代。
|
||||
在函数式编程中,可以通过一系列较小的模块化函数或操作来编排复杂的操作。这一系列函数被称为函数组合。当一个数据集合流经一个函数组合时,它就成为一个集合管道。函数组合和集合管道是函数式编程中经常使用的两种设计模式。
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用场景
|
||||
在以下场景适用集合管道模式:
|
||||
|
||||
* 当你想执行一组连续的算子操作,其中一个算子收集的输出需要被输入到下一个算子中
|
||||
* 当你在代码中需要使用大量的中间状态语句时
|
||||
* 当你在代码中使用大量的循环语句时
|
||||
|
||||
## 引用
|
||||
|
||||
* [Function composition and the Collection Pipeline pattern](https://www.ibm.com/developerworks/library/j-java8idioms2/index.html)
|
||||
* [Martin Fowler](https://martinfowler.com/articles/collection-pipeline/)
|
||||
* [Java8 Streams](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html)
|
@ -1,121 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Composite Entity
|
||||
folder: composite-entity
|
||||
permalink: /patterns/composite-entity/
|
||||
categories: Structural
|
||||
tags:
|
||||
- Enterprise Integration Pattern
|
||||
---
|
||||
|
||||
## 含义
|
||||
|
||||
复合实体模式用于对一组相关联的持久化对象进行建模、描述和管理,用于取代对这组对象描述为单独粒度的实体。
|
||||
|
||||
## 解释
|
||||
|
||||
现实例子
|
||||
|
||||
> 对于一个控制台对象,需要管理许多接口功能。通过使用复合实体模式,将消息对象、信号对象等依赖性对象组合在一起,直接使用单个对象对其进行控制。
|
||||
|
||||
简单地说
|
||||
|
||||
> 复合实体模式允许使用一个统一对象来管理一组相互关联的对象
|
||||
|
||||
**编程示例**
|
||||
|
||||
我们需要一个通用的解决方案来解决上述的控制台问题。我们引入了以下的通用复合对象。
|
||||
|
||||
```java
|
||||
public abstract class DependentObject<T> {
|
||||
|
||||
T data;
|
||||
|
||||
public void setData(T message) {
|
||||
this.data = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class CoarseGrainedObject<T> {
|
||||
|
||||
DependentObject<T>[] dependentObjects;
|
||||
|
||||
public void setData(T... data) {
|
||||
IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i]));
|
||||
}
|
||||
|
||||
public T[] getData() {
|
||||
return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
专用的 `console` 复合实体继承自这个基类,如下所示。
|
||||
|
||||
```java
|
||||
public class MessageDependentObject extends DependentObject<String> {
|
||||
|
||||
}
|
||||
|
||||
public class SignalDependentObject extends DependentObject<String> {
|
||||
|
||||
}
|
||||
|
||||
public class ConsoleCoarseGrainedObject extends CoarseGrainedObject<String> {
|
||||
|
||||
@Override
|
||||
public String[] getData() {
|
||||
super.getData();
|
||||
return new String[]{
|
||||
dependentObjects[0].getData(), dependentObjects[1].getData()
|
||||
};
|
||||
}
|
||||
|
||||
public void init() {
|
||||
dependentObjects = new DependentObject[]{
|
||||
new MessageDependentObject(), new SignalDependentObject()};
|
||||
}
|
||||
}
|
||||
|
||||
public class CompositeEntity {
|
||||
|
||||
private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject();
|
||||
|
||||
public void setData(String message, String signal) {
|
||||
console.setData(message, signal);
|
||||
}
|
||||
|
||||
public String[] getData() {
|
||||
return console.getData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在我们使用 `console` 复合实体来进行消息对象、信号对象的分配。
|
||||
|
||||
```java
|
||||
var console = new CompositeEntity();
|
||||
console.init();
|
||||
console.setData("No Danger", "Green Light");
|
||||
Arrays.stream(console.getData()).forEach(LOGGER::info);
|
||||
console.setData("Danger", "Red Light");
|
||||
Arrays.stream(console.getData()).forEach(LOGGER::info);
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用场景
|
||||
|
||||
复合实体模式适用于以下场景:
|
||||
|
||||
* 你想要通过一个对象来管理多个依赖对象,已调整对象之间的细化程度。同时将依赖对象的生命周期托管到这个粗粒度的复合实体对象。
|
||||
## 引用
|
||||
|
||||
* [Composite Entity Pattern in wikipedia](https://en.wikipedia.org/wiki/Composite_entity_pattern)
|
@ -1,31 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Data Bus
|
||||
folder: data-bus
|
||||
permalink: /patterns/data-bus/
|
||||
|
||||
categories: Architectural
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## 含义
|
||||
|
||||
数据总线模式(译者:实际上,就是 Event-Bus 消息总线模式)允许在一个应用程序的组件之间收发消息/事件,而不需要这些组件相互感知,它们只需要知道所发送/接收的消息/事件的类型即可。
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用场景
|
||||
可以在以下场景使用数据总线模式:
|
||||
|
||||
* 你希望由你的组件自己决定要接收哪些信息/事件
|
||||
* 你希望实现多对多的通信
|
||||
* 你希望你的组件不需要感知彼此
|
||||
|
||||
## 相关模式
|
||||
数据总线类似于以下设计模式:
|
||||
|
||||
* 中介者模式(Mediator pattern),由数据总线成员自己决定是否要接受任何给定的消息。
|
||||
* 观察者模式(Observer pattern),但进一步支持了多对多的通信。
|
||||
* 发布/订阅模式(Publish/Subscribe pattern),但是数据总线将发布者和订阅者解耦。
|
@ -1,25 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Data Mapper
|
||||
folder: data-mapper
|
||||
permalink: /patterns/data-mapper/
|
||||
categories: Architectural
|
||||
tags:
|
||||
- Decoupling
|
||||
---
|
||||
|
||||
## 含义
|
||||
一个用于在持久化对象和数据库之间传输数据的映射器,同时保持它们之间和映射器本身的独立性。
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用场景
|
||||
数据映射器适用于以下场景:
|
||||
|
||||
* 当你想把数据对象从数据库访问层解耦时时
|
||||
* 当你想编写多个数据查询/持久化实现时
|
||||
|
||||
## 引用
|
||||
|
||||
* [Data Mapper](http://richard.jp.leguen.ca/tutoring/soen343-f2010/tutorials/implementing-data-mapper/)
|
@ -1,21 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Double Checked Locking
|
||||
folder: double-checked-locking
|
||||
permalink: /patterns/double-checked-locking/
|
||||
categories: Idiom
|
||||
tags:
|
||||
- Performance
|
||||
---
|
||||
|
||||
## 含义
|
||||
通过先测试锁定标准("锁提示")而不实际获取锁的方式来减少获取锁的开销。只有当锁定标准检查表明需要锁定时,才进行实际的锁定逻辑。
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用场景
|
||||
在以下场景适合使用双重锁检查模式:
|
||||
|
||||
* 在创建对象时有存在并发的访问。如单例模式中,你想创建同一个类的单个实例,如果存在两个或更多的线程对实例进行判空,仅仅检查该该实例是否为空可能是不够的。
|
||||
* 在一个方法上存在并发访问,该方法的行为是根据一些约束条件而改变,而这些约束条件在该方法中也会发生变化。
|
@ -1,27 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Factory Kit
|
||||
folder: factory-kit
|
||||
permalink: /patterns/factory-kit/
|
||||
categories: Creational
|
||||
tags:
|
||||
- Extensibility
|
||||
---
|
||||
|
||||
## 含义
|
||||
使用分离的构建器和工厂接口来定义一个不可变内容的工厂。
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用场景
|
||||
工厂套件模式适用于与以下场景:
|
||||
|
||||
* 一个类无法预知它需要创建的对象的类别
|
||||
- 你只是想要一个新的自定义构建器(builder)的实例,而非全局的构建器
|
||||
- 你明确地想要定义对象的类型,而且工厂可以创建这些对象
|
||||
- 你想要分离构建器(builder)和创建器(creator)接口
|
||||
|
||||
## 引用
|
||||
|
||||
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)
|
@ -1,141 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Factory
|
||||
folder: factory
|
||||
permalink: /patterns/factory/
|
||||
categories: Creational
|
||||
tags:
|
||||
- Gang of Four
|
||||
---
|
||||
|
||||
## 也被称为
|
||||
|
||||
* 简单工厂
|
||||
* 静态工厂方法
|
||||
|
||||
## 含义
|
||||
|
||||
在工厂类中提供一个封装的静态工厂方法,用于隐藏对象初始化细节,使客户端代码可以专注于使用,而不用关心类的初始化过程。
|
||||
|
||||
## 解释
|
||||
|
||||
现实例子
|
||||
|
||||
>
|
||||
> 假设我们有一个需要连接到 SQL Server 的 Web 应用,但现在我们需要切换到连接 Oracle。为了不修改现有代码的情况下做到这一点,我们需要实现简单工厂模式。在这种模式下,可以通过调用一个静态方法来创建与给定数据库的连接。
|
||||
|
||||
维基百科
|
||||
|
||||
> 工厂类是一个用于创建其他对象的对象 -- 从形式上看,工厂方法是一个用于返回不同原型或类型的函数或方法。
|
||||
|
||||
**编程示例**
|
||||
|
||||
我们有一个 `Car` 接口,以及实现类 `Ford`, `Ferrari`。
|
||||
|
||||
```java
|
||||
public interface Car {
|
||||
String getDescription();
|
||||
}
|
||||
|
||||
public class Ford implements Car {
|
||||
|
||||
static final String DESCRIPTION = "This is Ford.";
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
}
|
||||
|
||||
public class Ferrari implements Car {
|
||||
|
||||
static final String DESCRIPTION = "This is Ferrari.";
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enumeration above represents types of cars that we support (`Ford` and `Ferrari`).
|
||||
|
||||
以下的枚举用于表示支持的 `Car` 类型(`Ford` 和 `Ferrari`)
|
||||
|
||||
```java
|
||||
public enum CarType {
|
||||
|
||||
FORD(Ford::new),
|
||||
FERRARI(Ferrari::new);
|
||||
|
||||
private final Supplier<Car> constructor;
|
||||
|
||||
CarType(Supplier<Car> constructor) {
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
public Supplier<Car> getConstructor() {
|
||||
return this.constructor;
|
||||
}
|
||||
}
|
||||
```
|
||||
接着我们实现了一个静态方法 `getCar` 用于封装工厂类 `CarsFactory` 创建 `Car` 具体对象实例的细节。
|
||||
|
||||
```java
|
||||
public class CarsFactory {
|
||||
|
||||
public static Car getCar(CarType type) {
|
||||
return type.getConstructor().get();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在我们可以在客户端代码中通过工厂类创建不同类型的 `Car` 对象实例。
|
||||
|
||||
```java
|
||||
var car1 = CarsFactory.getCar(CarType.FORD);
|
||||
var car2 = CarsFactory.getCar(CarType.FERRARI);
|
||||
LOGGER.info(car1.getDescription());
|
||||
LOGGER.info(car2.getDescription());
|
||||
```
|
||||
|
||||
程序输出:
|
||||
|
||||
```java
|
||||
This is Ford.
|
||||
This is Ferrari.
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用场景
|
||||
|
||||
在你只关心对象的创建,但不关心如何创建、管理它的时候,请使用简单工厂模式。
|
||||
|
||||
**优点**
|
||||
|
||||
* 可以把对象创建代码集中在一个地方,避免在代码库存散布 "new" 关键字。
|
||||
* 可以让代码更加低耦合。它的一些主要优点包括更好的可测试性、更好的可读性、组件可替换性、可拓展性、更好的隔离性。
|
||||
|
||||
**缺点**
|
||||
|
||||
* 会使代码变得比原来的更加复杂一些。
|
||||
|
||||
## 现实案例
|
||||
|
||||
* [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
|
||||
* [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)
|
||||
* [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--)
|
||||
* [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-)
|
||||
* [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) (Returns different singleton objects, depending on a protocol)
|
||||
* [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E))
|
||||
* [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) and other similar methods.
|
||||
|
||||
## 相关模式
|
||||
|
||||
* [Factory Method](https://java-design-patterns.com/patterns/factory-method/)
|
||||
* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/)
|
||||
* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/)
|
||||
|
@ -1,30 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Sharding
|
||||
folder: sharding
|
||||
permalink: /patterns/sharding/
|
||||
categories: Behavioral
|
||||
tags:
|
||||
- Performance
|
||||
- Cloud distributed
|
||||
---
|
||||
|
||||
## 含义
|
||||
分片模式是指将数据存储划分为水平分区或分片。每个分片都有相同的模式,但持有自己独特的数据子集。
|
||||
|
||||
一个分片本身就是一个数据存储(它可以包含许多不同类型的实体的数据),运行在作为存储节点的服务器上。
|
||||
|
||||
## 类图
|
||||

|
||||
|
||||
## 适用场景
|
||||
这种设计模式提供了一下的好处:
|
||||
|
||||
- 你可以通过增加在额外的存储节点上,运行的更多分片来实现系统扩容。
|
||||
- 系统可以使用现成的廉价硬件,而不是为每个存储节点使用专门(或者昂贵)的服务器硬件。
|
||||
- 你可以通过平衡各分片之间的工作负载来减少竞争,以提高性能。
|
||||
- 在云环境中,分片可以在物理上靠近访问该节点数据的用户。
|
||||
|
||||
## 引用
|
||||
|
||||
* [Sharding pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/sharding)
|
@ -1,163 +0,0 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Version Number
|
||||
folder: versionnumber
|
||||
permalink: /patterns/versionnumber/
|
||||
description: Entity versioning with version number
|
||||
|
||||
categories:
|
||||
- Concurrency
|
||||
|
||||
tags:
|
||||
- Data access
|
||||
- Microservices
|
||||
---
|
||||
|
||||
## 名字 / 分类
|
||||
|
||||
版本号
|
||||
|
||||
## 或称
|
||||
|
||||
实体版本控制,乐观锁。
|
||||
|
||||
## 目的
|
||||
|
||||
解决多个客户端尝试同时更新同一实体时的并发冲突。
|
||||
|
||||
## 解释
|
||||
|
||||
现实世界的例子
|
||||
|
||||
> 爱丽丝(Alice)和鲍勃(Bob)正在管理书,该书存储在数据库中。 我们的英雄们正在同时进行更改,我们需要某种机制来防止他们相互覆盖。
|
||||
|
||||
通俗地说
|
||||
|
||||
> 版本号模式可防止对同一实体进行并发更新。
|
||||
|
||||
维基百科说
|
||||
|
||||
> 乐观并发控制假设多个事务可以频繁完成而不会互相干扰。 在运行时,事务使用数据资源而不获取这些资源的锁。 在提交之前,每个事务都将验证没有其他事务修改了已读取的数据。如果检查发现有冲突的修改,则提交的事务将回滚并可以重新启动。
|
||||
|
||||
**程序示例**
|
||||
|
||||
我们有`Book` 已版本化的实体,它有一个复制构造函数。
|
||||
|
||||
```java
|
||||
public class Book {
|
||||
private long id;
|
||||
private String title = "";
|
||||
private String author = "";
|
||||
|
||||
private long version = 0; // version number
|
||||
|
||||
public Book(Book book) {
|
||||
this.id = book.id;
|
||||
this.title = book.title;
|
||||
this.author = book.author;
|
||||
this.version = book.version;
|
||||
}
|
||||
|
||||
// getters and setters are omitted here
|
||||
}
|
||||
```
|
||||
|
||||
我们还有一个 `BookRepository`, 它实现了并发控制。
|
||||
|
||||
```java
|
||||
public class BookRepository {
|
||||
private final Map<Long, Book> collection = new HashMap<>();
|
||||
|
||||
public void update(Book book) throws BookNotFoundException, VersionMismatchException {
|
||||
if (!collection.containsKey(book.getId())) {
|
||||
throw new BookNotFoundException("Not found book with id: " + book.getId());
|
||||
}
|
||||
|
||||
var latestBook = collection.get(book.getId());
|
||||
if (book.getVersion() != latestBook.getVersion()) {
|
||||
throw new VersionMismatchException(
|
||||
"Tried to update stale version " + book.getVersion()
|
||||
+ " while actual version is " + latestBook.getVersion()
|
||||
);
|
||||
}
|
||||
|
||||
// update version, including client representation - modify by reference here
|
||||
book.setVersion(book.getVersion() + 1);
|
||||
|
||||
// save book copy to repository
|
||||
collection.put(book.getId(), new Book(book));
|
||||
}
|
||||
|
||||
public Book get(long bookId) throws BookNotFoundException {
|
||||
if (!collection.containsKey(bookId)) {
|
||||
throw new BookNotFoundException("Not found book with id: " + bookId);
|
||||
}
|
||||
|
||||
// return copy of the book
|
||||
return new Book(collection.get(bookId));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这是实践中的并发控制:
|
||||
|
||||
```java
|
||||
var bookId = 1;
|
||||
// Alice and Bob took the book concurrently
|
||||
final var aliceBook = bookRepository.get(bookId);
|
||||
final var bobBook = bookRepository.get(bookId);
|
||||
|
||||
aliceBook.setTitle("Kama Sutra"); // Alice has updated book title
|
||||
bookRepository.update(aliceBook); // and successfully saved book in database
|
||||
LOGGER.info("Alice updates the book with new version {}", aliceBook.getVersion());
|
||||
|
||||
// now Bob has the stale version of the book with empty title and version = 0
|
||||
// while actual book in database has filled title and version = 1
|
||||
bobBook.setAuthor("Vatsyayana Mallanaga"); // Bob updates the author
|
||||
try {
|
||||
LOGGER.info("Bob tries to update the book with his version {}", bobBook.getVersion());
|
||||
bookRepository.update(bobBook); // Bob tries to save his book to database
|
||||
} catch (VersionMismatchException e) {
|
||||
// Bob update fails, and book in repository remained untouchable
|
||||
LOGGER.info("Exception: {}", e.getMessage());
|
||||
// Now Bob should reread actual book from repository, do his changes again and save again
|
||||
}
|
||||
```
|
||||
|
||||
程序输出:
|
||||
|
||||
```java
|
||||
Alice updates the book with new version 1
|
||||
Bob tries to update the book with his version 0
|
||||
Exception: Tried to update stale version 0 while actual version is 1
|
||||
```
|
||||
|
||||
## 类图
|
||||
|
||||

|
||||
|
||||
## 适用性
|
||||
|
||||
将版本号用于:
|
||||
|
||||
* 解决对数据的并发写访问
|
||||
* 强的数据一致性
|
||||
|
||||
## 教程
|
||||
* [Version Number Pattern Tutorial](http://www.java2s.com/Tutorial/Java/0355__JPA/VersioningEntity.htm)
|
||||
|
||||
## 已知用途
|
||||
* [Hibernate](https://vladmihalcea.com/jpa-entity-version-property-hibernate/)
|
||||
* [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning)
|
||||
* [Apache Solr](https://lucene.apache.org/solr/guide/6_6/updating-parts-of-documents.html)
|
||||
|
||||
## 意义
|
||||
版本号模式允许实现并发控制,通常通过乐观离线锁模式来完成。
|
||||
|
||||
## 相关模式
|
||||
* [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html)
|
||||
|
||||
## 鸣谢
|
||||
* [Optimistic Locking in JPA](https://www.baeldung.com/jpa-optimistic-locking)
|
||||
* [JPA entity versioning](https://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking)
|
||||
* [J2EE Design Patterns](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf)
|
Reference in New Issue
Block a user