Compare commits

...

17 Commits

Author SHA1 Message Date
allcontributors[bot]
e29a18860a docs: update .all-contributorsrc [skip ci] 2021-05-17 19:08:17 +00:00
allcontributors[bot]
08caf6ddb0 docs: update README.md [skip ci] 2021-05-17 19:08:16 +00:00
Tao
e498c25675 feature: #1319 add table module pattern (#1742)
* modify table module pattern

* fix code smells

* resolve conversation

Co-authored-by: tao-sun2 <sustc18st@gmai.com>
Co-authored-by: Ilkka Seppälä <iluwatar@users.noreply.github.com>
2021-05-17 22:06:35 +03:00
Noam Greenshtain
122e6edb38 feature: resolve #1282 for Lockable Object pattern. (#1702)
* Added Lockable-Object pattern. Closes #1282.

* Refactor method name.

* Refactor sonar lint bugs.

* Added tests and enum Constants.

* Increase coverage.

* Changed @Data to Getters and Setters.

* Iluwatar's comment on pull request #1702.

* Fixed codes mells.

* Incremented wait time to 3 seconds.

* Reduced wait time to 2 seconds.

* Cleaned Code Smells.

* Incremented wait time, removed cool down.

* Refactored README.md file.

Co-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
2021-05-14 21:26:41 +05:30
allcontributors[bot]
ea3c9d955e docs: add EdisonE3 as a contributor (#1751)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-05-11 19:17:36 +05:30
EdisonE3
f1feb3f6a0 feature: Implement Presentation Model Pattern (#1710)
* #415 initial all componets

* #415 add src and tests

* #415 add diagram

* #415 add README

* #415 add README

* #415 change pom.xml

* #415 change pom.xml

* #415 change pom.xml

* #415 update pom.xml

* #415 change some code smell

* #415 change some code smell

* #415 update test

* #415 add javadoc

* #415 remove author tag

* #415 add lombok @AllArgsConstructor

* #415 fix code converge

* #415 fix code converge

* #415 fix code converge

* #415 add javadoc

* #415 fix code smell

* #415 fix code smell

* #415 add log information

* #415 remove unused import

* #415 add javadoc and more test

* #415 modify test

* #415 fix checkstyle

* #415 remove useless code and add more javadoc and test.

* #415 add package-info.java.

* #415 add package-info.java.

* #415 add more test.

* #415 fix code smell.

* #415 fix code smell and increase code coverage.

* #415 fix code smell.

* #415 update README.md

* #415 update README.md

* #415 make this demo better

* #415 satisfy checkstyle

* #415 make some field static.

* #415 make some fields static.

* #415 rename some fields static.

* Delete package-info.java

Co-authored-by: Subhrodip Mohanta <subhrodipmohanta@gmail.com>
2021-05-11 19:16:11 +05:30
VR
1388e38744 docs: Correcting the URL to fix #1747 (#1748)
* Correcting the Hyperlink to redirect to the correct URL.
Corresponds to bug #1703

* Deleting the extra Hyphen(-) that was incorrectly added.

* Corrected the contributors inside the Readme file.

Updated the correct number of contributors to be in sync with `master`

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-05-11 16:30:30 +05:30
allcontributors[bot]
1dd26289e5 docs: add JackieNim as a contributor (#1749)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-05-09 17:00:47 +03:00
allcontributors[bot]
241a7ad9a2 docs: add DEV-VRUPER as a contributor (#1746)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-05-07 00:01:23 +05:30
VR
32b33480dd docs: Correcting the Hyperlink to redirect to correct URL. (#1745)
Corresponds to bug #1703

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-05-06 23:55:29 +05:30
余林颖
068fa0371e docs: Translate some of the README docs into Chinese (#1744)
* docs: translated docs to zh

* docs: translated doc of sharding pattern to Chinese

* docs: translated doc of factory pattern to Chinese

* docs: translated doc of factory-kit pattern to Chinese

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-05-06 23:27:16 +05:30
余林颖
b5aaa94794 docs: replace more suitable translation (#1743)
Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
2021-05-03 20:25:57 +05:30
余林颖
825b5a9a29 docs: translated the async method invocation pattern into Chinese (#1741) 2021-05-02 20:34:34 +03:00
Subhrodip Mohanta
31890f67e6 updated with neccessary changes (#1709) 2021-05-01 21:22:35 +03:00
dependabot-preview[bot]
41b1462eed [Security] Bump spring-webmvc from 5.0.13.RELEASE to 5.0.17.RELEASE (#1739)
Bumps [spring-webmvc](https://github.com/spring-projects/spring-framework) from 5.0.13.RELEASE to 5.0.17.RELEASE. **This update includes a security fix.**
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.0.13.RELEASE...v5.0.17.RELEASE)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-05-01 10:29:50 +05:30
dependabot-preview[bot]
276549d156 [Security] Bump jackson.version from 2.10.2 to 2.12.3 (#1738)
Bumps `jackson.version` from 2.10.2 to 2.12.3.

Updates `jackson-core` from 2.10.2 to 2.12.3
- [Release notes](https://github.com/FasterXML/jackson-core/releases)
- [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.10.2...jackson-core-2.12.3)

Updates `jackson-databind` from 2.10.2 to 2.12.3
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

Updates `jackson-annotations` from 2.10.2 to 2.12.3
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-05-01 10:29:05 +05:30
allcontributors[bot]
d67b625a74 docs: add STudio26 as a contributor (#1735)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: Subhrodip Mohanta <hello@subho.xyz>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-04-29 18:55:41 +05:30
66 changed files with 3787 additions and 24 deletions

View File

@@ -1478,6 +1478,51 @@
"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,

View File

@@ -40,26 +40,26 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@master
uses: actions/checkout@v2
with:
# Disabling shallow clone for improving relevancy of SonarQube reporting
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@master
uses: actions/setup-java@v2
with:
java-version: 11
distribution: 'adopt'
- name: Cache SonarCloud packages
uses: actions/cache@master
uses: actions/cache@v2
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven dependencies
uses: actions/cache@master
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

View File

@@ -38,16 +38,16 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@master
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@master
uses: actions/setup-java@v2
with:
java-version: 11
distribution: 'adopt'
- name: Cache Maven Dependecies
uses: actions/cache@master
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

View File

@@ -10,7 +10,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](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 -->
[![All Contributors](https://img.shields.io/badge/all_contributors-162-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-167-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/>
@@ -318,6 +318,13 @@ This project is licensed under the terms of the MIT license.
<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>

View File

@@ -25,4 +25,4 @@ This pattern is one of those ones where youll know when you need it. If you h
## Credits
* [Game Programming Patterns - Double Buffer]([http://gameprogrammingpatterns.com/double-buffer.html](http://gameprogrammingpatterns.com/double-buffer.html))
* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html)

285
lockable-object/README.md Normal file
View File

@@ -0,0 +1,285 @@
---
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
![alt text](./etc/lockable-object.urm.png "Lockable Object class diagram")
## Credits
* [Lockable Object - Chapter 10.3, J2EE Design Patterns, O'Reilly](http://ommolketab.ir/aaf-lib/axkwht7wxrhvgs2aqkxse8hihyu9zv.pdf)

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -0,0 +1,94 @@
@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

60
lockable-object/pom.xml Normal file
View File

@@ -0,0 +1,60 @@
<?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>

View File

@@ -0,0 +1,77 @@
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();
}
}
}

View File

@@ -0,0 +1,43 @@
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();
}

View File

@@ -0,0 +1,14 @@
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);
}
}

View File

@@ -0,0 +1,66 @@
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;
}
}

View File

@@ -0,0 +1,89 @@
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;
}
}

View File

@@ -0,0 +1,21 @@
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;
}
}

View File

@@ -0,0 +1,8 @@
package com.iluwatar.lockableobject.domain;
/** Constants of supported creatures. */
public enum CreatureType {
ORC,
HUMAN,
ELF
}

View File

@@ -0,0 +1,17 @@
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());
}
}

View File

@@ -0,0 +1,71 @@
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());
}
}
}
}

View File

@@ -0,0 +1,17 @@
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());
}
}

View File

@@ -0,0 +1,16 @@
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());
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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());
}
}

View File

@@ -0,0 +1,79 @@
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));
}
}

View File

@@ -0,0 +1,21 @@
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());
}
}

View File

@@ -0,0 +1,47 @@
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());
}
}

View File

@@ -0,0 +1,24 @@
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());
}
}

View File

@@ -0,0 +1,27 @@
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));
}
}

View File

@@ -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.13.RELEASE</spring.version>
<spring.version>5.0.17.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.10.2</jackson.version>
<jackson.version>2.12.3</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>
@@ -225,6 +225,9 @@
<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>

193
presentation/README.md Normal file
View File

@@ -0,0 +1,193 @@
---
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:
![](./etc/result.png)
## Class diagram
![](./etc/presentation.urm.png "presentation model")
## 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.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -0,0 +1,59 @@
@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

BIN
presentation/etc/result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

63
presentation/pom.xml Normal file
View File

@@ -0,0 +1,63 @@
<!--
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>

View File

@@ -0,0 +1,31 @@
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;
}

View File

@@ -0,0 +1,27 @@
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();
}
}

View File

@@ -0,0 +1,46 @@
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, ""));
}
}
}

View File

@@ -0,0 +1,163 @@
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;
}
}

View File

@@ -0,0 +1,167 @@
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);
}
}

View File

@@ -0,0 +1,37 @@
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());
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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[]{}));
}
}

View File

@@ -0,0 +1,22 @@
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());
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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());
}
}

View File

@@ -0,0 +1,55 @@
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());
}
}

View File

@@ -131,9 +131,10 @@ public class App {
* log requests.
*/
reactor
.registerChannel(tcpChannel(6666, loggingHandler))
.registerChannel(tcpChannel(6667, loggingHandler))
.registerChannel(udpChannel(6668, loggingHandler))
.registerChannel(tcpChannel(16666, loggingHandler))
.registerChannel(tcpChannel(16667, loggingHandler))
.registerChannel(udpChannel(16668, loggingHandler))
.registerChannel(udpChannel(16669, loggingHandler))
.start();
}

View File

@@ -63,10 +63,10 @@ public class AppClient {
*/
public void start() throws IOException {
LOGGER.info("Starting logging clients");
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));
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));
}
/**
@@ -114,7 +114,7 @@ public class AppClient {
@Override
public void run() {
try (Socket socket = new Socket(InetAddress.getLocalHost(), serverPort)) {
try (var socket = new Socket(InetAddress.getLocalHost(), serverPort)) {
var outputStream = socket.getOutputStream();
var writer = new PrintWriter(outputStream);
sendLogRequests(writer, socket.getInputStream());

134
table-module/README.md Normal file
View File

@@ -0,0 +1,134 @@
---
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
![](./etc/table-module.urm.png "table module")
## 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.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -0,0 +1,38 @@
@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

73
table-module/pom.xml Normal file
View File

@@ -0,0 +1,73 @@
<?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>

View File

@@ -0,0 +1,81 @@
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;
}
}

View File

@@ -0,0 +1,23 @@
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;
}

View File

@@ -0,0 +1,96 @@
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;
}
}
}

View File

@@ -0,0 +1,16 @@
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[]{}));
}
}

View File

@@ -0,0 +1,78 @@
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));
}
}

View File

@@ -0,0 +1,131 @@
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());
}
}

View File

@@ -0,0 +1,161 @@
---
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
```
## 类图
![alt text](../../async-method-invocation/etc/async-method-invocation.png "Async Method Invocation")
## 适用场景
在以下场景可以使用异步调用模式
* 你有多有可以并行执行的独立任务
* 你需要提高一组串行任务的性能
* 你的处理能力有限、或者有长期运行的任务,调用者不应该等待任务所有任务运行结束
## 现实示例
* [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)

View File

@@ -8,7 +8,7 @@ tags:
- Decoupling
---
## 作用
## 含义
阻止模式用于防止一个对象在不完整或不适当的状态下执行某段代码。
@@ -112,7 +112,7 @@ public interface DelayProvider {
![alt text](../../balking/etc/balking.png "Balking")
## 适用
## 适用场景
在以下情况下可以使用阻止模式:
@@ -124,6 +124,6 @@ public interface DelayProvider {
* [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)

View File

@@ -8,7 +8,7 @@ tags:
- Decoupling
---
## 作用
## 含义
业务委托模式(译者:国内也有翻译成业务代表模式)在表现层和业务层之间增加了一个抽象层。通过使用该模式,我们获得了各层之间的松散耦合,并封装了关于如何定位、连接和与构成应用程序的业务对象进行交互的知识。
@@ -154,7 +154,7 @@ public class MobileClient {
* [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)

View File

@@ -0,0 +1,312 @@
---
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模拟了网络应用进行本地和远程调用。
该服务架构如下:
![alt text](../../circuit-breaker/etc/ServiceDiagram.png "Service Diagram")
终端用户(译者:上图的 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;
}
}
}
}
```
上述模式是如何防止失败的呢?让我们通过它所实现的这个有限状态机来了解。
![alt text](../../circuit-breaker/etc/StateDiagram.png "State Diagram")
- 我们用 `timeout`(超时)、 `failureThreshold` (失败阈值)、`retryTimePeriod`(重试时间周期) 参数初始化断路器对象 ,用于确定 API 的适应性。
- 最初,断路器处于 `closed` 关闭状态,没有发生对 API 的远程调用。
- 每次调用成功,我们就把状态重置为开始时的样子。
- 如果失败的次数超过了一定的阈值(`failureThreshold`),断路器就会进入 `open` 开启状态,它的作用就像一个开启的电路,阻止远程服务的调用,从而节省资源。
- 一旦我们超过重试时间周期(`retryTimePeriod`),断路器就会转到 `half-open` 半启用状态,并再次调用远程服务,检查服务是否正常,以便我们可以提供最新的响应内容。如果远程服务调用失败会使断路器回到 `open` 状态,并在重试超时后进行另一次尝试;如果远程服务调用成功则使断路器进入 `closed` 状态,这样一切又开始正常工作。
## 类图
![alt text](../../circuit-breaker/etc/circuit-breaker.urm.png "Circuit Breaker class diagram")
## 适用场景
在以下场景下,可以使用断路器模式:
- 构建一个高可用的应用程序,某些些服务的失败不会导致整个应用程序的崩溃。
- 构建一个持续运行(长期在线)的应用程序,以便其组件可以在不完全关闭的情况下进行升级。
## 相关模式
- [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)

View File

@@ -0,0 +1,29 @@
---
layout: pattern
title: Collection Pipeline
folder: collection-pipeline
permalink: /patterns/collection-pipeline/
categories: Functional
tags:
- Reactive
---
## 释义
**集合管道Collection Pipeline**包含**函数组合Function Composition**和**集合管道Collection Pipeline**两组合概念,这是两种函数式编程模式,你可以在代码中结合这两种模式来进行集合迭代。
在函数式编程中,可以通过一系列较小的模块化函数或操作来编排复杂的操作。这一系列函数被称为函数组合。当一个数据集合流经一个函数组合时,它就成为一个集合管道。函数组合和集合管道是函数式编程中经常使用的两种设计模式。
## 类图
![alt text](../../collection-pipeline/etc/collection-pipeline.png "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)

View File

@@ -0,0 +1,121 @@
---
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);
```
## 类图
![alt text](../../composite-entity/etc/composite_entity.urm.png "Composite Entity Pattern")
## 适用场景
复合实体模式适用于以下场景:
* 你想要通过一个对象来管理多个依赖对象,已调整对象之间的细化程度。同时将依赖对象的生命周期托管到这个粗粒度的复合实体对象。
## 引用
* [Composite Entity Pattern in wikipedia](https://en.wikipedia.org/wiki/Composite_entity_pattern)

31
zh/data-bus/README.md Normal file
View File

@@ -0,0 +1,31 @@
---
layout: pattern
title: Data Bus
folder: data-bus
permalink: /patterns/data-bus/
categories: Architectural
tags:
- Decoupling
---
## 含义
数据总线模式(译者:实际上,就是 Event-Bus 消息总线模式)允许在一个应用程序的组件之间收发消息/事件,而不需要这些组件相互感知,它们只需要知道所发送/接收的消息/事件的类型即可。
## 类图
![data bus pattern uml diagram](../../data-bus/etc/data-bus.urm.png "Data Bus pattern")
## 适用场景
可以在以下场景使用数据总线模式:
* 你希望由你的组件自己决定要接收哪些信息/事件
* 你希望实现多对多的通信
* 你希望你的组件不需要感知彼此
## 相关模式
数据总线类似于以下设计模式:
* 中介者模式Mediator pattern由数据总线成员自己决定是否要接受任何给定的消息。
* 观察者模式Observer pattern但进一步支持了多对多的通信。
* 发布/订阅模式Publish/Subscribe pattern但是数据总线将发布者和订阅者解耦。

25
zh/data-mapper/README.md Normal file
View File

@@ -0,0 +1,25 @@
---
layout: pattern
title: Data Mapper
folder: data-mapper
permalink: /patterns/data-mapper/
categories: Architectural
tags:
- Decoupling
---
## 含义
一个用于在持久化对象和数据库之间传输数据的映射器,同时保持它们之间和映射器本身的独立性。
## 类图
![alt text](../../data-mapper/etc/data-mapper.png "Data Mapper")
## 适用场景
数据映射器适用于以下场景:
* 当你想把数据对象从数据库访问层解耦时时
* 当你想编写多个数据查询/持久化实现时
## 引用
* [Data Mapper](http://richard.jp.leguen.ca/tutoring/soen343-f2010/tutorials/implementing-data-mapper/)

View File

@@ -0,0 +1,21 @@
---
layout: pattern
title: Double Checked Locking
folder: double-checked-locking
permalink: /patterns/double-checked-locking/
categories: Idiom
tags:
- Performance
---
## 含义
通过先测试锁定标准("锁提示")而不实际获取锁的方式来减少获取锁的开销。只有当锁定标准检查表明需要锁定时,才进行实际的锁定逻辑。
## 类图
![alt text](../../double-checked-locking/etc/double_checked_locking_1.png "Double Checked Locking")
## 适用场景
在以下场景适合使用双重锁检查模式:
* 在创建对象时有存在并发的访问。如单例模式中,你想创建同一个类的单个实例,如果存在两个或更多的线程对实例进行判空,仅仅检查该该实例是否为空可能是不够的。
* 在一个方法上存在并发访问,该方法的行为是根据一些约束条件而改变,而这些约束条件在该方法中也会发生变化。

27
zh/factory-kit/README.md Normal file
View File

@@ -0,0 +1,27 @@
---
layout: pattern
title: Factory Kit
folder: factory-kit
permalink: /patterns/factory-kit/
categories: Creational
tags:
- Extensibility
---
## 含义
使用分离的构建器和工厂接口来定义一个不可变内容的工厂。
## 类图
![alt text](../../factory-kit/etc/factory-kit.png "Factory Kit")
## 适用场景
工厂套件模式适用于与以下场景:
* 一个类无法预知它需要创建的对象的类别
- 你只是想要一个新的自定义构建器(builder)的实例,而非全局的构建器
- 你明确地想要定义对象的类型,而且工厂可以创建这些对象
- 你想要分离构建器(builder)和创建器(creator)接口
## 引用
* [Design Pattern Reloaded by Remi Forax: ](https://www.youtube.com/watch?v=-k2X7guaArU)

141
zh/factory/README.md Normal file
View File

@@ -0,0 +1,141 @@
---
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.
```
## 类图
![alt text](../../factory/etc/factory.urm.png "Factory pattern class diagram")
## 适用场景
在你只关心对象的创建,但不关心如何创建、管理它的时候,请使用简单工厂模式。
**优点**
* 可以把对象创建代码集中在一个地方,避免在代码库存散布 "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/)

30
zh/sharding/README.md Normal file
View File

@@ -0,0 +1,30 @@
---
layout: pattern
title: Sharding
folder: sharding
permalink: /patterns/sharding/
categories: Behavioral
tags:
- Performance
- Cloud distributed
---
## 含义
分片模式是指将数据存储划分为水平分区或分片。每个分片都有相同的模式,但持有自己独特的数据子集。
一个分片本身就是一个数据存储(它可以包含许多不同类型的实体的数据),运行在作为存储节点的服务器上。
## 类图
![alt text](../../sharding/etc/sharding.urm.png "Sharding pattern class diagram")
## 适用场景
这种设计模式提供了一下的好处:
- 你可以通过增加在额外的存储节点上,运行的更多分片来实现系统扩容。
- 系统可以使用现成的廉价硬件,而不是为每个存储节点使用专门(或者昂贵)的服务器硬件。
- 你可以通过平衡各分片之间的工作负载来减少竞争,以提高性能。
- 在云环境中,分片可以在物理上靠近访问该节点数据的用户。
## 引用
* [Sharding pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/sharding)