1. Overview
Structural design patterns are those that simplify the design of large object structures by identifying relationships between them. They describe common ways of composing classes and objects so that they become repeatable as solutions.
The Gang of Four has described seven such structural ways or patterns. In this quick tutorial, we'll see examples of how some core libraries in Java have adopted each one of them.
2. Adapter
An adapter, as the name suggests, acts as an intermediary to convert an otherwise incompatible interface to one that a client expects.
This is useful in cases where we want to take an existing class whose source code cannot be modified and make it work with another class.
JDK's collection framework offers many examples of the adapter pattern:
List<String> musketeers = Arrays.asList("Athos", "Aramis", "Porthos");
Here, Arrays#asList is helping us adapt an Array to a List.
The I/O framework also makes extensive use of this pattern. As an example, let's consider this snippet, which is mapping an InputStream to a Reader object:
InputStreamReader input = new InputStreamReader(new FileInputStream("input.txt"));
3. Bridge
A bridge pattern allows separation between abstractions and implementations so that they can be developed independently from each other but still have a way, or bridge, to coexist and interact.
An example of this in Java would be the JDBC API. It acts as a link between the database such as Oracle, MySQL, and PostgreSQL, and their particular implementations.
The JDBC API is a set of standard interfaces such as Driver, Connection, and ResultSet, to name a few. This enables different database vendors to have their separate implementations.
For example, to create a connection to a database, we'd say:
Connection connection = DriverManager.getConnection(url);
Here, url is a String that can represent any database vendor.
As an example, for PostgreSQL, we might have:
String url = "jdbc:postgresql://localhost/demo";
And for MySQL:
String url = "jdbc:mysql://localhost/demo";
4. Composite
This pattern deals with a tree-like structure of objects. In this tree, the individual object, or even the entire hierarchy, is treated the same way. In simpler words, this pattern arranges objects in a hierarchical fashion so that a client can work seamlessly with either the part of the whole.
Nested containers in AWT/Swing are great examples of usages of the composite pattern in core Java. The java.awt.Container object is basically a root component that can contain other components, forming a tree structure of nested components.
Consider this code snippet:
JTabbedPane pane = new JTabbedPane();
pane.addTab("1", new Container());
pane.addTab("2", new JButton());
pane.addTab("3", new JCheckBox());
All the classes used here – namely, JTabbedPane, JButton, JCheckBox, and JFrame – are descendants of Container. As we can see, this code snippet handles the root of the tree or Container, in the second line, in the same way as it handles its children.
5. Decorator
This pattern comes into play when we want to enhance the behavior of an object without modifying the original object itself. This is achieved by adding a wrapper of the same type to the object to attach additional responsibility to it.
One of the most ubiquitous usages of this pattern can be found in the java.io package:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("test.txt")));
while (bis.available() > 0) {
char c = (char) bis.read();
System.out.println("Char: " + c);
}
Here, BufferedInputStream is decorating the FileInputStream to add the capability to buffer the input. Notably, both these classes have InputStream as a common ancestor. This implies that both the object that decorates and the object that's being decorated are of the same type. This is an unmistakable indicator of decorator pattern.
6. Facade
By definition, the word facade means an artificial or false appearance of an object. Applied to programming, it similarly means providing another face – or rather, interface – to a complex set of objects.
This pattern comes into play when we want to simplify or hide the complexity of a subsystem or framework.
Faces API's ExternalContext is an excellent example of the facade pattern. It uses classes such as HttpServletRequest, HttpServletResponse, and HttpSession internally. Basically, it's a class that allows the Faces API to be blissfully unaware of its underlying application environment.
Let's look at how Primefaces uses it to write an HttpResponse, without actually knowing about it:
protected void writePDFToResponse(ExternalContext externalContext, ByteArrayOutputStream baos, String fileName)
throws IOException, DocumentException {
externalContext.setResponseContentType("application/pdf");
externalContext.setResponseHeader("Expires", "0");
// set more relevant headers
externalContext.setResponseContentLength(baos.size());
externalContext.addResponseCookie(
Constants.DOWNLOAD_COOKIE, "true", Collections.<String, Object>emptyMap());
OutputStream out = externalContext.getResponseOutputStream();
baos.writeTo(out);
// do cleanup
}
As we can see here, we're setting the response headers, the actual response, and the cookie directly using ExternalContext as a facade. HTTPResponse is not in the picture.
7. Flyweight
The flyweight pattern takes the weight, or memory footprint, off of our objects by recycling them. In other words, if we have immutable objects that can share state, as per this pattern, we can cache them to improve system performance.
Flyweight can be spotted all over the Number classes in Java.
The valueOf methods used to create an object of any data type's wrapper class are designed to cache values and return them when required.
For example, Integer has a static class, IntegerCache, which helps its valueOf method to always cache values in the range -128 to 127:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) {
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}
8. Proxy
This pattern offers a proxy, or a substitute, to another complex object. While it sounds similar to a facade, it's actually different in the sense that a facade offers a different interface to the client to interact with. In the case of a proxy, the interface is the same as that of the object it hides.
Using this pattern, it becomes easy to perform any operation on the original object before or after its creation.
JDK provides a java.lang.reflect.Proxy class out-of-the-box for proxy implementations:
Foo proxyFoo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class }, handler);
The above code snippet creates a proxy, proxyFoo, for an interface Foo.
9. Conclusion
In this short tutorial, we saw practical usages of structural design patterns implemented in core Java.
To summarize, we briefly defined what each of the seven patterns stands for and then understood them one by one with code snippets.
The post Structural Patterns in Core Java first appeared on Baeldung.