任何适配器模式的实际例子

我想向我的团队演示如何使用 适配器模式。我在网上读了很多书和文章。每个人都在引用一个有助于理解这个概念的例子(形状,存储卡,电子适配器等) ,但是没有真正的案例研究。

你能分享任何适配器模式的案例研究吗?

另外,我试着在 stackoverflow 上搜索现有的问题,但是没有找到答案,所以把它作为一个新问题贴出来。如果你知道这个问题已经有答案了,那么请重新提问。

67444 次浏览

Many examples of Adapter are trivial or unrealistic (Rectangle vs. LegacyRectangle, Ratchet vs. Socket, SquarePeg vs RoundPeg, Duck vs. Turkey). Worse, many don't show multiple Adapters for different Adaptees (someone cited Java's Arrays.asList as an example of the adapter pattern). Adapting an interface of only one class to work with another seems a weak example of the GoF Adapter pattern. This pattern uses inheritance and polymorphism, so one would expect a good example to show multiple implementations of adapters for different adaptees.

The best example I found is in Chapter 26 of Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development (3rd Edition). The following images are from the instructor material provided on an FTP site for the book.

The first one shows how an application can use multiple implementations (adaptees) that are functionally similar (e.g., tax calculators, accounting modules, credit authorization services, etc.) but have different APIs. We want to avoid hard-coding our domain-layer code to handle the different possible ways to calculate tax, post sales, authorize credit card requests, etc. Those are all external modules that might vary, and for which we can't modify the code. The adapter allows us to do the hard-coding in the adapter, whereas our domain-layer code always uses the same interface (the IWhateverAdapter interface).

Fig. 26.1

We don't see in the above figure the actual adaptees. However, the following figure shows how a polymorphic call to postSale(...) in the IAccountingAdapter interface is made, which results in a posting of the sale via SOAP to an SAP system.

Fig. 26.2

Adapter pattern works as a bridge between two incompatible interfaces. This pattern involves a single class called adapter which is responsible for communication between two independent or incompatible interfaces.

Real-world examples might be a language translator or a mobile charger. More here in this youtube video:

Youtube - Adapter Design pattern: Introduction

You can find a PHP implementation of the Adapter pattern used as a defense against injection attacks here:

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

One of the interesting aspects of the Adapter pattern is that it comes in two flavors: A class adapter relying on multiple inheritance and an object adapter relying on composition. The above example relies on composition.

A real example can be reporting documents in an application. Simple code as here.

Adapters i think are very useful for programming structure.

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

Results will be:

Trial report1 with one adaptee Word
Trial report 2 Excel
Trial report 2 Word

One Real example is Qt-Dbus.

The qt-dbus has a utility to generate the adaptor and interface code from the xml file provided. Here are the steps to do so.

 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.

You can see the complete example of Qt-Dbus over here -

http://www.tune2wizard.com/linux-qt-signals-and-slots-qt-d-bus/

Convert an Interface into another Interface.

Any real example of Adapter Pattern

In order to connect power, we have different interfaces all over the world. Using Adapter we can connect easily like wise.

enter image description here

How to turn a french person into a normal person...

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

Example

    var service = new PersonService();
var person = new Person();
var frenchPerson = new FrenchPerson();


service.PrintName(person);
service.PrintName(new FrenchPersonAdapter(frenchPerson));

Use Adapter when you have an interface you cannot change, but which you need to use. See it as you're the new guy in an office and you can't make the gray-hairs follow your rules - you must adapt to theirs. Here is a real example from a real project I worked on sometime where the user interface is a given.

You have an application that read all the lines in a file into a List data structure and displayed them in a grid (let's call the underlying data store interface IDataStore). The user can navigate through these data by clicking the buttons "First page", "Previous page", "Next page", "Last Page". Everything works fine.

Now the application needs to be used with production logs which are too big to read into memory but the user still needs to navigate through it! One solution would be to implement a Cache that stores the first page, next, previous and last pages. What we want is when the user clicks "Next page", we return the page from the cache and update the cache; when they click last page, we return last page from cache. In the background we have a filestream doing all the magic. By so doing we only have four pages in memory as opposed to the entire file.

You can use an adapter to add this new cache feature to your application without the user noticing it. We extend the current IDataStore and call it CacheDataStore. If the file to load is big, we use CacheDataStore. When we make a request for First, Next, Previous and Last pages, the information is routed to our Cache.

And who knows, tomorrow the boss wants to start reading the files from a database table. All you do is still extend IDataStore to SQLDataStore as you did for Cache, setup the connection in the background. When they click Next page, you generate the necessary sql query to fetch the next couple hundred rows from the database.

Essentially, the original interface of the application did not change. We simply adapted modern and cool features to work it while preserving the legacy interface.

Here is an example that simulates converting analog data to digit data.

It provides an adapter that converts float digit data to binary data, it's probably not useful in real world, it just helps to explain the concept of adapter pattern.


Code

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

Code - Test case

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

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

How to test

Just run the unit test.

@Justice o's example does not talk about adapter pattern clearly. Extending his answer - We have existing interface IDataStore that our consumer code uses and we cannot change it. Now we are asked to use a cool new class from XYZ library that does what we want to implement, but but but, we cannot change that class to extend our IDataStore, seen the problem already ? Creating a new class - ADAPTER, that implements interface our consumer code expects, i.e. IDataStore and by using class from the library whose features we need to have - ADAPTEE, as a member in our ADAPTER, we can achieve what we wanted to.

This is an example of adapter implementation:

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

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

As per “C# 3.0 Design Patterns” book by Judith Bishop, Apple used Adapter pattern to adapt Mac OS to work with Intel products (explained in Chapter # 4, excerpt here2)

Adapter design patterns helps in converting interface of one class into interface of client expects.

You have a service which returns weather (in celsius) by passing city name as a input value. Now, assume that your client wants to pass zipcode as input and expecting the temperature of the city in return. Here you need an adaptor to achieve this.

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

An example from Yii framework would be: Yii uses internally cache utilizing an interface ICache. https://www.yiiframework.com/doc/api/1.1/ICache

whose signature is like : -

abstract public boolean set(string $id, mixed $value, integer $expire=0, ICacheDependency $dependency=NULL)
abstract public mixed get(string $id)

Let's say, you would like to use inside a Yii project the symfony cache library https://packagist.org/packages/symfony/cache with it's cache interface, by defining this service in Yii services components (service locator) configuration https://github.com/symfony/cache-contracts/blob/master/CacheInterface.php

    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null);

We see, symfony cache has an interface with only a get method, missing a set method and a different signature for a get method, as Symfony uses the get method also as a setter when supplying the second callable parameter.

As Yii core internally uses this Yii cache/interface, it's difficult (extending Yii/YiiBase) if not impossible at places , to rewrite the calls to that interface.

Plus Symfony cache is nor our class, so we can't rewrite it's interface to fit with the Yii cache interface.

So here comes the adapter pattern to rescue. We will write a mapping = an intermediate adapter which will map the Yii cache interface calls to Symfony cache interface

Would look like this

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