1. 程式人生 > >軟體設計一點通 | 抽象文件模式詳解

軟體設計一點通 | 抽象文件模式詳解

這裡寫圖片描述

抽象文件模式

抽象文件模式是什麼

一種面向物件的結構設計模式,用於在鬆散型別的鍵值儲存中組織物件並使用型別化檢視公開資料。該模式的目的是在強型別語言中實現元件之間的高度靈活性,其中可以動態地將新屬性新增到物件樹,而不會失去對型別安全的支援。該模式利用特徵將類的不同屬性分成不同的介面

定義

文件是包含許多屬性的物件。例如:屬性可以是數字或字串之類的值,也可以是其他文件的列表。使用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": "蘋果"
    }]
}

參考引用

  • 維基百科