第11节 结构型模式(适配器模式)
❤️💕💕Java和Golang的设计模式,设计模式介绍、创建者模式、结构型模式、行为型模式。Myblog:http://nsddd.top
[TOC]
结构型模式
模式名称 | 模式名称 | 作用 |
---|---|---|
结构型模式 Structural Pattern (7) | 适配器模式 ★★★★☆ | 将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 |
桥接模式 ★★★☆☆ | 将抽象部分与实际部分分离,使它们都可以独立的变化。 | |
组合模式 ★★☆☆☆ | 将对象组合成树形结构以表示“部分--整体”的层次结构。使得用户对单个对象和组合对象的使用具有一致性。 | |
装饰模式 ★★★☆☆ | 动态的给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活。 | |
外观模式 ★★★★★ | 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 | |
享元模式 ★☆☆☆☆ | 以共享的方式高效的支持大量的细粒度的对象。 | |
代理模式 ★★★★☆ | 为其他对象提供一种代理以控制对这个对象的访问。 |
什么是适配器
将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
+----------------------+ +--------------------------+
| Target Interface | <---- | Client Struct |
+----------------------+ +--------------------------+
| | | - adapter AdapterIface |
| TargetMethod() void | +--------------------------+
| | |
+----------------------+ +-------------------------------+
| Adapter Struct |
+-------------------------------+
| - adaptee AdapteeStructIface |
+-------------------------------+
| - AdapterMethod() void |
+-------------------------------+
- 目标接口(Target Interface): 这是客户端(Client)代码期望使用的接口。它定义了一个或多个方法,客户端(Client)通过这些方法来与系统进行交互。
- 客户端(Client Struct): Clients是使用目标接口(Target Interface)的结构体。他们并不知道实际使用的具体实现,而是通过目标接口(Target Interface)与系统进行交互。客户端(Client)拥有一个指向适配器(Adapter)的引用,该适配器实现了目标接口(Target Interface),并将请求委派给被适配者(Adaptee)。
- 适配器(Adapter): 适配器(Adapter)是连接客户端(Client)和被适配者(Adaptee)的桥梁。它实现了目标接口(Target Interface),并将客户端(Client)请求转换为被适配者(Adaptee)可以理解的形式。适配器(Adapter)通常包装了一个被适配者(Adaptee)对象,并在目标接口(Target Interface)中定义了一个或多个方法来调用适配器(Adapter)的方法。
- 被适配者(Adaptee): 被适配者(Adaptee)是系统的一部分,它是适配器(Adapter)所要调用的对象。被适配者(Adaptee)有自己独特的接口,但客户端(Client)不知道如何与其进行交互。
演示
// 目标接口(Target Interface)
type TargetInterface interface {
Request() string
}
// 被适配者(Adaptee)
type Adaptee struct{}
func (a *Adaptee) SpecificRequest() string {
return "Specific Request"
}
// 适配器(Adapter)
type Adapter struct {
adaptee *Adaptee
}
func (ad *Adapter) Request() string {
return ad.adaptee.SpecificRequest()
}
// 客户端(Client)
type Client struct{}
func (c *Client) ExecuteRequest(target TargetInterface) string {
return target.Request()
}
func main() {
adaptee := &Adaptee{}
adapter := &Adapter{adaptee: adaptee}
client := &Client{}
result := client.ExecuteRequest(adapter)
fmt.Println(result)
}
在上面的例子中,我们使用了一个目标接口(Target Interface),该接口定义了一个Request()
方法。然后我们创建了一个被适配者(Adaptee),它实现了不兼容的SpecificRequest()
方法。
接着,我们创建了一个适配器(Adapter),它实现了目标接口(Target Interface),并将适配请求委托给被适配者(Adaptee)的SpecificRequest()
方法。
最后,我们创建了一个客户端(Client),它可以执行任何符合目标接口(Target Interface)的请求。在这个例子中,我们将适配器(Adapter)传递给客户端(Client),并调用ExecuteRequest()
方法。ExecuteRequest()
方法向适配器(Adapter)发出请求,该适配器(Adapter)将请求转换为被适配者(Adaptee)可以理解的形式并返回结果。
这个例子演示了如何使用Go语言实现适配器模式来解决不兼容的接口问题。
场景
一般在调用第三方接口的时候会选择使用适配器模式
比如说 Kubernetes 源码中对 docker 或者 containerd 的时候,选择使用适配器包装,包装为 Kubernetes 中的规范。
虽然现在在 Kubernetes 的新版本源码中被废弃了,但是在之前很长一段时间依旧是选择的适配器。
模拟相关的代码实现:
package main
import "fmt"
// Target Interface, 需要适配成的接口
type ContainerRuntime interface {
RunContainer(image string) error
}
// 被适配者(Adaptee), docker
type Docker struct{}
func (d *Docker) StartContainer(image string) error {
fmt.Printf("Starting container with image %s using Docker...\n", image)
return nil
}
// 适配器(Adapter), 将docker转换为ContainerRuntime接口
type DockerAdapter struct {
docker *Docker
}
func (da *DockerAdapter) RunContainer(image string) error {
return da.docker.StartContainer(image)
}
// 被适配者(Adaptee), containerd
type Containerd struct{}
func (c *Containerd) CreateContainer(image string) error {
fmt.Printf("Creating container with image %s using Containerd...\n", image)
return nil
}
// 客户端(Client), Kubernetes
type Kubernetes struct{}
func (k *Kubernetes) HandleContainer(runtime ContainerRuntime, image string) {
runtime.RunContainer(image)
}
func main() {
docker := &Docker{}
dockerAdapter := &DockerAdapter{docker: docker}
containerd := &Containerd{}
kubernetes := &Kubernetes{}
// Kubernetes 使用 docker
kubernetes.HandleContainer(dockerAdapter, "nginx")
// Kubernetes 使用 containerd,需要创建一个新的Adapter将containerd适配成ContainerRuntime接口
containerdAdapter := &struct {
*Containerd
}{}
containerdAdapter.Containerd = containerd
kubernetes.HandleContainer(containerdAdapter, "nginx")
}
在本示例中,我们有两个具体的被适配者(Adaptee):Docker和Containerd。它们都实现了不同的接口,但我们想要将它们适配为统一的ContainerRuntime接口。
为此,我们创建了一个目标接口(ContainerRuntime),其中只定义了一个RunContainer()
方法。我们还创建了两个被适配者(Adaptee):Docker和Containerd,它们分别实现了不同的方法来启动容器。
然后,我们创建了一个适配器(Adapter),将Docker适配成ContainerRuntime接口。DockerAdapter结构体包装了一个指向Docker对象的引用,并实现了ContainerRuntime接口的RunContainer()
方法。
最后,我们创建了一个客户端(Client),即Kubernetes。Kubernetes结构体包含一个HandleContainer()
方法,该方法接受一个ContainerRuntime类型参数和一个镜像名称(image),并使用给定的运行时(runtime)来启动容器。
在本例中,我们使用DockerAdapter将Docker适配成ContainerRuntime接口,并将其传递给Kubernetes。然后,我们创建了一个新的containerdAdapter,将Containerd适配成ContainerRuntime接口,并将其传递给Kubernetes。这样,我们就可以使用不同的容器运行时来启动容器,并且Kubernetes不需要知道底层容器运行时的细节。
因此,本示例演示了如何使用适配器模式来解决Kubernetes与不同容器运行时之间的兼容性问题。
优缺点
优点:
(1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
(2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
(3) 灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
适配器中置换适配者类的某些方法比较麻烦。
END 链接
✴️版权声明 © :本书所有内容遵循CC-BY-SA 3.0协议(署名-相同方式共享)©