adapter-任何真实的适配器模式示例


84

我想向我的团队演示适配器模式的使用。我已经在线阅读了许多书籍和文章。每个人都在举一个例子,它对理解概念(形状,存储卡,电子适配器等)很有用,但没有实际案例研究。

您能否分享一下适配器模式的任何案例研究?

ps我尝试搜索关于stackoverflow的现有问题,但没有找到答案,因此将其发布为新问题。如果您知道已经有了答案,请重定向。


4
好吧,如果您想演示它。您应该在您的环境中有一个现成的示例,实际上是几个示例。否则,为什么要演示呢?
托尼·霍普金森

1
这里有几个例子。stackoverflow.com/questions/1673841/...
R4。

1
@TonyHopkinson的目标是通过实际示例使人们意识到这种设计模式。
AksharRoop 2012年

10
@AksharRoop。设计模式旨在解决问题,而不是寻找问题的解决方案。最好的例子是您自己的“世界”中的一个。
托尼·霍普金森

1
@TonyHopkinson我在这里可能使用了不正确的术语进行演示,但是我的意思是用一个很好的例子来解释这种模式的概念。我同意我应该在自己的系统中找到一个...
AksharRoop 2012年

Answers:


77

Adapter的许多示例都是琐碎的或不切实际的(Rectangle vs. LegacyRectangle,Ratchet vs. SocketSquarePeg vs RoundPegDuck vs. Turkey)。更糟糕的是,许多人并未针对不同的Adaptee显示多个Adapter有人引用Java的Arrays.asList作为适配器模式的示例)。调整仅一个类的接口以与另一个一起使用似乎是GoF适配器模式的一个较弱的示例。这种模式使用继承和多态性,因此可以期望有一个很好的例子来展示针对不同适配器的适配器的多种实现

我发现的最好的示例是在“应用UML和模式:面向对象的分析与设计和迭代开发简介”(第3版)的第26章中。下图来自该书的FTP站点上提供的指导材料。

第一个展示了应用程序如何使用功能上相似(例如,税收计算器,会计模块,信用授权服务等)但具有不同API的多个实现(适配器)。我们希望避免对域层代码进行硬编码,以处理计算税款,售后,授权信用卡申请等的各种可能方式。所有这些都是可能有所不同的外部模块,因此我们无法对其进行修改码。适配器允许我们在适配器中进行硬编码,而我们的域层代码始终使用相同的接口(IWhateverAdapter接口)。

图26.1

在上图中我们看不到实际的适应者。但是,下图显示了如何postSale(...)在IAccountingAdapter接口中对进行多态调用,这导致通过SOAP将销售过帐到SAP系统。

图26.2


本例中使用会话倒也不错(虽然执行不完全正确的,我认为,使用静态):community.sitepoint.com/t/phpunit-testing-cookies-and-sessions/...
亚历杭德罗·莫雷诺


50

如何将法国人变成普通人...

 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));

19
我是法国人,我感到侮辱您不认为我是真实的人。(JK)
ZeroUltimax

13
@ZeroUltimax我很确定这段代码不会在魁北克编译。
Fuhrmanator

任何不了解适配器的编码器,都可以轻松解决此问题。适配器理论知识如何帮助您节省时间或使解决方案更好?使用特殊类而不是仅使用方法的最终目的是吗?
罗恩·贡捷

如果您不控制界面并且需要将您的一个课程适应第三方图书馆怎么办?还有许多其他好的原因,这些原因不在此答案的范围内。
CountZero

3
这是关于如何使用我曾经遇到的Adapter模式的最有趣的(也许也是最容易接近的)示例。
Mass Dot Net


13

这里是一个可模拟转换的例子analog datadigit 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()));
    }
}

依赖-通过Maven

    <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>

测试方法

只需运行单元测试。


7

适配器模式充当两个不兼容接口之间的桥梁。此模式涉及一个称为适配器的类,该类负责两个独立或不兼容的接口之间的通信。

现实世界中的示例可能是语言翻译器或移动充电器。这段youtube视频中的更多内容:

Youtube-适配器设计模式:简介


3

当您必须处理行为相似的不同接口(通常表示行为相似但方法不同的类)时,可以使用适配器设计模式。一个例子是一类连接到三星电视,另一个类连接到索尼电视。它们将共享常见的行为,例如打开菜单,开始播放,连接到网络等,但是每个库都将具有不同的实现方式(具有不同的方法名称和签名)。这些特定于供应商的不同实现在UML图中称为Adaptee

因此,在您的代码中(在UML图中称为Client),而不是硬编码每个供应商(或Adaptee)的方法调用,然后可以创建一个通用接口(在UML图中称为Target)来包装这些相似的行为并工作只有一种类型的对象。

然后,适配器将实现Target接口,将其方法调用委派给通过构造函数传递给适配器Adaptee

为了用Java代码实现这一目标,我使用与适配器完全相同的示例编写了一个非常简单的项目,它使用适配器来处理多个智能电视接口。该代码小巧,文档齐全且易于解释,因此请对其进行深入研究,以了解实际的实现情况。

只需下载代码并将其作为Maven项目导入到Eclipse(或您最喜欢的IDE)即可。您可以通过运行org.example.Main.java来执行代码。请记住,这里重要的是要了解如何将类和接口组装在一起来设计模式。我还在com.thirdparty.libs包中创建了一些伪造的Adaptee。希望能帮助到你!

https://github.com/Dannemann/java-design-patterns


2

一个真实的例子是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/



2

适配器设计模式有助于将一类接口转换为客户期望的接口。

示例: 您有一项通过将城市名称作为输入值来返回天气(以摄氏度为单位)的服务。现在,假设您的客户希望传递邮政编码作为输入,并期望返回城市的温度。在这里,您需要一个适配器来实现此目的。

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";
    }
}

0

一个真实的例子是在应用程序中报告文档。如此处的简单代码。

我认为适配器对于编程结构非常有用。

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

1
这实际上是一个代理。适配器和适配器具有不同的接口。他们没有实现相同的接口。这就是代理所做的。
dvallejo

这不是适配器模式。适配器模式用于实现适配器未实现的目标接口。
FedericoG

0

当您拥有无法更改但需要使用的接口时,请使用适配器。当您是办公室里的新手时就可以看到它,您不能让灰头发遵循您的规则-您必须适应他们的规则。这是我在给定用户界面的某个时间里工作的真实项目的真实示例。

您有一个应用程序,该应用程序将文件中的所有行读入List数据结构,并在网格中显示它们(我们称之为基础数据存储接口IDataStore)。用户可以通过单击“首页”,“上一页”,“下一页”,“最后一页”按钮浏览这些数据。一切正常。

现在,该应用程序需要用于生产日志,该日志太大而无法读入内存,但用户仍然需要浏览它!一种解决方案是实现存储第一页,下一页,上一页和最后一页的缓存。我们想要的是当用户单击“下一页”时,我们从缓存中返回页面并更新缓存;当他们单击最后一页时,我们从缓存中返回最后一页。在后台,我们有一个文件流可以完成所有工作。这样一来,我们在内存中只有四个页面,而不是整个文件。

您可以使用适配器将该新的缓存功能添加到您的应用程序中,而无需用户注意。我们扩展当前的IDataStore并将其称为CacheDataStore。如果要加载的文件很大,则使用CacheDataStore。当我们请求“首页”,“下一页”,“上一页”和“最后一页”时,信息将路由到我们的缓存中。

谁知道呢,明天老板希望开始从数据库表中读取文件。您所做的只是仍然像对Cache一样将IDataStore扩展到SQLDataStore,在后台设置连接。当他们单击“下一页”时,您将生成必要的sql查询,以从数据库中获取接下来的几百行。

本质上,该应用程序的原始接口没有更改。我们仅修改了现代和酷炫的功能即可使用它,同时保留了旧界面。


我不明白吗?听起来您刚刚使用了现有接口并实现了方法?您需要适应的接口和适配器类在哪里?
berimbolo

@berimbolo您的困惑是有效的,因为上面的示例并未清楚地讨论适配器模式。
rahil008

0

@Justice o的示例并未明确讨论适配器模式。扩展他的答案-我们已有我们的消费者代码使用的接口IDataStore,我们无法对其进行更改。现在,我们被要求使用XYZ库中一个很酷的新类来实现我们要实现的功能,但是但是,我们不能更改该类来扩展IDataStore,已经看到了问题吗?创建一个新类-ADAPTER,该类实现我们的消费者代码期望的接口,即IDataStore,并使用库中具有我们需要的功能的类-ADAPTEE,作为ADAPTER的成员,我们可以实现我们想要的。



0

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) 
       }
    } 


-1

这是适配器实现的示例:

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.