Skip to content

Commit 1098852

Browse files
Merge branch 'master' of github.com:iluwatar/java-design-patterns into java-11
2 parents daf5322 + 9ff5b9e commit 1098852

File tree

364 files changed

+8558
-2289
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

364 files changed

+8558
-2289
lines changed

.all-contributorsrc

+1,086
Large diffs are not rendered by default.

.github/workflows/maven.yml

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#
2+
# The MIT License
3+
# Copyright © 2014-2019 Ilkka Seppälä
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
#
23+
24+
# This workflow will build a Java project with Maven
25+
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
26+
27+
name: Java CI with Maven
28+
29+
on:
30+
push:
31+
branches: [ master ]
32+
pull_request:
33+
branches: [ master ]
34+
35+
jobs:
36+
build:
37+
38+
runs-on: ubuntu-18.04
39+
40+
steps:
41+
- uses: actions/checkout@v2
42+
- name: Set up JDK 11
43+
uses: actions/setup-java@v1
44+
with:
45+
java-version: 11
46+
# Some tests need screen access
47+
- name: Install xvfb
48+
run: sudo apt-get install xvfb
49+
# SonarQube scan does not work for forked repositories
50+
# See https://jira.sonarsource.com/browse/MMF-1371
51+
- name: Build with Maven
52+
if: github.ref != 'refs/heads/master'
53+
run: xvfb-run mvn clean verify
54+
- name: Build with Maven and run SonarQube analysis
55+
if: github.ref == 'refs/heads/master'
56+
run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
57+
env:
58+
# These two env variables are needed for sonar analysis
59+
GITHUB_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }}
60+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

.travis.yml

-36
This file was deleted.

README.md

+200-6
Large diffs are not rendered by default.

abstract-document/README.md

+167-6
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,182 @@ tags:
99
---
1010

1111
## Intent
12-
Achieve flexibility of untyped languages and keep the type-safety
12+
13+
Use dynamic properties and achieve flexibility of untyped languages while keeping type-safety.
14+
15+
## Explanation
16+
17+
The Abstract Document pattern enables handling additional, non-static properties. This pattern
18+
uses concept of traits to enable type safety and separate properties of different classes into
19+
set of interfaces.
20+
21+
Real world example
22+
23+
> Consider a car that consists of multiple parts. However we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible.
24+
25+
In plain words
26+
27+
> Abstract Document pattern allows attaching properties to objects without them knowing about it.
28+
29+
Wikipedia says
30+
31+
> An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing
32+
the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components
33+
in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the
34+
support of type-safety. The pattern makes use of traits to separate different properties of a class into different
35+
interfaces.
36+
37+
**Programmatic Example**
38+
39+
Let's first define the base classes `Document` and `AbstractDocument`. They basically make the object hold a property
40+
map and any amount of child objects.
41+
42+
```java
43+
public interface Document {
44+
45+
Void put(String key, Object value);
46+
47+
Object get(String key);
48+
49+
<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
50+
}
51+
52+
public abstract class AbstractDocument implements Document {
53+
54+
private final Map<String, Object> properties;
55+
56+
protected AbstractDocument(Map<String, Object> properties) {
57+
Objects.requireNonNull(properties, "properties map is required");
58+
this.properties = properties;
59+
}
60+
61+
@Override
62+
public Void put(String key, Object value) {
63+
properties.put(key, value);
64+
return null;
65+
}
66+
67+
@Override
68+
public Object get(String key) {
69+
return properties.get(key);
70+
}
71+
72+
@Override
73+
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
74+
return Stream.ofNullable(get(key))
75+
.filter(Objects::nonNull)
76+
.map(el -> (List<Map<String, Object>>) el)
77+
.findAny()
78+
.stream()
79+
.flatMap(Collection::stream)
80+
.map(constructor);
81+
}
82+
...
83+
}
84+
```
85+
Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create
86+
static looking interface to our `Car` class.
87+
88+
```java
89+
public enum Property {
90+
91+
PARTS, TYPE, PRICE, MODEL
92+
}
93+
94+
public interface HasType extends Document {
95+
96+
default Optional<String> getType() {
97+
return Optional.ofNullable((String) get(Property.TYPE.toString()));
98+
}
99+
}
100+
101+
public interface HasPrice extends Document {
102+
103+
default Optional<Number> getPrice() {
104+
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
105+
}
106+
}
107+
public interface HasModel extends Document {
108+
109+
default Optional<String> getModel() {
110+
return Optional.ofNullable((String) get(Property.MODEL.toString()));
111+
}
112+
}
113+
114+
public interface HasParts extends Document {
115+
116+
default Stream<Part> getParts() {
117+
return children(Property.PARTS.toString(), Part::new);
118+
}
119+
}
120+
```
121+
122+
Now we are ready to introduce the `Car`.
123+
124+
```java
125+
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
126+
127+
public Car(Map<String, Object> properties) {
128+
super(properties);
129+
}
130+
}
131+
```
132+
133+
And finally here's how we construct and use the `Car` in a full example.
134+
135+
```java
136+
LOGGER.info("Constructing parts and car");
137+
138+
var wheelProperties = Map.of(
139+
Property.TYPE.toString(), "wheel",
140+
Property.MODEL.toString(), "15C",
141+
Property.PRICE.toString(), 100L);
142+
143+
var doorProperties = Map.of(
144+
Property.TYPE.toString(), "door",
145+
Property.MODEL.toString(), "Lambo",
146+
Property.PRICE.toString(), 300L);
147+
148+
var carProperties = Map.of(
149+
Property.MODEL.toString(), "300SL",
150+
Property.PRICE.toString(), 10000L,
151+
Property.PARTS.toString(), List.of(wheelProperties, doorProperties));
152+
153+
var car = new Car(carProperties);
154+
155+
LOGGER.info("Here is our car:");
156+
LOGGER.info("-> model: {}", car.getModel().orElseThrow());
157+
LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
158+
LOGGER.info("-> parts: ");
159+
car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}",
160+
p.getType().orElse(null),
161+
p.getModel().orElse(null),
162+
p.getPrice().orElse(null))
163+
);
164+
165+
// Constructing parts and car
166+
// Here is our car:
167+
// model: 300SL
168+
// price: 10000
169+
// parts:
170+
// wheel/15C/100
171+
// door/Lambo/300
172+
```
13173
14174
## Class diagram
15-
![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain")
16175
176+
![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain")
17177
18178
## Applicability
19-
Use the Abstract Document Pattern when
20179
21-
* there is a need to add new properties on the fly
22-
* you want a flexible way to organize domain in tree like structure
23-
* you want more loosely coupled system
180+
Use the Abstract Document Pattern when
24181
182+
* There is a need to add new properties on the fly
183+
* You want a flexible way to organize domain in tree like structure
184+
* You want more loosely coupled system
25185
26186
## Credits
27187
28188
* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern)
29189
* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf)
190+
* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492)

abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java

+4-12
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ public class App {
4343
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
4444

4545
/**
46-
* Executes the App.
46+
* Program entry point.
47+
*
48+
* @param args command line args
4749
*/
48-
public App() {
50+
public static void main(String[] args) {
4951
LOGGER.info("Constructing parts and car");
5052

5153
var wheelProperties = Map.of(
@@ -75,14 +77,4 @@ public App() {
7577
p.getPrice().orElse(null))
7678
);
7779
}
78-
79-
/**
80-
* Program entry point.
81-
*
82-
* @param args command line args
83-
*/
84-
public static void main(String[] args) {
85-
new App();
86-
}
87-
8880
}

abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java

-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
*/
3333
public interface HasParts extends Document {
3434

35-
3635
default Stream<Part> getParts() {
3736
return children(Property.PARTS.toString(), Part::new);
3837
}

abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java

-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
*/
3333
public interface HasPrice extends Document {
3434

35-
3635
default Optional<Number> getPrice() {
3736
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
3837
}

abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java

-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
*/
3333
public interface HasType extends Document {
3434

35-
3635
default Optional<String> getType() {
3736
return Optional.ofNullable((String) get(Property.TYPE.toString()));
3837
}

abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ public class AbstractDocumentTest {
4040
private static final String KEY = "key";
4141
private static final String VALUE = "value";
4242

43-
private class DocumentImplementation extends AbstractDocument {
43+
private static class DocumentImplementation extends AbstractDocument {
4444

4545
DocumentImplementation(Map<String, Object> properties) {
4646
super(properties);
4747
}
4848
}
4949

50-
private DocumentImplementation document = new DocumentImplementation(new HashMap<>());
50+
private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());
5151

5252
@Test
5353
public void shouldPutAndGetValue() {

0 commit comments

Comments
 (0)