document
package com.iluwatar.abstractdocument;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
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);
}
abstract-document
package com.iluwatar.abstractdocument;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class AbstractDocument implements Document {
//a properties map,if a property doesn't contain sub-property,
//then it directly return a Object which stand for the property it self
//and if a property does contain sub-property,then the Object
//refer to it is a list of map,and each map is property's name - property's object map
private final Map<String, Object> properties;
protected AbstractDocument(Map<String, Object> properties) {
Objects.requireNonNull(properties, "properties map is required");
this.properties = properties;
}
//put a property into properties
@Override
public Void put(String key, Object value) {
properties.put(key, value);
return null;
}
//directly return the non-sub-property's object itself
@Override
public Object get(String key) {
return properties.get(key);
}
//return a list of map of has-sub-property property,each map contain key-object pair
//you can operate the result Stream<T> like this:
//result.forEach((r)->{//doSomething});
//since forEach method look for every sub-property object and invoke the lambda expression
@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);
}
@Override
public String toString() {
var builder = new StringBuilder();
builder.append(getClass().getName()).append("[");
properties.forEach((key, value) -> builder.append("[").append(key).append(" : ").append(value)
.append("]"));
builder.append("]");
return builder.toString();
}
}
hasParts
public interface HasParts extends Document {
default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new);
}
}
Parts
public class Part extends AbstractDocument implements HasType, HasModel, HasPrice {
public Part(Map<String, Object> properties) {
super(properties);
}
}
Car
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
public Car(Map<String, Object> properties) {
super(properties);
}
}
App
/*
* 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.
*/
package com.iluwatar.abstractdocument;
import com.iluwatar.abstractdocument.domain.Car;
import com.iluwatar.abstractdocument.domain.enums.Property;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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.
*
* <p>In Abstract Document pattern,({@link AbstractDocument}) fully implements {@link Document})
* interface. Traits are then defined to enable access to properties in usual, static way.
*/
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
/**
* Executes the App.
*/
public App() {
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))
);
}
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
new App();
}
}
对于每个部分,HasModel,HasPrice,HasParts…这些接口都实现了,getXXX()形式的函数
而且由于这些接口又都继承子Document,意味着这些接口拥有判断这个该属性是否拥有子属性的能力,
因为如果拥有子属性,那么调用getXXX的时候就直接调用Document.children来返回一包含一串Object的Stream,可以通过Stream.forEach来遍历和操作每个子属性的Object
有几个巧妙的地方值得提一下
1.Part和Car既是平级,又是父子关系
2.hasParts的getParts要传递给children一个构造函数,让Parts的子属性构造成一个AbstractDocument