Skip to content

Commit

Permalink
Merge branch 'master' of github.com:iluwatar/java-design-patterns int…
Browse files Browse the repository at this point in the history
…o java-11
  • Loading branch information
anuragagarwal561994 committed Aug 3, 2020
2 parents daf5322 + 9ff5b9e commit 1098852
Show file tree
Hide file tree
Showing 364 changed files with 8,558 additions and 2,289 deletions.
1,086 changes: 1,086 additions & 0 deletions .all-contributorsrc

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#
# The MIT License
# Copyright © 2014-2019 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.
#

# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Java CI with Maven

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-18.04

steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
# Some tests need screen access
- name: Install xvfb
run: sudo apt-get install xvfb
# SonarQube scan does not work for forked repositories
# See https://jira.sonarsource.com/browse/MMF-1371
- name: Build with Maven
if: github.ref != 'refs/heads/master'
run: xvfb-run mvn clean verify
- name: Build with Maven and run SonarQube analysis
if: github.ref == 'refs/heads/master'
run: xvfb-run mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
env:
# These two env variables are needed for sonar analysis
GITHUB_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
36 changes: 0 additions & 36 deletions .travis.yml

This file was deleted.

206 changes: 200 additions & 6 deletions README.md

Large diffs are not rendered by default.

173 changes: 167 additions & 6 deletions abstract-document/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,182 @@ tags:
---

## Intent
Achieve flexibility of untyped languages and keep the type-safety

Use dynamic properties and achieve flexibility of untyped languages while keeping type-safety.

## Explanation

The Abstract Document pattern enables handling additional, non-static properties. This pattern
uses concept of traits to enable type safety and separate properties of different classes into
set of interfaces.

Real world example

> 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.
In plain words

> Abstract Document pattern allows attaching properties to objects without them knowing about it.
Wikipedia says

> An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing
the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components
in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the
support of type-safety. The pattern makes use of traits to separate different properties of a class into different
interfaces.

**Programmatic Example**

Let's first define the base classes `Document` and `AbstractDocument`. They basically make the object hold a property
map and any amount of child objects.

```java
public interface Document {

Void put(String key, Object value);

Object get(String key);

<T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor);
}

public abstract class AbstractDocument implements Document {

private final Map<String, Object> properties;

protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}

@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}

@Override
public Object get(String key) {
return properties.get(key);
}

@Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
return Stream.ofNullable(get(key))
.filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el)
.findAny()
.stream()
.flatMap(Collection::stream)
.map(constructor);
}
...
}
```
Next we define an enum `Property` and a set of interfaces for type, price, model and parts. This allows us to create
static looking interface to our `Car` class.

```java
public enum Property {

PARTS, TYPE, PRICE, MODEL
}

public interface HasType extends Document {

default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
}

public interface HasPrice extends Document {

default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
}
public interface HasModel extends Document {

default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString()));
}
}

public interface HasParts extends Document {

default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new);
}
}
```

Now we are ready to introduce the `Car`.

```java
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {

public Car(Map<String, Object> properties) {
super(properties);
}
}
```

And finally here's how we construct and use the `Car` in a full example.
```java
LOGGER.info("Constructing parts and car");
var wheelProperties = Map.of(
Property.TYPE.toString(), "wheel",
Property.MODEL.toString(), "15C",
Property.PRICE.toString(), 100L);
var doorProperties = Map.of(
Property.TYPE.toString(), "door",
Property.MODEL.toString(), "Lambo",
Property.PRICE.toString(), 300L);
var carProperties = Map.of(
Property.MODEL.toString(), "300SL",
Property.PRICE.toString(), 10000L,
Property.PARTS.toString(), List.of(wheelProperties, doorProperties));
var car = new Car(carProperties);
LOGGER.info("Here is our car:");
LOGGER.info("-> model: {}", car.getModel().orElseThrow());
LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
LOGGER.info("-> parts: ");
car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}",
p.getType().orElse(null),
p.getModel().orElse(null),
p.getPrice().orElse(null))
);
// Constructing parts and car
// Here is our car:
// model: 300SL
// price: 10000
// parts:
// wheel/15C/100
// door/Lambo/300
```
## Class diagram
![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain")
![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain")
## Applicability
Use the Abstract Document Pattern when
* there is a need to add new properties on the fly
* you want a flexible way to organize domain in tree like structure
* you want more loosely coupled system
Use the Abstract Document Pattern when
* There is a need to add new properties on the fly
* You want a flexible way to organize domain in tree like structure
* You want more loosely coupled system
## Credits
* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern)
* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf)
* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492)
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

/**
* Executes the App.
* Program entry point.
*
* @param args command line args
*/
public App() {
public static void main(String[] args) {
LOGGER.info("Constructing parts and car");

var wheelProperties = Map.of(
Expand Down Expand Up @@ -75,14 +77,4 @@ public App() {
p.getPrice().orElse(null))
);
}

/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
new App();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
*/
public interface HasParts extends Document {


default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
*/
public interface HasPrice extends Document {


default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
*/
public interface HasType extends Document {


default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ public class AbstractDocumentTest {
private static final String KEY = "key";
private static final String VALUE = "value";

private class DocumentImplementation extends AbstractDocument {
private static class DocumentImplementation extends AbstractDocument {

DocumentImplementation(Map<String, Object> properties) {
super(properties);
}
}

private DocumentImplementation document = new DocumentImplementation(new HashMap<>());
private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());

@Test
public void shouldPutAndGetValue() {
Expand Down
Loading

0 comments on commit 1098852

Please sign in to comment.