设计模式-抽象文档

抽象文档(Abstract Document)

wiki

一种面向对象的结构设计模式,用于在松散类型的键值存储中组织对象并使用类型视图公开数据。该模式的目的是在强类型语言中实现组件之间的高度灵活性,其中新的属性可以随时添加到对象树中,而不会失去对类型安全性的支持。该模式利用特征将类型的不同的属性分隔为不同的接口。

定义

文档是包含许多属性的对象。例如,属性可以是数字或字符串的值,也可以是其他文档的列表。每个属性都使用一个键引用。遍历文档树时,用户指定一个构造函数用于创建下一级的实现类。这些实现通常是扩展Document接口的各个特征的联合,使他们可以自己处理设置和获取属性。

结构

接口”Document”指出可以使用”put”方法编辑属性,使用”get”方法读取属性并使用”children”方法遍历子文档。”children”方法需要对一个方法的函数引用,该方法可以给出孩子应该拥有的数据地图的子类型视图。地图应该是指向原始地图的指针,以便视图中的更改也会影响原始文档。
实现可以从描述不同属性的多个特征接口继承。多个实现甚至可以共享相同的映射,模式对实现设计的唯一限制是除了从”AbstractDocument”继承的属性之外,它必须是无状态的

用法

抽象文档模式允许开发人员将配置设置等变量存储在无类型的树结构中,并使用类型视图对文档进行操作。视图的新视图或替代实现可以在不影响内部文档结构的情况下创建。这是一个更松散耦合的系统优点,但它也增加了铸造错误的风险,因为属性的继承类型并不总是确定的。

实例实现

Document.java

1
2
3
4
5
6
7
8
public interface Document {

Object put(String key, Object value);

Object get(String key);

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

AbstractDocument.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public abstract class AbstractDocument implements Document {
private final Map<String, Object> properties;

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

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

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

@Override
public final <T> Stream<T> children(
String key,
Function<Map<String, Object>, T> constructor) {
final List<Map<String, Object>> children =
(List<Map<String, Object>>) get(key);
return children == null
? Stream.empty()
: children.stream().map(constructor);
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getClass().getSimpleName()).append("[");
properties.entrySet().forEach(e -> builder.append("[").append(e.getKey())
.append(" : ").append(e.getValue()).append("]"));
builder.append("]");
return builder.toString();
}
}

HasPrice.java

1
2
3
4
5
6
7
8
9
10
11
/**
* 价格属性
* @author luokai
*/
public interface HasPrice extends Document{
String PROPERTY = "price";

default Optional<BigDecimal> getPrice() {
return Optional.ofNullable((BigDecimal) get(PROPERTY));
}
}

HasParts.java

1
2
3
4
5
6
7
8
9
10
11
/**
* 零件属性
* @author luokai
*/
public interface HasParts extends Document{
String PROPERTY = "parts";

default Stream<Part> getParts(){
return children(PROPERTY, Part::new);
}
}

Part.java

1
2
3
4
5
6
7
8
9
/**
* 零件
* @author luokai
*/
public class Part extends AbstractDocument implements HasPrice {
public Part(Map<String, Object> properties) {
super(properties);
}
}

Car.java

1
2
3
4
5
6
7
8
9
/**
* 汽车
* @author luokai
*/
public class Car extends AbstractDocument implements HasPrice, HasParts{
public Car(Map<String, Object> properties) {
super(properties);
}
}

App.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class App {
public static void main(String[] args) {

Map<String, Object> partMap1 =new HashMap<>();
partMap1.put(HasPrice.PROPERTY, new BigDecimal("20"));

Map<String, Object> partMap2 =new HashMap<>();
partMap2.put(HasPrice.PROPERTY, new BigDecimal("52"));

Map<String, Object> carMap1 =new HashMap<>();
carMap1.put(HasPrice.PROPERTY, new BigDecimal("72"));
carMap1.put(HasParts.PROPERTY, Arrays.asList(partMap1, partMap2));
Car car = new Car(carMap1);
System.out.println(car);
System.out.println(car.getPrice().get());
car.getParts().forEach(e -> {
System.out.println(e.getPrice().get());
});

}
}