Design Pattern Tour: Structural Patterns

Adapter vs. Proxy vs. Decorator

Adapter

Readings:

I think Delegate is the core idea of all structural patterns, as well as some creational patterns, such as Factory and Builder.

Adapter, as the name indicates, is a way to connect incompatible interfaces. There are a variety of implementations, as long as the adapter can provide the client with all target interfaces(interfaces required by the client).

There are mainly two kinds of implementations:

  1. Class adapter: The adapter class should inherit from the service class and implement the target interface. But it sometimes requires multiple inheritance, which is not supported in some programming language(e.x. Java).
  2. Object adapter: The adapter class, as a wrapper, only needs to implement the target interface, and the service will reside within as a wrappee(adaptee), following the Object Composition Principle. Personally speaking, I would prefer this approach, because it’s more flexible and doesn’t involve the annoying multiple inheritance.

The design of adapter follows the Single Responsibility Principle: The function calls and arguments(data) are converted in the adapter class, instead of in the client code. It also follows the Open/Closed Principle: If you want to change the backend service or change the frontend APIs while you still want to reuse your existing service, the only thing the client needs to do is to replace her current service with the adapter class.

As for the advantages, I still cannot agree with the opinions of decoupling. I think it’s just a convenient way for code reuse.

So, here comes a problem: Can an adapter adapt to multiple services? Technically, it definitely can. We can simply add more services as adaptees. However, from the aspect of the intent, to add multiple services is to simplify(aka. We just want to use part of the functionalities) while Adapter is focusing on changes(aka. We want to reuse the code with different target interfaces and implement the same functionalities). Thus, to adapt multiple services can be defined as another structural pattern: Facade.

And this website(https://refactoring.guru/design-patterns/adapter) also mentions this difference: “Facade defines a new interface for existing objects, whereas Adapter tries to make the existing interface usable. Adapter usually wraps just one object, while Facade works with an entire subsystem of objects.”

Proxy

Readings:

Proxy looks quite similar to Object Adapter from the code aspect, but the goals are different. Adapter just wants to have the same(existing) functionalities, but the target interfaces are not compatible. However, Proxy wants to enhance current functionalities or add new functionalities while the interfaces remain the same.

For Proxy, new functionalities involve logging, cache, RPC, access control, lazy initialization, etc.

Decorator

The implementations of Decorator, Proxy and Adapter are similar, which are all based on the Object Composition Principle and wrap a wrappee to delegate.

Decorator and Proxy are both to enhance the functionalities and their difference is slight. From the reading materials, I prefer this explanation, although it’s not very strict: “Decorator focuses on adding new functionalities dynamically, while Proxy underscore the access control over objects. In other words, proxy class can hide detailed information from the client. Therefore, we always create an instance of a class(a wrappee) within the proxy class. However, we usually pass the original instance of a class to the constructor of the decorator class as an argument while applying the decorator pattern.”

And “pass the original instance of a class to the constructor of the decorator class as an argument” also explains why decorators are always implemented recursively, because we can add a bunch of decorators to the same class(or a method in the class).

Actually, this explanation is still not very clear. So, if I’m asked to tell the difference, I’d like to say, if there’s only one new functionality and the client can decide whether she wants to use it or not, I will use Proxy. However, if there are N functionalities, and the client can select K(0<=0<=N) of them to add, I will use Decorator.

Another thing I want to mention here is the difference between Java Annotation and Python Decorator. They look the same, like @xxx. However, they are completely two different things. Java Annotation is just an annotation(a marker). And it by itself just stores metadata and you must have something that inspects it to add behaviors. And Python Decorator is a syntactic sugar for passing a function to another function and replacing the first function with the result. (But of course, Python Decorator can decorate classes: https://foofish.net/python-decorator.html)(https://stackoverflow.com/questions/15347136/is-a-python-decorator-the-same-as-java-annotation-or-java-with-aspects)

Bridge vs. Strategy

Bridge

Readings:

Bridge’s intent is to split a monolithic class, meaning to avoid a class being too big. Of course, it should still follow the Single Responsibility Principle. Thus, the normal idea is to split it by Abstraction and Implementation.

Abstraction, from my understanding, is business logics, while Implementation means the implementation details. And both Abstraction and Implementation, as an interface, are able to be extended. In practice, the Implementation will serve as a member variable(in composite way) to delegate the details.

I want to take the remote(Abstraction) and TV(Implementation) as an example:The client only cares if she is able to switch channels and turn up/down the voice, but doesn’t care how the remote does those, which is the responsibility of the TV. Therefore, the Abstraction and the Implementation get separated.Remotes of the same brand should have the same interface for different TVs, so they should be able to pair any of these TVs.

For the use cases, I think Bridge gives me a sense of “2D orthogonal”.

Strategy(or State)

Strategy is a behavioral pattern. Although the implementation of Strategy is similar to Bridge, Bridge, as a structural pattern, is focusing on simplifying the code structure. However, behavioral patterns usually consider the convenience of functionalities.

You should find there are only a few ways of design pattern implementation(e.x. delegate, composite), although there are a lot of design patterns. So, I don’t think we should remember all these concrete patterns. Instead, we should learn what problems they are trying to solve(aka. intent) and their core ideas, and then we can say we have truly understood design patterns.

Adapter vs. Facade

Facade

Readings:

I think I’ve mentioned Facade in the section of Adapter before: Facade is aiming to simplify the interfaces for multiple classes(or subsystems) as a coordinator, whose implementation is just like an adapter with many services embedded.

Practically, when the logics become so complicated that the client feels uncomfortable using the framework(e.x. The client finds she is writing more code for a single logic), we should abstract a simple interface for the commonly used logics and probably can apply Facade if we still want to reuse current code.

For example, I really like this example of MVC(https://zhuanlan.zhihu.com/p/26036733) that the Controller becomes too bulky to be maintained(probably because I had a similar experience).

And for Facade, I think it’s safe to say it decouples the client code, because the client only needs to interact with Facade rather than with all kinds of subsystems(Because all couplings are moved into Facade class).

There’s a small detail that Facade is usually implemented in the Singleton way because a single instance is enough for a coordinator.

Others

Composite

Readings:

If the object we are designing is of a tree structure and all the nodes in the tree(both leaf nodes and top nodes) have the same interface, then we can apply composite.A widely used case is UI components.

Of course, the leaf nodes and top nodes always have some not in common. So, here comes the trade-off between safety and transparency. To add APIs which are supported by top nodes but not by the leaf nodes to the common interface increases transparency, but it undermines the safety because it’s apparently unsafe to be able to call some functions incompatible with leaf nodes. Otherwise, it improves safety but loses transparency because the client should check if a node is a top node or a leaf node before she calls the APIs.

Thus, it’s one of the issues we need to consider while designing. Personally, I prefer the safer way. Although it breaches the intent of consistent interfaces of Composite, the logics are more reasonable.

There’s an optimization of the implementation: Because of the tree structure, it always involves a lot of aggregation and traversal, which can be optimized using cache to make the program faster.

Flyweight

Readings:

Flyweight is a method to save memory(RAM). It extracts a frequently used part from many objects of the same class(called Intrinsic State) as a class(or enum) and leaves the other part(called Extrinsic State) and then shares the frequently used part.

Usually, it is combined with Factory Method because Factory can maintain a Flyweight pool of frequently used objects implicitly.