Abstract Document Pattern

The Abstract Document Pattern is a powerful and versatile design pattern that allows developers to easily organize and manipulate complex data structures within their applications. By abstracting away the details of individual document formats and providing a unified interface for interacting with them, this pattern enables developers to seamlessly handle a wide range of different data types, including text documents, images, videos, and more.

With its intuitive and flexible API, the Abstract Document Pattern empowers developers to quickly and easily build robust, scalable, and maintainable applications that can handle even the most demanding data processing tasks. Whether you're building a content management system, a data analytics platform, or anything in between, this pattern is an essential tool for any developer looking to take their application to the next level. So why wait? Start exploring the power of the Abstract Document Pattern today and unleash your application's full potential!




# Intent



Looking for a programming language that combines the flexibility of untyped languages with the safety of typed ones? With dynamic properties, you can enjoy both! Get the best of both worlds and make your code more flexible and type-safe than ever before.



Explanation



If you're looking for a way to handle additional, non-static properties in your code, the Abstract Document pattern is a great option. By leveraging the concept of traits, this pattern allows for type safety while separating the properties of different classes into a set of interfaces. With the Abstract Document pattern, you can easily manage and manipulate complex sets of data, without sacrificing type safety or performance.


Real-world Example:

Imagine you have a car made up of multiple parts, but you're not sure if all the parts are present or only some of them. The beauty of the Abstract Document pattern is that it allows for dynamic and flexible cars, making it easy to manage and manipulate data sets.


In plain words

The Abstract Document pattern is a way to attach properties to objects without the objects being aware of it. This makes it possible to easily work with complex data structures in an efficient and flexible manner.


Wikipedia Definition:

The Abstract Document pattern is a structural design pattern used in object-oriented programming. It involves organizing objects in loosely typed key-value stores and presenting the data using typed views. The objective of this pattern is to provide flexibility between components in a strongly typed language by allowing new properties to be added to the object-tree on the fly, while still maintaining type-safety. Traits are used to separate different properties of a class into distinct interfaces.



Programmatic Example



To begin, we'll create two fundamental classes: Document and AbstractDocument. These classes allow an object to store a collection of properties as well as any number of child objects.

    
public interface Document {

    Void put(String key, Object value);
    
    Object get(String key);
    
     Stream children(String key, Function, T> constructor);
    }
    
    public abstract class AbstractDocument implements Document {
    
    private final Map properties;
    
    protected AbstractDocument(Map properties) {
        Objects.requireNonNull(properties, "properties map is required");
        this.properties = properties;
    }
    
    @Override
    public Void put(String key, Object value) {
        properties.put(key, value);
        return null;
    }
    
    @Override
    public Object get(String key) {
        return properties.get(key);
    }
    
    @Override
    public  Stream children(String key, Function, T> constructor) {
        return Stream.ofNullable(get(key))
            .filter(Objects::nonNull)
            .map(el -> (List>) el)
            .findAny()
            .stream()
            .flatMap(Collection::stream)
            .map(constructor);
    }
    ...
}
    

Next we establish an enum called "Property," and a collection of interfaces pertaining to type, price, model, and parts. These components enable us to fashion a statically-structured interface for our Car class.


public enum Property {

    PARTS, TYPE, PRICE, MODEL
    }
    
    public interface HasType extends Document {
    
    default Optional getType() {
        return Optional.ofNullable((String) get(Property.TYPE.toString()));
    }
    }
    
    public interface HasPrice extends Document {
    
    default Optional getPrice() {
        return Optional.ofNullable((Number) get(Property.PRICE.toString()));
    }
    }
    public interface HasModel extends Document {
    
    default Optional getModel() {
        return Optional.ofNullable((String) get(Property.MODEL.toString()));
    }
    }
    
    public interface HasParts extends Document {
    
    default Stream getParts() {
        return children(Property.PARTS.toString(), Part::new);
    }
}

Now we are ready to introduce the Car.


public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {

    public Car(Map properties) {
        super(properties);
    }
}  

Lastly, presented below is a complete example illustrating the construction and utilization of the Car.


    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))
    );

    // Constructing parts and car
    // Here is our car:
    // model: 300SL
    // price: 10000
    // parts: 
    // wheel/15C/100
    // door/Lambo/300

Class diagram



abstract document class diagram

Applicability



The Abstract Document Pattern is recommended for use when:

  • You have a requirement to dynamically add new properties
  • Desire a versatile method for organizing domain in a tree-like structure
  • Aim to create a loosely coupled system






Copyrights and trademarks and other promotional materials are held by their respective owners and their use is allowed under the fair use clause of the Copyright Law. © 2023. BestForStudy.com