While creational patterns play the part of flexibly creating objects, structural patterns, on the other hand, are patterns about composing objects. In this chapter, we are going to talk about structural patterns that fit different scenarios.
If we take a closer look at structural patterns, they can be divided into structural class patterns and structural object patterns. Structural class patterns are patterns that play with "interested parties" themselves, while structural object patterns are patterns that weave pieces together (like Composite Pattern). These two kinds of structural patterns complement each other to some degree.
Here are the patterns we'll walk through in this chapter:
Objects under the same class could vary from their properties or even specific subclasses, but a complex object can have more than just normal properties. Taking DOM elements, for example, all the elements are instances of class Node
. These nodes form tree structures to represent different pages, but every node in these trees is complete and uniform compared to the node at the root:
<html> <head> <title>TypeScript</title> </head> <body> <h1>TypeScript</h1> <img /> </body> </html>
The preceding HTML represents a DOM structure like this:
All of the preceding objects are instances of Node
, they implement the interface of a component in Composite Pattern. Some of these nodes like HTML elements (except for HTMLImageElement
) in this example have child nodes (components) while others don't.
The participants of Composite Pattern implementation include:
Node
Defines the interface and implement the default behavior for objects of the composite. It should also include an interface to access and manage the child components of an instance, and optionally a reference to its parent.
HTMLHeadElement
and HTMLBodyElement
Stores child components and implements related operations, and of course its own behaviors.
TextNode
, HTMLImageElement
Defines behaviors of a primitive component.
Manipulates the composite and its components.
Composite Pattern applies when objects can and should be abstracted recursively as components that form tree structures. Usually, it would be a natural choice when a certain structure needs to be formed as a tree, such as trees of view components, abstract syntax trees, or trees that represent file structures.
We are going to create a composite that represents simple file structures and has limited kinds of components.
First of all, let's import related node modules:
import * as Path from 'path'; import * as FS from 'fs';
Module path
and fs
are built-in modules of Node.js, please refer to Node.js documentation for more information: https://nodejs.org/api/.
Now we need to make abstraction of the components, say FileSystemObject
:
abstract class FileSystemObject { constructor( public path: string, public parent?: FileSystemObject ) { } get basename(): string { return Path.basename(this.path); } }
We are using abstract class
because we are not expecting to use FileSystemObject
directly. An optional parent
property is defined to allow us to visit the upper component of a specific object. And the basename
property is added as a helper for getting the basename of the path.
The FileSystemObject
is expected to have subclasses, FolderObject
and FileObject
. For FolderObject
, which is a composite that may contain other folders and files, we are going to add an items
property (getter) that returns other FileSystemObject
it contains:
class FolderObject extends FileSystemObject { items: FileSystemObject[]; constructor(path: string, parent?: FileSystemObject) { super(path, parent); } }
We can initialize the items
property in the constructor
with actual files and folders existing at given path
:
this.items = FS .readdirSync(this.path) .map(path => { let stats = FS.statSync(path); if (stats.isFile()) { return new FileObject(path, this); } else if (stats.isDirectory()) { return new FolderObject(path, this); } else { throw new Error('Not supported'); } });
You may have noticed we are forming items
with different kinds of objects, and we are also passing this
as the parent
of newly created child components.
And for FileObject
, we'll add a simple readAll
method that reads all bytes of the file:
class FileObject extends FileSystemObject { readAll(): Buffer { return FS.readFileSync(this.path); } }
Currently, we are reading the child items inside a folder from the actual filesystem when a folder object gets initiated. This might not be necessary if we want to access this structure on demand. We may actually create a getter that calls readdir
only when it's accessed, thus the object would act like a proxy to the real filesystem.
Both the primitive object and composite object in Composite Pattern share the component interface, which makes it easy for developers to build a composite structure with fewer things to remember.
It also enables the possibility of using markup languages like XML and HTML to represent a really complex object with extreme flexibility. Composite Pattern can also make the rendering easier by having components rendered recursively.
As most components are compatible with having child components or being child components of their parents themselves, we can easily create new components that work great with existing ones.
18.117.186.46