0. What is the Adapter Pattern?
What is the problem it’s solving?
Incompatible interfaces between a client and a service-provider.
How to solve the problem?
Adapter convert the interface of one class into an interface a client expects.
Sometimes adapters are referred to as wrappers, because they wrap an incompatible class into a compatible one.
1. What software design principles apply to this pattern?
当出现以下情况的时候，可以考虑用 Adapter 解决问题。
- Polymorphism（多态）can be used to avoid complex conditional logic
- Prefer loose coupling to third party dependencies (e.g., files, APIs should be easily integrated)
- Improve testability of the clients
2. How to apply this pattern?
Follow SOLID principles:
- Follow Single Responsibility Principle & Interface Segregation Principle to create small, focused interfaces that are easiest to wrap with Adapters
- If the S and I are not followed, you are likely to break Liskov substitution principle because you might decide not to fully implement a large interface
- Open/Closed Principle can make sure that we don’t need to change the code to change the way we get data from service providers, which means the client depends on adapter abstraction and it no longer needs to be changed
Different Kinds of Adapters
- Object Adapters
- Class Adapters
The difference is how they reference the class that they are wrapping or adapting.
Adapter can work with service providers as well as wrapping result types.
- Hold an instance of the Adaptee class
- Can implement an Adapter interface or inherit from the Adapter type
- Use composition and single inheritance
If an Adapter implementation uses composition instead of inheritance, it is an Object Adapter.
- Inherits from the Adaptees and the Adapter type
- Requires multiple inheritance
One variation of class adapter that doesn’t require multiple class inheritance simply implements the Adapter Interface, this variation can be used in languages like C# and Java (they don’t support multiple inheritance).
In C#, the Adapter pattern uses composition and is known as an object adapter. The Adapter implementations will contain instances of the incompatible type and will delegate calls to this instance’s incompatible methods or properties.
Usually you’ll use the Adapter pattern when you are already going to work with a particular interface to an existing class implementation.
- Take the if-else statements and break them out into two different class implementations (extract the logic out)
- When the extracted methods belong to different classes and have different signatures (basically different interfaces are present)
- Identify an Interface that we can use commonly across these classes (least input required to execute the method)
- Create the Interface with suitable signature (e.g., async functions should have return type of Task)
- Refactor the code by creating different implementations of the Adapter Interface (this is the core of the adapter pattern)
4. What are the similar or frequently used patterns with Adapter?
Similar but different design intent patterns:
- Decorator Pattern - has a similar structure, but the intent of Decorator is to add functionality
- Proxy Pattern - has a similar structure, but the intent is to control access to a resource, not to convert an incompatible interface
- Bridge Pattern - has a similar structure, but the intent is to allow interfaces and their implementations to vary independently from one another.
Patterns that commonly used together:
- Data Access (Repository) Pattern - sometimes acts as an Adapter to provide a common interface for persistence that can map various incompatible interfaces to a single common data access strategy
- Strategy Pattern (Policy Pattern) - frequently used with Adapter as a way of injecting different implementations of behaviour into a particular client class
- Facade Pattern（外观模式） - intent is similar to Adapter, it alters an interface to make it easier for a client to use, compare to Adapter, it provides a simpler interface to use when working with one or more types
- Facade often sits in front of multiple different types, and its goal is to simplify a complex set of operations, not necessarily to provide a way to easily swap between different incompatible operations.