# 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
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
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