Abstract Document Pattern

Last updated

An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the support of type-safety. The pattern makes use of traits to separate different properties of a class into different interfaces. [1] The term "document" is inspired from document-oriented databases.

Contents

Definition

A document is an object that contains a number of properties. A property can for an example be a value like a number or a string, or it can be a list of other documents. Every property is referenced using a key. [2] When traversing the document tree, the user specifies a constructor to be used for creating the implementation class of the next level. The implementations are often a union of various traits that extend the Document interface, making it possible for them to handle setting and getting properties on their own.

Structure

Abstract Document Pattern Example Abstract-document-pattern.svg
Abstract Document Pattern Example

The interface "Document" states that properties can be edited using the "put" method, read using the "get" method and sub-documents can be traversed using the "children" method. The "children" method requires a functional reference to a method that can produce a typed view of a child given a map of the data the child should have. The map should be a pointer to the original map so that changes in the view also affect the original document.

Implementations can inherit from multiple trait interfaces that describe different properties. Multiple implementations can even share the same map, the only restriction the pattern puts on the design of the implementation is that it must be stateless except for the properties inherited from "BaseDocument".

Pseudocode

interface Document     put(key : String, value : Object) : Object     get(key : String) : Object     children(key : String, constructor : Map<String, Object> -> T) : T[]  abstractclass 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

Usage

The Abstract Document Pattern allows the developer to store variables like configuration settings in an untyped tree structure and operate on the documents using typed views. New views or alternative implementations of views can be created without affecting the internal document structure. The advantage of this is a more loosely coupled system, but it also increases the risk of casting errors as the inherit type of a property is not always certain.

Example implementation

The full implementation of the Abstract Document pattern is available at https://java-design-patterns.com/patterns/abstract-document/. Here are the key classes shortly.

Document.java

publicinterfaceDocument{Objectput(Stringkey,Objectvalue);Objectget(Stringkey);<T>Stream<T>children(Stringkey,Function<Map<String,Object>,T>constructor););}

BaseDocument.java

publicabstractclassAbstractDocumentimplementsDocument{privatefinalMap<String,Object>properties;protectedAbstractDocument(Map<String,Object>properties){Objects.requireNonNull(properties,"properties map is required");this.properties=properties;}@OverridepublicVoidput(Stringkey,Objectvalue){properties.put(key,value);returnnull;}@OverridepublicObjectget(Stringkey){returnproperties.get(key);}@Overridepublic<T>Stream<T>children(Stringkey,Function<Map<String,Object>,T>constructor){returnStream.ofNullable(get(key)).filter(Objects::nonNull).map(el->(List<Map<String,Object>>)el).findAny().stream().flatMap(Collection::stream).map(constructor);}}

Usage.java

varcarProperties=...;varcar=newCar(carProperties);Stringmodel=car.getModel().orElseThrow());intprice=car.getPrice().orElseThrow());varparts=car.getParts();

Related Research Articles

A visitor pattern is a software design pattern that separates the algorithm from the object structure. Because of this separation, new operations can be added to existing object structures without modifying the structures. It is one way to follow the open/closed principle in object-oriented programming and software engineering.

Java Platform, Standard Edition is a computing platform for development and deployment of portable code for desktop and server environments. Java SE was formerly known as Java 2 Platform, Standard Edition (J2SE).

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. It is a kind of lazy evaluation that refers specifically to the instantiation of objects or other resources.

In object oriented programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify their exact class. Rather than by calling a constructor, this is done by calling a factory method to create an object. Factory methods can either be specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes.

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other instances of the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. Decorator use can be more efficient than subclassing, because an object's behavior can be augmented without defining an entirely new object.

In software design and engineering, the observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

In object-oriented (OO) and functional programming, an immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created. In some cases, an object is considered immutable even if some internally used attributes change, but the object's state appears unchanging from an external point of view. For example, an object that uses memoization to cache the results of expensive computations could still be considered an immutable object.

A method in object-oriented programming (OOP) is a procedure associated with an object, and generally also a message. An object consists of state data and behavior; these compose an interface, which specifies how the object may be used. A method is a behavior of an object parametrized by a user.

This article compares two programming languages: C# with Java. While the focus of this article is mainly the languages and their features, such a comparison will necessarily also consider some features of platforms and libraries. For a more detailed comparison of the platforms, see Comparison of the Java and .NET platforms.

<span class="mw-page-title-main">Java syntax</span> Set of rules defining correctly structured program

The syntax of Java is the set of rules defining how a Java program is written and interpreted.

<span class="mw-page-title-main">Dependency injection</span> Software programming technique

In software engineering, dependency injection is a programming technique in which an object or function receives other objects or functions that it requires, as opposed to creating them internally. Dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs. The pattern ensures that an object or function that wants to use a given service should not have to know how to construct those services. Instead, the receiving 'client' is provided with its dependencies by external code, which it is not aware of. Dependency injection makes implicit dependencies explicit and helps solve the following problems:

<span class="mw-page-title-main">JavaScript syntax</span> Set of rules defining correctly structured programs

The syntax of JavaScript is the set of rules that define a correctly structured JavaScript program.

In the Java computer programming language, an annotation is a form of syntactic metadata that can be added to Java source code. Classes, methods, variables, parameters and Java packages may be annotated. Like Javadoc tags, Java annotations can be read from source files. Unlike Javadoc tags, Java annotations can also be embedded in and read from Java class files generated by the Java compiler. This allows annotations to be retained by the Java virtual machine at run-time and read via reflection. It is possible to create meta-annotations out of the existing ones in Java.

Haxe is a high-level cross-platform programming language and compiler that can produce applications and source code for many different computing platforms from one code-base. It is free and open-source software, released under the MIT License. The compiler, written in OCaml, is released under the GNU General Public License (GPL) version 2.

In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.

In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (null) behavior. The null object design pattern, which describes the uses of such objects and their behavior, was first published as "Void Value" and later in the Pattern Languages of Program Design book series as "Null Object".

This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.

This comparison of programming languages compares how object-oriented programming languages such as C++, Java, Smalltalk, Object Pascal, Perl, Python, and others manipulate data structures.

Kotlin is a cross-platform, statically typed, general-purpose high-level programming language with type inference. Kotlin is designed to interoperate fully with Java, and the JVM version of Kotlin's standard library depends on the Java Class Library, but type inference allows its syntax to be more concise. Kotlin mainly targets the JVM, but also compiles to JavaScript or native code via LLVM. Language development costs are borne by JetBrains, while the Kotlin Foundation protects the Kotlin trademark.

<span class="mw-page-title-main">Strongly typed identifier</span>

A strongly typed identifier is user-defined data type which serves as an identifier or key that is strongly typed. This is a solution to the "primitive obsession" code smell as mentioned by Martin Fowler. The data type should preferably be immutable if possible. It is common for implementations to handle equality testing, serialization and model binding.

References

  1. Forslund, Emil (2016-01-15). "Age of Java: The Best of Both Worlds". Ageofjava.blogspot.com. Archived from the original on 2016-01-18. Retrieved 2016-01-23.
  2. Fowler, Martin. "Dealing with Properties" (PDF). Retrieved 2016-01-29.