我想向我的团队演示适配器模式的使用。我在网上阅读了很多书籍和文章。每个人都在引用一个有助于理解这个概念的例子(形状、存储卡、电子适配器等),但没有真正的案例研究。
您能否分享任何适配器模式的案例研究?
ps 我尝试在 stackoverflow 上搜索现有问题,但没有找到答案,因此将其发布为新问题。如果您知道已经有答案,请重定向。
我想向我的团队演示适配器模式的使用。我在网上阅读了很多书籍和文章。每个人都在引用一个有助于理解这个概念的例子(形状、存储卡、电子适配器等),但没有真正的案例研究。
您能否分享任何适配器模式的案例研究?
ps 我尝试在 stackoverflow 上搜索现有问题,但没有找到答案,因此将其发布为新问题。如果您知道已经有答案,请重定向。
Adapter 的许多示例都是微不足道的或不切实际的(矩形与 LegacyRectangle、Ratchet 与 Socket、SquarePeg 与 RoundPeg、Duck 与土耳其)。更糟糕的是,许多没有为不同的 Adaptee 显示多个适配器(有人引用 Java 的 Arrays.asList 作为适配器模式的示例)。仅将一个类的接口适配为与另一个类一起工作似乎是 GoF 适配器模式的一个弱示例。这种模式使用继承和多态性,因此人们期望有一个很好的例子来展示针对不同适配者的适配器的多种实现。
我发现的最好的例子是在应用 UML 和模式:面向对象分析和设计以及迭代开发(第 3 版)的第 26 章。以下图片来自该书的 FTP 站点上提供的讲师资料。
第一个展示了应用程序如何使用功能相似但具有不同 API 的多个实现(适配对象)(例如,税收计算器、会计模块、信用授权服务等)。我们希望避免硬编码我们的域层代码来处理计算税收、售后、授权信用卡请求等不同的可能方式。这些都是可能会有所不同的外部模块,我们无法修改代码。适配器允许我们在适配器中进行硬编码,而我们的域层代码总是使用相同的接口(IWhateverAdapter 接口)。
我们在上图中没有看到实际的适配者。但是,下图显示了如何postSale(...)
在 IAccountingAdapter 接口中进行多态调用,从而通过 SOAP 将销售过帐到 SAP 系统。
如何把一个法国人变成一个正常人...
public interface IPerson
{
string Name { get; set; }
}
public interface IFrenchPerson
{
string Nom { get; set; }
}
public class Person : IPerson
{
public string Name { get; set; }
}
public class FrenchPerson : IFrenchPerson
{
public string Nom { get; set; }
}
// that is a service that we want to use with our French person
// we cannot or don't want to change the service contract
// therefore we need 'l'Adaptateur'
public class PersonService
{
public void PrintName(IPerson person)
{
Debug.Write(person.Name);
}
}
public class FrenchPersonAdapter : IPerson
{
private readonly IFrenchPerson frenchPerson;
public FrenchPersonAdapter(IFrenchPerson frenchPerson)
{
this.frenchPerson = frenchPerson;
}
public string Name
{
get { return frenchPerson.Nom; }
set { frenchPerson.Nom = value; }
}
}
例子
var service = new PersonService();
var person = new Person();
var frenchPerson = new FrenchPerson();
service.PrintName(person);
service.PrintName(new FrenchPersonAdapter(frenchPerson));
将一个接口转换为另一个接口。
适配器模式的任何真实示例
为了连接电源,我们在世界各地都有不同的接口。使用适配器,我们可以很容易地连接。
这是一个模拟转换analog data
为digit data
.
它提供了一个将浮点数数据转换为二进制数据的适配器,它在现实世界中可能没有用,它只是有助于解释适配器模式的概念。
模拟信号.java
package eric.designpattern.adapter;
public interface AnalogSignal {
float[] getAnalog();
void setAnalog(float[] analogData);
void printAnalog();
}
数字信号.java
package eric.designpattern.adapter;
public interface DigitSignal {
byte[] getDigit();
void setDigit(byte[] digitData);
void printDigit();
}
FloatAnalogSignal.java
package eric.designpattern.adapter;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FloatAnalogSignal implements AnalogSignal {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private float[] data;
public FloatAnalogSignal(float[] data) {
this.data = data;
}
@Override
public float[] getAnalog() {
return data;
}
@Override
public void setAnalog(float[] analogData) {
this.data = analogData;
}
@Override
public void printAnalog() {
logger.info("{}", Arrays.toString(getAnalog()));
}
}
BinDigitSignal.java
package eric.designpattern.adapter;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BinDigitSignal implements DigitSignal {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private byte[] data;
public BinDigitSignal(byte[] data) {
this.data = data;
}
@Override
public byte[] getDigit() {
return data;
}
@Override
public void setDigit(byte[] digitData) {
this.data = digitData;
}
@Override
public void printDigit() {
logger.info("{}", Arrays.toString(getDigit()));
}
}
AnalogToDigitAdapter.java
package eric.designpattern.adapter;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Adapter - convert analog data to digit data.
* </p>
*
* @author eric
* @date Mar 8, 2016 1:07:00 PM
*/
public class AnalogToDigitAdapter implements DigitSignal {
public static final float DEFAULT_THRESHOLD_FLOAT_TO_BIN = 1.0f; // default threshold,
private Logger logger = LoggerFactory.getLogger(this.getClass());
private AnalogSignal analogSignal;
private byte[] digitData;
private float threshold;
private boolean cached;
public AnalogToDigitAdapter(AnalogSignal analogSignal) {
this(analogSignal, DEFAULT_THRESHOLD_FLOAT_TO_BIN);
}
public AnalogToDigitAdapter(AnalogSignal analogSignal, float threshold) {
this.analogSignal = analogSignal;
this.threshold = threshold;
this.cached = false;
}
@Override
public synchronized byte[] getDigit() {
if (!cached) {
float[] analogData = analogSignal.getAnalog();
int len = analogData.length;
digitData = new byte[len];
for (int i = 0; i < len; i++) {
digitData[i] = floatToByte(analogData[i]);
}
}
return digitData;
}
// not supported, should set the inner analog data instead,
@Override
public void setDigit(byte[] digitData) {
throw new UnsupportedOperationException();
}
public synchronized void setAnalogData(float[] analogData) {
invalidCache();
this.analogSignal.setAnalog(analogData);
}
public synchronized void invalidCache() {
cached = false;
digitData = null;
}
@Override
public void printDigit() {
logger.info("{}", Arrays.toString(getDigit()));
}
// float -> byte convert,
private byte floatToByte(float f) {
return (byte) (f >= threshold ? 1 : 0);
}
}
AdapterTest.java
package eric.designpattern.adapter.test;
import java.util.Arrays;
import junit.framework.TestCase;
import org.junit.Test;
import eric.designpattern.adapter.AnalogSignal;
import eric.designpattern.adapter.AnalogToDigitAdapter;
import eric.designpattern.adapter.BinDigitSignal;
import eric.designpattern.adapter.DigitSignal;
import eric.designpattern.adapter.FloatAnalogSignal;
public class AdapterTest extends TestCase {
private float[] analogData = { 0.2f, 1.4f, 3.12f, 0.9f };
private byte[] binData = { 0, 1, 1, 0 };
private float[] analogData2 = { 1.2f, 1.4f, 0.12f, 0.9f };
@Test
public void testAdapter() {
AnalogSignal analogSignal = new FloatAnalogSignal(analogData);
analogSignal.printAnalog();
DigitSignal digitSignal = new BinDigitSignal(binData);
digitSignal.printDigit();
// adapter
AnalogToDigitAdapter adAdapter = new AnalogToDigitAdapter(analogSignal);
adAdapter.printDigit();
assertTrue(Arrays.equals(digitSignal.getDigit(), adAdapter.getDigit()));
adAdapter.setAnalogData(analogData2);
adAdapter.printDigit();
assertFalse(Arrays.equals(digitSignal.getDigit(), adAdapter.getDigit()));
}
}
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
只需运行单元测试。
适配器模式充当两个不兼容接口之间的桥梁。这种模式涉及一个称为适配器的类,它负责两个独立或不兼容的接口之间的通信。
现实世界的例子可能是语言翻译器或移动充电器。此 youtube 视频中的更多内容:
当您必须处理具有相似行为的不同接口(这通常意味着具有相似行为但具有不同方法的类)时,您可以使用适配器设计模式。一个例子是一个连接到三星电视的类和另一个连接到索尼电视的类。它们将共享常见的行为,如打开菜单、开始播放、连接到网络等,但每个库都有不同的实现(具有不同的方法名称和签名)。这些不同的供应商特定实现在 UML 图中称为Adaptee 。
因此,在您的代码(在 UML 图中称为Client )中,您可以创建一个通用接口(在 UML 图中称为Target)而不是硬编码每个供应商(或Adaptee )的方法调用,以包装这些类似的行为和工作只有一种类型的对象。
然后,适配器将实现Target接口,将其方法调用委托给通过构造函数传递给适配器的适配器。
为了让您在 Java 代码中实现这一点,我编写了一个非常简单的项目,使用与上面提到的完全相同的示例,使用适配器来处理多个智能电视接口。该代码很小,有据可查且易于解释,因此请深入研究它以了解真实世界的实现情况。
只需下载代码并将其作为 Maven 项目导入 Eclipse(或您最喜欢的 IDE)。您可以通过运行org.example.Main.java来执行代码。请记住,这里重要的是要了解如何将类和接口组合在一起以设计模式。我还在com.thirdparty.libs包中创建了一些假的Adaptee。希望能帮助到你!
适配器设计模式有助于将一个类的接口转换为客户期望的接口。
示例: 您有一个服务,它通过将城市名称作为输入值来返回天气(以摄氏度为单位)。现在,假设您的客户想要传递邮政编码作为输入,并期望得到城市的温度作为回报。在这里,您需要一个适配器来实现这一点。
public interface IWetherFinder {
public double getTemperature(String cityName);
}
class WeatherFinder implements IWetherFinder{
@Override
public double getTemperature(String cityName){
return 40;
}
}
interface IWeatherFinderClient
{
public double getTemperature(String zipcode);
}
public class WeatherAdapter implements IWeatherFinderClient {
@Override
public double getTemperature(String zipcode) {
//method to get cityname by zipcode
String cityName = getCityName(zipcode);
//invoke actual service
IWetherFinder wetherFinder = new WeatherFinder();
return wetherFinder.getTemperature(cityName);
}
private String getCityName(String zipCode) {
return "Banaglore";
}
}
您可以在此处找到用于防御注入攻击的适配器模式的 PHP 实现:
http://www.php5dp.com/category/design-patterns/adapter-composition/
适配器模式的一个有趣的方面是它有两种风格:依赖于多重继承的类适配器和依赖于组合的对象适配器。上面的例子依赖于组合。
一个真实的例子是 Qt-Dbus。
qt-dbus 有一个实用程序,可以从提供的 xml 文件生成适配器和接口代码。以下是执行此操作的步骤。
1. Create the xml file - this xml file should have the interfaces
that can be viewed by the qdbus-view in the system either on
the system or session bus.
2.With the utility - qdbusxml2cpp , you generate the interface adaptor code.
This interface adaptor does the demarshalling of the data that is
received from the client. After demarshalling, it invokes the
user defined - custom methods ( we can say as adaptee).
3. At the client side, we generate the interface from the xml file.
This interface is invoked by the client. The interface does the
marshalling of the data and invokes the adaptor interface. As told
in the point number 2, the adaptor interface does the demarshalling
and calls the adaptee - user defined methods.
您可以在此处查看 Qt-Dbus 的完整示例 -
http://www.tune2wizard.com/linux-qt-signals-and-slots-qt-d-bus/
当您有一个无法更改但需要使用的接口时,请使用适配器。把它看成你是办公室里的新人,你不能让白发苍苍的人遵守你的规则——你必须适应他们的规则。这是一个真实的例子,来自我曾经参与过的一个真实项目,其中用户界面是给定的。
您有一个应用程序将文件中的所有行读入 List 数据结构并将它们显示在网格中(我们将其称为底层数据存储接口 IDataStore)。用户可以通过单击“首页”、“上一页”、“下一页”、“最后一页”按钮来浏览这些数据。一切正常。
现在应用程序需要与生产日志一起使用,这些日志太大而无法读入内存,但用户仍然需要浏览它!一种解决方案是实现一个缓存来存储第一页、下一页、上一页和最后一页。我们想要的是当用户点击“下一页”时,我们从缓存中返回页面并更新缓存;当他们点击最后一页时,我们从缓存中返回最后一页。在后台,我们有一个文件流在做所有的事情。通过这样做,我们在内存中只有四页,而不是整个文件。
您可以使用适配器将这个新的缓存功能添加到您的应用程序中,而用户不会注意到它。我们扩展当前的 IDataStore 并将其称为 CacheDataStore。如果要加载的文件很大,我们使用 CacheDataStore。当我们请求第一页、下一页、上一页和最后一页时,信息将被路由到我们的缓存。
谁知道呢,明天老板要开始从数据库表中读取文件。您所做的仍然是像对缓存所做的那样将 IDataStore 扩展到 SQLDataStore,在后台设置连接。当他们单击 Next page 时,您将生成必要的 sql 查询以从数据库中获取接下来的几百行。
本质上,应用程序的原始界面没有改变。我们简单地调整了现代和酷炫的功能来工作,同时保留旧界面。
@Justice o 的示例没有清楚地讨论适配器模式。扩展他的答案 - 我们有我们的消费者代码使用的现有接口 IDataStore,我们无法更改它。现在我们被要求使用 XYZ 库中的一个很酷的新类来执行我们想要实现的功能,但是,但是,我们无法更改该类来扩展我们的 IDataStore,已经看到问题了吗?创建一个新类 - ADAPTER,它实现了我们的消费者代码期望的接口,即 IDataStore,并通过使用我们需要具有其特性的库中的类 - ADAPTEE,作为我们 ADAPTER 中的成员,我们可以实现我们想要的。
根据 Judith Bishop 的“C# 3.0 设计模式”一书,Apple 使用适配器模式来调整 Mac OS 以与 Intel 产品一起工作(在第 4 章中进行了解释,此处摘录2)
Yii 框架的一个例子是:Yii 通过接口 ICache 使用内部缓存。 https://www.yiiframework.com/doc/api/1.1/ICache
其签名如下:-
abstract public boolean set(string $id, mixed $value, integer $expire=0, ICacheDependency $dependency=NULL)
abstract public mixed get(string $id)
假设你想在 Yii 项目中使用 symfony 缓存库 https://packagist.org/packages/symfony/cache和它的缓存接口,通过在 Yii 服务组件(服务定位器)配置 https:// /github.com/symfony/cache-contracts/blob/master/CacheInterface.php
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null);
我们看到,symfony 缓存有一个只有一个 get 方法的接口,缺少一个 set 方法和一个不同的 get 方法签名,因为 Symfony 在提供第二个可调用参数时也使用 get 方法作为 setter。
由于 Yii 核心在内部使用这个 Yii 缓存/接口,因此很难(扩展 Yii/YiiBase)如果不是不可能的话,重写对该接口的调用。
另外 Symfony 缓存也不是我们的类,所以我们不能重写它的接口来适应 Yii 缓存接口。
所以这里来拯救适配器模式。我们将编写一个映射 = 一个中间适配器,它将 Yii 缓存接口调用映射到 Symfony 缓存接口
看起来像这样
class YiiToSymfonyCacheAdapter implements \Yii\system\caching\ICache
{
private \Symfony\Contracts\Cache\CacheInterface $symfonyCache;
public function __construct(\Symfony\Contracts\Cache\CacheInterface $symfonyCache)
{
$this->symfonyCache = $symfonyCache;
}
public boolean set(string $id, mixed $value, integer $expire=0, ICacheDependency
$dependency=NULL)
{
// https://symfony.com/doc/current/cache.html
return $this->symfonyCache->get(
$id,
function($item) {
// some logic ..
return $value;
}
);
// https://github.com/symfony/cache/blob/master/Adapter/MemcachedAdapter.php
// if a class could be called statically, the adapter could call statically also eg. like this
// return \Symfony\Component\Cache\Adapter\MemcacheAdapter::get(
// $id,
// function($item) {
// // some logic ..
// return $value;
// }
);
}
public mixed get(string $id)
{
// https://github.com/symfony/cache/blob/master/Adapter/FilesystemAdapter.php
// if a class could be called statically, the adapter could call statically also eg. like this
// \Symfony\Component\Cache\Adapter\FileSystemAdapter::get($id)
return $this->symfonyCache->get($id)
}
}
一个真实的例子可以是应用程序中的报告文档。简单的代码就像这里。
我认为适配器对于编程结构非常有用。
class WordAdaptee implements IReport{
public void report(String s) {
System.out.println(s +" Word");
}
}
class ExcellAdaptee implements IReport{
public void report(String s) {
System.out.println(s +" Excel");
}
}
class ReportAdapter implements IReport{
WordAdaptee wordAdaptee=new WordAdaptee();
@Override
public void report(String s) {
wordAdaptee.report(s);
}
}
interface IReport {
public void report(String s);
}
public class Main {
public static void main(String[] args) {
//create the interface that client wants
IReport iReport=new ReportAdapter();
//we want to write a report both from excel and world
iReport.report("Trial report1 with one adaptee"); //we can directly write the report if one adaptee is avaliable
//assume there are N adaptees so it is like in our example
IReport[] iReport2={new ExcellAdaptee(),new WordAdaptee()};
//here we can use Polymorphism here
for (int i = 0; i < iReport2.length; i++) {
iReport2[i].report("Trial report 2");
}
}
}
结果将是:
Trial report1 with one adaptee Word
Trial report 2 Excel
Trial report 2 Word
这是适配器实现的示例:
interface NokiaInterface {
chargementNokia(x:boolean):void
}
class SamsungAdapter implements NokiaInterface {
//nokia chargement adapted to samsung
chargementNokia(x:boolean){
const old= new SamsungCharger();
let y:number = x ? 20 : 1;
old.charge(y);
}
}
class SamsungCharger {
charge(x:number){
console.log("chrgement x ==>", x);
}
}
function main() {
//charge samsung with nokia charger
const adapter = new SamsungAdapter();
adapter.chargementNokia(true);
}