我想向我的团队演示适配器模式的使用。我已经在线阅读了许多书籍和文章。每个人都在举一个例子,它对理解概念(形状,存储卡,电子适配器等)很有用,但没有实际案例研究。
您能否分享一下适配器模式的任何案例研究?
ps我尝试搜索关于stackoverflow的现有问题,但没有找到答案,因此将其发布为新问题。如果您知道已经有了答案,请重定向。
我想向我的团队演示适配器模式的使用。我已经在线阅读了许多书籍和文章。每个人都在举一个例子,它对理解概念(形状,存储卡,电子适配器等)很有用,但没有实际案例研究。
您能否分享一下适配器模式的任何案例研究?
ps我尝试搜索关于stackoverflow的现有问题,但没有找到答案,因此将其发布为新问题。如果您知道已经有了答案,请重定向。
Answers:
Adapter的许多示例都是琐碎的或不切实际的(Rectangle vs. LegacyRectangle,Ratchet vs. Socket,SquarePeg vs RoundPeg,Duck vs. Turkey)。更糟糕的是,许多人并未针对不同的Adaptee显示多个Adapter(有人引用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; }
}
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
。
它提供了一个将浮点数字数据转换为二进制数据的适配器,在现实世界中可能没有用,它只是有助于解释适配器模式的概念。
AnalogSignal.java
package eric.designpattern.adapter;
public interface AnalogSignal {
float[] getAnalog();
void setAnalog(float[] analogData);
void printAnalog();
}
DigitSignal.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),而不是硬编码每个供应商(或Adaptee)的方法调用,然后可以创建一个通用接口(在UML图中称为Target)来包装这些相似的行为并工作只有一种类型的对象。
然后,适配器将实现Target接口,将其方法调用委派给通过构造函数传递给适配器的Adaptee。
为了用Java代码实现这一目标,我使用与适配器完全相同的示例编写了一个非常简单的项目,它使用适配器来处理多个智能电视接口。该代码小巧,文档齐全且易于解释,因此请对其进行深入研究,以了解实际的实现情况。
只需下载代码并将其作为Maven项目导入到Eclipse(或您最喜欢的IDE)即可。您可以通过运行org.example.Main.java来执行代码。请记住,这里重要的是要了解如何将类和接口组装在一起来设计模式。我还在com.thirdparty.libs包中创建了一些伪造的Adaptee。希望能帮助到你!
一个真实的例子是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/
您可以在此处找到用于防御注入攻击的Adapter模式的PHP实现:
http://www.php5dp.com/category/design-patterns/adapter-composition/
适配器模式有趣的方面之一是它有两种形式:依赖于多重继承的类适配器和依赖于组合的对象适配器。上面的示例依赖于组成。
适配器设计模式有助于将一类接口转换为客户期望的接口。
示例: 您有一项通过将城市名称作为输入值来返回天气(以摄氏度为单位)的服务。现在,假设您的客户希望传递邮政编码作为输入,并期望返回城市的温度。在这里,您需要一个适配器来实现此目的。
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";
}
}
一个真实的例子是在应用程序中报告文档。如此处的简单代码。
我认为适配器对于编程结构非常有用。
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
当您拥有无法更改但需要使用的接口时,请使用适配器。当您是办公室里的新手时就可以看到它,您不能让灰头发遵循您的规则-您必须适应他们的规则。这是我在给定用户界面的某个时间里工作的真实项目的真实示例。
您有一个应用程序,该应用程序将文件中的所有行读入List数据结构,并在网格中显示它们(我们称之为基础数据存储接口IDataStore)。用户可以通过单击“首页”,“上一页”,“下一页”,“最后一页”按钮浏览这些数据。一切正常。
现在,该应用程序需要用于生产日志,该日志太大而无法读入内存,但用户仍然需要浏览它!一种解决方案是实现存储第一页,下一页,上一页和最后一页的缓存。我们想要的是当用户单击“下一页”时,我们从缓存中返回页面并更新缓存;当他们单击最后一页时,我们从缓存中返回最后一页。在后台,我们有一个文件流可以完成所有工作。这样一来,我们在内存中只有四个页面,而不是整个文件。
您可以使用适配器将该新的缓存功能添加到您的应用程序中,而无需用户注意。我们扩展当前的IDataStore并将其称为CacheDataStore。如果要加载的文件很大,则使用CacheDataStore。当我们请求“首页”,“下一页”,“上一页”和“最后一页”时,信息将路由到我们的缓存中。
谁知道呢,明天老板希望开始从数据库表中读取文件。您所做的只是仍然像对Cache一样将IDataStore扩展到SQLDataStore,在后台设置连接。当他们单击“下一页”时,您将生成必要的sql查询,以从数据库中获取接下来的几百行。
本质上,该应用程序的原始接口没有更改。我们仅修改了现代和酷炫的功能即可使用它,同时保留了旧界面。
根据Judith Bishop撰写的“ C#3.0设计模式”一书,苹果使用适配器模式来使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方法用作设置方法。
由于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)
}
}
这是适配器实现的示例:
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);
}