我们什么时候需要适配器模式?
我们什么时候需要采用适配器模式?如果可能的话,请给我一个适合该模式的现实世界示例。
When do we need to go for Adapter pattern? If possible give me a real world example that suits that pattern.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我开发了一个需要与外部 DVR 接口的系统。大多数情况下,所有 DVR 都具有相同的基本功能:从某个视频源开始录制;停止录音;从某个时间开始播放; 每个 DVR 制造商都提供了一个软件库,允许我们编写代码来
控制他们的设备(为了讨论起见,我将其称为 SDK)。尽管每个 SDK 都提供了所有基本功能的 API,但它们都不完全相同。这是一个非常粗略的示例,但您明白了:
我们的软件需要能够与所有 DVR 交互。因此,我们没有为每个不同的 SDK 编写可怕的 switch/case,而是创建了自己的通用 IDVRController 接口,并将所有系统代码写入该接口:
然后,我们为每个 SDK 编写了不同的适配器实现,所有这些都实现了我们的 IDVRController 接口。我们使用配置文件来指定系统将连接到的 DVR 类型,并使用工厂模式来实例化该 DVR 的 IDVRController 的正确实现者。
这样,适配器模式使我们的系统代码变得更简单:我们总是编码到 IDVRController。它允许我们在部署后推出新 SDK 的适配器(我们的工厂使用反射来实例化正确的 IDVRController 实例)。
I worked on a system which needed to interface with external DVRs. For the most part, all DVRs have the same basic functionality: start recording from a certain video source; stop recording; start playback from a certain time; stop playback, etc.
Every DVR manufacturer provided a software library, allowing us to write code to control their device (for sake of this discussion, I'll refer to it as the SDK). Even though every SDK provided APIs for all the basic functionality, none of them were quite the same. Here's a very rough example, but you get the idea:
Our software needed to be able to interact with all DVRs. So instead of writing horrible switch/cases for each different SDK, we created our own common IDVRController interface, and wrote all of our system code to that interface:
We then wrote a different adapter implementation for each SDK, all of which implemented our IDVRController interface. We used a config file to specify the type of DVR the system would connect to, and a Factory pattern to instantiate the correct implementer of IDVRController for that DVR.
In that way, the adapter pattern made our system code simpler: we always coded to IDVRController. And it allowed us to roll out adapters for new SDKs post-deployment (our Factory used reflection to instantiate the correct IDVRController instance).
现有接口
Shape 接口的当前实现
现在考虑一下您希望 Circle 类适应我们无法修改的现有接口(由第三方编写)。
现在我们已经将 Circle 实现适应了我们的 Shape 接口。因此我们需要一个适配器,因为它们不兼容。
CircleAdaptor - 是 Circle 的适配器;
圆圈 - 是适应者;
形状 - 是目标界面。
希望这可以让您更好地了解何时使用它。
另请参阅什么是装饰器模式?
Existing Interface
Current Implementation for Shape interface
Now Consider that you want Circle class to adapt to our existing interface which in no way we can modify (Written by third party).
Now we have adapt Circle implementation to our Shape interface. So we need an adaptor as they are incompatible.
CircleAdaptor - Is the Adaptor for Circle;
Circle - Is the Adaptee;
Shape - Is the Target Interface.
Hope this gives a better idea about when to use it.
See also what is Decorator pattern?
维基百科!!!
Wikipedia!!!
当您必须处理具有相似行为的不同接口(这通常意味着具有相似行为但具有不同方法的类)时,可以使用适配器设计模式。例如,一个类连接到三星电视,另一个类连接到索尼电视。它们将共享常见的行为,例如打开菜单、开始播放、连接到网络等,但每个库都会有不同的实现(具有不同的方法名称和签名)。这些不同供应商特定的实现在 UML 图中称为Adaptee。
因此,在您的代码(在 UML 图中称为 Client)中,您可以创建一个通用接口,而不是对每个供应商(或 Adaptee)的方法调用进行硬编码(在 UML 图中称为 Target)来包装这些类似的行为并仅使用一种类型的对象。
然后适配器将实现目标接口,将其方法调用委托给通过适配器传递给适配器的Adaptees构造函数。
为了让您在 Java 代码中实现这一点,我使用与上面提到的完全相同的示例编写了一个非常简单的项目,使用适配器来处理多个智能电视接口。该代码很小,文档齐全且不言自明,因此请深入研究它以了解现实世界的实现是什么样子。
只需下载代码并将其作为 Maven 项目导入 Eclipse(或您最喜欢的 IDE)即可。您可以通过运行org.example.Main.java来执行代码。请记住,这里重要的是了解如何将类和接口组装在一起来设计模式。我还在com.thirdparty.libs包中创建了一些假的Adaptees。希望有帮助!
https://github.com/Dannemann/java-design-patterns
You can use the Adapter design pattern when you have to deal with different interfaces with similar behavior (which usually means classes with similar behavior but with different methods). An example of it would be a class to connect to a Samsung TV and another one to connect to a Sony TV. They will share common behavior like open menu, start playback, connect to a network and etc but each library will have a different implementation of it (with different method names and signatures). These different vendor specific implementations are called Adaptee in the UML diagrams.
So, in your code (called Client in the UML diagrams), instead of hard code the method calls of each vendor (or Adaptee), you could then create a generic interface (called Target in UML diagrams) to wrap these similar behaviors and work with only one type of object.
The Adapters will then implement the Target interface delegating its method calls to the Adaptees that are passed to the Adapters via constructor.
For you to realize this in Java code, I wrote a very simple project using exactly the same example mentioned above using adapters to deal with multiple smart TV interfaces. The code is small, well documented and self explanatory so dig on it to see how a real world implementation would look like.
Just download the code and import it to Eclipse (or your favorite IDE) as a Maven project. You can execute the code by running org.example.Main.java. Remember that the important thing here is to understand how classes and interfaces are assembled together to design the pattern. I also created some fake Adaptees in the package com.thirdparty.libs. Hope it helps!
https://github.com/Dannemann/java-design-patterns
在以下场景中需要适配器模式:
定义了接口
I1
假设您已经使用方法
M1
和M2
C1
,并且C2
实现此接口I1
,现在用于C1
,同时实现M1
和M2
您没有找到其他现有类的帮助,因此您需要自己编写所有逻辑。现在,在实现类
C2
时,您遇到了类C3
,其方法M3
和M4
可用于实现M1
和M2
用于C2
,以便在类M3
和M4
中使用这些M3
和M4
code>C2 扩展类C3
并使用C3
的M3
和M4
。在此示例中,
C2
变为Adapter 类
,C3
变为adaptee
Adapter pattern is required in following scenario:
Say you have defined an interface
I1
with methodM1
andM2
C1
andC2
implements this interfaceI1
, now forC1
while implementingM1
andM2
you have found no help from other existing classes so you need to write all logic by yourself.Now while implementing class
C2
you have come across classC3
with methodsM3
andM4
that can be used to implementM1
andM2
forC2
so to utilize thoseM3
andM4
in classC2
you extends classC3
and useM3
andM4
ofC3
.In this example
C2
becomesAdapter class
andC3
becomesadaptee
不兼容的接口
EuroPlug
连接器仅连接到欧洲电源插座:USPlug
连接器仅连接到美国电源插座:创建适配器
当我们有
USSocket
和 < code>EuroPlug,我们创建一个适配器将EuroPlug
转换为USPlug
:现在
EuroToUSPlugAdapter
适配接口USPlug< /code> 已经存在的类
USSocket
而不对其进行更改。使用适配器
这里我们有一个
USSocket
但有一个EuroPlug
对象。因此,我们将EuroPlug
对象传递给EuroToUSPlugAdapter
,它负责将EuroPlug
转换为USPlug
:就是这样!这就是适配器模式允许两个不兼容的接口一起工作的方式。希望有帮助。
Incompatible interfaces
EuroPlug
connector connects only to european electrical sockets:USPlug
connector connects only to US electrical sockets:Creating an adapter
When we have
USSocket
andEuroPlug
, we create an adapter to convert theEuroPlug
toUSPlug
:Now
EuroToUSPlugAdapter
adapts the interfaceUSPlug
of the already existing classUSSocket
without changing it.Using the adapter
Here we have a
USSocket
but with aEuroPlug
object. So, we pass theEuroPlug
object to theEuroToUSPlugAdapter
which does the work of converting aEuroPlug
to theUSPlug
:That's it! This is how the adapter pattern allows two incompatible interfaces to work together. Hope that helps.
适配器模式的一个非常常见的示例是通过服务提供者接口< /a> 并且在很多 Java EE 框架中常用。
其原因是允许 Java EE 的不同实现,但程序员只需根据 Java EE 规范进行编码,而不是特定于实现的内容。
与直接使用 WebSphere 类进行编码之类的东西相反,这会限制您使用 WebSphere。
或者更糟糕(根据我的经验),Apache HTTP Client 并后来发现,因为您编码为该实现而不是正常的 HttpUrlConnection,所以您必须进行大量重新编码,因为它不支持当前版本的 TLS,这将是如果原始开发人员编码为更稳定的 API,而我们只需要升级 Java 运行时,就可以避免这种情况。
A very common example of the adapter pattern is done through the Service Provider Interface and is commonly used in a lot of the Java EE framework.
The reason for it is to allow different implementations of Java EE but programmers simply code to the Java EE spec rather than something implementation specific.
As opposed to something like coding directly using WebSphere classes which lock you into using WebSphere.
Or worse (from my experience), Apache HTTP Client and find out later that because you coded to that implementation rather than the normal HttpUrlConnection you have to do a lot of recoding because it does not support the current version of TLS which would've been avoided if the original developer coded to a more stable API and we just need to upgrade Java runtime.
摘自:Alexey Soshin 的书《Kotlin 设计模式与最佳实践第二版》:
Adapter 设计模式的主要目标是将一个接口转换为
另一个界面。在物理世界中,这个想法的最好例子是
可以是电源插头适配器或USB 适配器。
想象一下你深夜在酒店房间里,电池电量还剩 7%
你的手机。您的手机充电器落在办公室另一端
城市。您只有一个欧盟插头充电器和迷你 USB 数据线。但你的
手机使用USB-C,因为您必须升级。你在纽约,所以所有
您的插座(当然)是 USB-A。那么,你做什么呢?哦,这很容易。你
在半夜寻找一个迷你 USB 转 USB-C 适配器并希望
您还记得带上欧盟至美国插头适配器。
电量仅剩 5% – 时间不多了!
那么,既然我们了解了适配器在物理世界中的用途,那么让我们
看看我们如何在代码中应用相同的原则。
让我们从接口开始。
USPlug 假定电源为 Int。如果它有幂,它的值就是 1
任何其他值(如果没有):
EUPlug 将电源视为字符串,为 TRUE 或 FALSE:
对于 UsbMini,电源是一个枚举:
最后,对于 UsbTypeC,功率是一个布尔值:
我们的目标是将美国电源插座的功率值带到我们的
手机,将由此函数表示:
让我们首先在代码中声明美国电源插座的外观。它
将是一个返回 USPlug 的函数:
我们的充电器将是一个将 EUPlug 作为输入和输出的函数
UsbMini:
接下来,让我们尝试将我们的手机、充电器和usPowerOutlet结合起来
函数:
如您所见,我们遇到了两种不同类型的错误 - 适配器设计
模式应该可以帮助我们解决这些问题。
调整现有代码
我们需要两种类型的适配器:一种用于电源插头,另一种用于电源插头
我们的 USB 端口。
我们可以通过定义以下内容来使美国插头与欧盟插头配合使用
以下扩展功能:
我们可以在Mini USB和USB-C实例之间创建一个USB适配器
以类似的方式:
最后,我们可以通过组合所有这些适配器再次重新上线
一起:
适配器设计模式比其他设计更简单
模式,你会看到它被广泛使用。现在,让我们更详细地讨论它在现实世界中的一些用途。
现实世界中的适配器
您可能遇到过适配器设计模式的许多用途
已经。这些通常用于适应概念和
实施。例如,让我们采用JVM集合的概念
与 JVM 流的概念不同。
列表是可以使用 listOf() 创建的元素集合
功能:
流是元素的惰性集合。你不能简单地通过
收集到接收流的函数,即使它可能会使
sense:
幸运的是,集合为我们提供了 .stream() 适配器方法:
许多其他 Kotlin 对象都有适配器方法,通常以 to 开头
作为前缀。例如,toTypedArray() 将列表转换为数组。
使用适配器的注意事项
您是否曾经将 110 V 美国电器插入 220 V EU 插座
通过适配器,然后完全炸了?
如果您不小心,您的代码也可能会发生这种情况。
以下示例使用另一个适配器,它也可以很好地编译:
但它永远不会完成,因为 Stream.generate() 生成无限列表
整数。因此,请小心并明智地采用这种设计模式。
From: Alexey Soshin's Book “Kotlin Design Patterns and Best Practices Second Edition”:
The main goal of the Adapter design pattern is to convert one interface to
another interface. In the physical world, the best example of this idea would
be an electrical plug adapter or a USB adapter.
Imagine yourself in a hotel room late in the evening, with 7% battery left on
your phone. Your phone charger was left in the office at the other end of the
city. You only have an EU plug charger with a Mini USB cable. But your
phone uses USB-C, as you had to upgrade. You're in New York, so all of
your outlets are (of course) USB-A. So, what do you do? Oh, it's easy. You
look for a Mini USB to USB-C adapter in the middle of the night and hope
that you have remembered to bring your EU to US plug adapter as well.
Only 5% battery left – time is running out!
So, now that we understand what adapters are for in the physical world, let's
see how we can apply the same principle in code.
Let's start with interfaces.
USPlug assumes that power is Int. It has 1 as its value if it has power and
any other value if it doesn't:
EUPlug treats power as String, which is either TRUE or FALSE:
For UsbMini, power is an enum:
Finally, for UsbTypeC, power is a Boolean value:
Our goal is to bring the power value from a US power outlet to our
cellphone, which will be represented by this function:
Let's start by declaring what a US power outlet will look like in our code. It
will be a function that returns a USPlug:
Our charger will be a function that takes EUPlug as an input and outputs
UsbMini:
Next, let's try to combine our cellPhone, charger, and usPowerOutlet
functions:
As you can see, we get two different type errors – the Adapter design
pattern should help us solve these.
Adapting existing code
We need two types of adapters: one for our power plugs and another one for
our USB ports.
We could adapt the US plug to work with the EU plug by defining the
following extension function:
We can create a USB adapter between the Mini USB and USB-C instances
in a similar way:
Finally, we can get back online again by combining all those adapters
together:
The Adapter design pattern is more straightforward than the other design
patterns, and you'll see it used widely. Now, let's discuss some of its real world uses in more detail.
Adapters in the real world
You've probably encountered many uses of the Adapter design pattern
already. These are normally used to adapt between concepts and
implementations. For example, let's take the concept of a JVM collection
versus the concept of a JVM stream.
A list is a collection of elements that can be created using the listOf()
function:
A stream is a lazy collection of elements. You cannot simply pass a
collection to a function that receives a stream, even though it may make
sense:
Luckily, collections provide us with the .stream() adapter method:
Many other Kotlin objects have adapter methods that usually start with to
as a prefix. For example, toTypedArray() converts a list to an array.
Caveats of using adapters
Have you ever plugged a 110 V US appliance into a 220 V EU socket
through an adapter, and fried it totally?
If you're not careful, that's something that could also happen to your code.
The following example uses another adapter, and it also compiles well:
But it never completes because Stream.generate() produces an infinite list
of integers. So, be careful and adopt this design pattern wisely.
我在 Bharath Thippireddy 设计模式课程中找到了最清晰、最真实的例子。
我们有
WeatherUI
类,它通过邮政编码查找温度,我们有WeatherFinderImpl
类,它通过城市名称了解温度,因此我们需要创建WeatherAdapter
> 类将获取邮政编码,将其更改为城市名称并调用WeatherFinder
类以返回正确的温度。WeatherUi
类:和
WeatherFinder
接口:以及只能通过城市名称查找温度的
WeatherFinderImpl
:在
WeatherUI
类我们可以创建一个将邮政编码转换为城市的方法,或者我们可以创建一个新的 adapter 类:并在
WeatherUI
中调用它:测试它很简单:
I found the clearest and kind of real life example in Bharath Thippireddy design pattern course.
We have
WeatherUI
class which is looking for temperature by zip-code and we haveWeatherFinderImpl
class which knows temperature by city name so we need to createWeatherAdapter
class which will take zipcode, change it to city name and callWeatherFinder
class to return proper temperature.WeatherUi
class:and
WeatherFinder
interface:and
WeatherFinderImpl
which can find temperature only by city name:having these three in
WeatherUI
class we can create a method which will translate zipcode into city or we can create a new adapter class:and call it in
WeatherUI
:test for it is as simple as: