软件设计一点通 | 抽象文档模式详解

这里写图片描述

抽象文档模式


抽象文档模式是什么

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


定义

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


使用场景

  • 需要动态添加新属性
  • 你想要一种灵活的方式来组织树状结构中的域
  • 你想要构建更松散耦合的系统

结构

界面“文档”表示可以使用“put”方法编辑属性,使用“get”方法读取,可以使用“children”方法遍历子文档。“children”方法需要对一个方法的功能性引用,该方法可以在给定子项应具有的数据的映射集合的情况下生成子类型的视图。映射集合应该是指向原始映射集合的指针,以便视图中的更改也会影响原始文档。

实现可以从描述不同属性的多个特征接口继承。多个实现甚至可以共享相同的映射集合,模式对实现设计的唯一限制是它必须是无状态的,除了从“BaseDocument”继承的属性。

来自WIKI百科

官方伪代码

interface Document
   put(key : String, value : Object) : Object
   get(key : String) : Object
   children(key : String, constructor : Map<String, Object> -> T) : T[]

 abstract class BaseDocument : Document
   properties : Map<String, Object>

   constructor(properties : Map<String, Object>)
       this->properties := properties

   implement put(key : String, value : Object) : Object
       return this->properties->put(key, value)

   implement get(key : String) : Object
       return this->properties->get(key)

   implement children(key : String, constructor : Map<String, Object> -> T) : T[]
       var result := new T[]

       var children := this->properties->get(key) castTo Map<String, Object>[]
       foreach ( child in children )
           result[] := constructor->apply(child)

       return result

用法

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


官方示例

Document.java

public interface Document {
    Object put(String key, Object value);
    Object get(String key);
    <T> Stream<T> children(
            String key,
            Function<Map<String, Object>, T> constructor
    );
}

BaseDocument.java

public abstract class BaseDocument implements Document {
    private final Map<String, Object> entries;
    protected BaseDocument(Map<String, Object> entries) {
        this.entries = requireNonNull(entries);
    }
    @Override
    public final Object put(String key, Object value) {
        return entries.put(key, value);
    }
    @Override
    public final Object get(String key) {
        return entries.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);
    }
}

Usage.java

Map<String, Object> source = ...;
Car car = new Car(source);
String model = car.getModel();
int price = car.getPrice();
List<Wheel> wheels = car.children("wheel", Wheel::new)
    .collect(Collections.toList());

我的实践

UML图

这里写图片描述

实现思路

对商品的属性特征拆分成价格、重量、品牌、类型几个特征接口,不同类型的商品可以根据需要装配对应的属性特征,本文用的是数码产品,只需要实现特征接口,就可以拥有操作该属性的能力,而且多个不同类型的商品都可以共用一个映射集合,使用key区分开,后期可以动态装配自己的商品属性,可以不断动态添加同一类型的商品(Mp3、耳机、耳塞、音箱等)
示例结构:

  • 数码产品
    • 商品分类
      • 电视
      • 平板
      • 手机
      • xxx

代码实现

Category

/**
 * 商品类别
 *
 * @author liangyh
 * @date 2018/8/9
 */
public class Category extends AbstractGoods implements HasType, HasBrand, HasPrice,HasWeight {

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

Digital

/**
 * 数码产品
 *
 * @author liangyh
 * @date 2018/8/9
 */
public class Digital extends AbstractGoods implements HasType, HasBrand, HasPrice,HasWeight {

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

HasBrand

/**
 * 包含品牌
 *
 * @author liangyh
 * @date 2018/8/9
 */
public interface HasBrand extends Goods {
    String BrandProperties = "brand";

    default Optional<String> getBrand() {
        return Optional.ofNullable((String) getProperties(BrandProperties));
    }
}

HasCategory

/**
 * 包含类别
 *
 * @author liangyh
 * @date 2018/8/9
 */
public interface HasCategory extends Goods {

    String CategoryProperties = "category";

    default Stream getCategory(){
    return children(CategoryProperties,Category::new);
    }

}

HasPrice

/**
 * 包含价格
 *
 * @author liangyh
 * @date 2018/8/9
 */
public interface HasPrice extends Goods {
    String PriceProperties = "price";
    default Optional<Number> getPrice(){
        return Optional.ofNullable((Number)getProperties(PriceProperties));
    }
}

HasType

/**
 * 类型
 *
 * @author liangyh
 * @date 2018/8/9
 */
public interface HasType extends Goods {

    String TypeProperty = "type";

    default Optional<String> getType(){
        return Optional.ofNullable((String)getProperties(TypeProperty));
    }
}

HasWeight

/**
 * 包含重量
 *
 * @author liangyh
 * @date 2018/8/9
 */
public interface HasWeight extends Goods {
    String WeightProperties = "weight";

    default Optional<Number> getWeight() {
        return Optional.ofNullable((Number) getProperties(WeightProperties));
    }
}

AbstractGoods

/**
 * 抽象商品类
 *
 * @author liangyh
 * @date 2018/8/9
 */
public class AbstractGoods implements Goods {

    private final Map<String, Object> properties;

    protected AbstractGoods(Map<String, Object> properties) {
        Objects.requireNonNull(properties, "属性不能为空");
        this.properties = properties;
    }


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

    @Override
    public void putProperties(String key, Object value) {
        this.properties.put(key, value);
    }

    @Override
    public <T> Stream<T> children(String key, Function<Map<String, Object>, T> constructor) {
        Optional<List<Map<String, Object>>> any = Stream.of(getProperties(key)).filter(el -> el != null).map(el -> (List<Map<String, Object>>) el).findAny();
        return any.isPresent() ? any.get().stream().map(constructor) : Stream.empty();
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this.properties);
    }
}

Goods

/**
 * 商品接口
 *
 * @author liangyh
 * @date 2018/8/9
 */
public interface Goods {


    Object getProperties(String key);

    void putProperties(String key,Object value);

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

APP

/**
 * 抽象文档示例
 *
 */
public class App
{

    public static void main( String[] args )
    {
        //创建电视属性
        Map<String,Object> televisionProperties = new HashMap<>();
        televisionProperties.put(HasType.TypeProperty,"电视");
        televisionProperties.put(HasPrice.PriceProperties,2000);
        televisionProperties.put(HasBrand.BrandProperties,"创维");
        televisionProperties.put(HasWeight.WeightProperties,50);

        //创建手机属性
        Map<String,Object> phoneProperties = new HashMap<>();
        phoneProperties.put(HasType.TypeProperty,"手机");
        phoneProperties.put(HasPrice.PriceProperties,1900);
        phoneProperties.put(HasBrand.BrandProperties,"小米");
        phoneProperties.put(HasWeight.WeightProperties,0.5);

        //创建平板属性
        Map<String,Object> padProperties = new HashMap<>();
        padProperties.put(HasType.TypeProperty,"平板");
        padProperties.put(HasPrice.PriceProperties,5000);
        padProperties.put(HasBrand.BrandProperties,"苹果");
        padProperties.put(HasWeight.WeightProperties,0.5);

        //创建数码产品属性
        Map<String,Object> digitalProperties = new HashMap<>();
        digitalProperties.put(HasCategory.CategoryProperties,Arrays.asList(televisionProperties,phoneProperties,padProperties));
        Digital digital = new Digital(digitalProperties);

        System.out.println(digital.toString());
    }
}

运行结果

{
    "category": [{
        "price": 2000,
        "weight": 50,
        "type": "电视",
        "brand": "创维"
    }, {
        "price": 1900,
        "weight": 0.5,
        "type": "手机",
        "brand": "小米"
    }, {
        "price": 5000,
        "weight": 0.5,
        "type": "平板",
        "brand": "苹果"
    }]
}

参考引用

  • 维基百科

猜你喜欢

转载自blog.csdn.net/Evan_Leung/article/details/81546369