抽象文档模式可以处理额外的非静态属性。该模式使用特征的概念来实现类型安全并将不同类的属性分离到一组接口中。
考虑由多个部件组成的汽车。然而,我们不知道具体的汽车是否真的拥有所有零件,或者只是其中的一部分。我们的汽车充满活力且极其灵活。
让我们首先定义基类Document和AbstractDocument. 它们基本上使对象拥有一个属性映射和任意数量的子对象。
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;}@Overridepublic Void put(String key, Object value) {properties.put(key, value);return null;}@Overridepublic Object get(String key) {return properties.get(key);}@Overridepublic <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);}...}
接下来我们定义一个枚举Property和一组类型、价格、模型和零件的接口。这允许我们为我们的Car类创建静态外观界面。
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);}}
现在我们准备介绍Car.
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {public Car(Map<String, Object> properties) {super(properties);}}
最后,这是我们Car在完整示例中构建和使用 的方式。
public static void main(String[] args) {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)));}
