适配器-适配器模式的任何真实示例[关闭]

Posted

技术标签:

【中文标题】适配器-适配器模式的任何真实示例[关闭]【英文标题】:adapter-Any real example of Adapter Pattern [closed] 【发布时间】:2012-06-20 05:33:13 【问题描述】:

我想向我的团队演示 Adapter Pattern 的用法。我在网上阅读了很多书籍和文章。每个人都在举一个有助于理解概念的例子(形状、存储卡、电子适配器等),但没有真正的案例研究。

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

附言我尝试在 *** 上搜索现有问题,但没有找到答案,因此将其作为新问题发布。如果您知道已经有答案,请重定向。

【问题讨论】:

如果你想演示它。您应该在您的环境中有一个现成的示例,实际上是几个。否则你为什么要演示它? 这里有几个例子。 ***.com/questions/1673841/… @TonyHopkinson 旨在通过真实示例让人们了解这种设计模式。 @AksharRoop。设计模式旨在解决问题,而不是寻找问题的解决方案。最好的例子是你自己的“世界”中的一个。 @TonyHopkinson 我可能在这里使用了不正确的术语演示,但我的意思是用很好的例子来解释这种模式的概念。我同意我应该在我自己的系统中找到那个... 【参考方案1】:

适配器的许多示例都是微不足道的或不切实际的(Rectangle vs. LegacyRectangle, Ratchet vs. Socket、SquarePeg vs RoundPeg、Duck vs. Turkey)。更糟糕的是,许多没有显示针对不同 Adaptee 的多个适配器 (someone cited Java's Arrays.asList as an example of the adapter pattern)。调整只有一个类的接口以与另一个类一起工作似乎是 GoF 适配器模式的一个弱示例。这种模式使用继承和多态性,因此人们希望有一个很好的例子来展示针对不同适配器的适配器的多种实现

我发现的最好的例子在Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development (3rd Edition)的第26章。以下图片来自该书的 FTP 站点上提供的讲师资料。

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

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

【讨论】:

这个使用会话的例子也很好(虽然我认为使用静态的实现并不完全正确):community.sitepoint.com/t/phpunit-testing-cookies-and-sessions/… 当然还有 PHP 中的实现:github.com/alex-moreno/DesignPatternsPHP/tree/master/Adapter【参考方案2】:

如何把法国人变成普通人……

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

【讨论】:

我是法国人,我觉得你不认为我是一个真实的人而受到侮辱。 (JK) @ZeroUltimax 我很确定这段代码不会在魁北克编译。 任何不了解适配器的编码器都可以轻松解决问题。适配器理论知识如何帮助节省时间或改进解决方案?使用特殊类而不是仅使用方法的最终目的是什么? 如果您无法控制界面并需要将您的某个类调整为第三方库怎么办?许多其他充分的理由不在此答案的范围内。 这是我遇到过的如何使用适配器模式的最有趣的——也可能是最平易近人的例子之一。【参考方案3】:

将一个接口转换为另一个接口。

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

为了连接电源,我们在世界各地都有不同的接口。 使用适配器,我们可以很容易地进行连接。

【讨论】:

这里有一些对应的代码:codeproject.com/Tips/595716/Adapter-Design-Pattern-in-Cplusplus 这是另一个:vogella.com/tutorials/DesignPatternAdapter/article.html【参考方案4】:

这是一个模拟将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()));
    


依赖 - 通过 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>

如何测试

只需运行单元测试。

【讨论】:

【参考方案5】:

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

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

Youtube - Adapter Design pattern: Introduction

【讨论】:

【参考方案6】:

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

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

Adapters 然后将实现 Target 接口,将其方法调用委托给传递给 AdaptersAdapters 的 Adaptees strong> 通过构造函数。

为了让您在 Java 代码中实现这一点,我编写了一个非常简单的项目,使用与上面提到的完全相同的示例,使用适配器来处理多个智能电视接口。代码很小,有据可查且一目了然,因此请深入研究它以了解实际实现的样子。

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

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

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

当您有一个无法更改但需要使用的接口时,请使用适配器。把它看成你是办公室里的新人,你不能让白发苍苍的人遵守你的规则——你必须适应他们的规则。这是一个真实的例子,来自我曾经参与过的一个真实项目,其中用户界面是给定的。

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

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

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

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

本质上,应用程序的原始界面没有改变。我们只是在保留旧界面的同时调整了现代和酷炫的功能来运行它。

【讨论】:

我不明白?听起来您只是使用了现有接口并实现了方法?你需要适应的不同接口和适配器类在哪里? @berimbolo 您的困惑是有效的,因为上面的示例没有清楚地讨论适配器模式。【参考方案10】:

您可以在此处找到用于防御注入攻击的适配器模式的 PHP 实现:

http://www.php5dp.com/category/design-patterns/adapter-composition/

适配器模式的一个有趣方面是它有两种形式:依赖于多重继承的类适配器和依赖于组合的对象适配器。上面的例子依赖于组合。

【讨论】:

链接php5dp.com/category/design-patterns/adapter-composition 失效了【参考方案11】:

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

【讨论】:

【参考方案12】:

根据 Judith Bishop 的“C# 3.0 设计模式”一书,Apple 使用适配器模式来调整 Mac OS 以与 Intel 产品一起使用(在第 4 章中进行了解释,摘录在这里2)

C# 3.0 Design Patterns Structural Patterns: Adapter and Façade

【讨论】:

【参考方案13】:

Yii 框架的一个例子是:Yii 通过接口使用内部缓存 缓存。 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) 
       
     

【讨论】:

【参考方案14】:

一个真实的例子可以是应用程序中的报告文档。简单的代码就像这里。

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

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

【讨论】:

这实际上是一个代理。适配器和被适配者有不同的接口。他们没有实现相同的接口。这就是代理的作用。 这不是适配器模式。适配器模式用于实现适配器未实现的目标接口。【参考方案15】:

这是一个适配器实现的例子:

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

【讨论】:

以上是关于适配器-适配器模式的任何真实示例[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之适配器模式

JAVAEE高级工程师就业教程之代理模式.适配器模式.策略模式.观察者模式

设计模式—适配器模式

设计模式-适配器模式

设计模式-适配器模式

设计模式学习笔记------适配器模式