Je souhaite démontrer l'utilisation de Adapter Pattern à mon équipe. J'ai lu beaucoup de livres et d'articles en ligne. Tout le monde cite un exemple utile pour comprendre le concept (forme, carte mémoire, adaptateur électronique, etc.), mais il n’ya pas d’étude de cas réelle.
Pouvez-vous partager des études de cas sur Adapter Pattern?
p.s. J'ai essayé de chercher des questions existantes sur stackoverflow, mais je n'ai pas trouvé la réponse, donc je l'ai affichée comme une nouvelle question. Si vous savez qu'il existe déjà une réponse à cette question, veuillez rediriger vos actions.
De nombreux exemples d'adaptateur sont triviaux ou irréalistes ( Rectangle vs LegacyRectangle, Ratchet vs Socket , SquarePeg vs RoundPeg , Duck vs. Turquie ). Pire, beaucoup ne montrent pas plusieurs adaptateurs pour différents Adaptees ( quelqu'un a cité Arrays.asList de Java comme exemple de modèle d'adaptateur ). Adapter une interface de une seule classe pour travailler avec une autre semble un exemple faible du modèle d'adaptateur GoF. Ce modèle utilise l'héritage et le polymorphisme. On pourrait donc s'attendre à ce qu'un bon exemple montre plusieurs implémentations d'adaptateurs pour différents types d'adaptés.
Le meilleur exemple que j'ai trouvé se trouve au chapitre 26 de Application de UML et de modèles: introduction à l'analyse orientée objet, à la conception et au développement itératif (3ème édition) . Les images suivantes proviennent du matériel de l'instructeur fourni sur un site FTP pour le livre.
La première montre comment une application peut utiliser plusieurs implémentations (adaptables) fonctionnellement similaires (par exemple, des calculatrices fiscales, des modules de comptabilité, des services d'autorisation de crédit, etc.), mais ayant des API différentes. Nous voulons éviter de coder en dur notre code de couche de domaine afin de gérer les différentes manières possibles de calculer les taxes, d’afficher les ventes, d’autoriser les demandes de carte de crédit, etc. Ce sont tous des modules externes qui peuvent varier et pour lesquels nous ne pouvons pas modifier la configuration. code. L'adaptateur nous permet d'effectuer le codage en dur dans l'adaptateur, alors que notre code de couche de domaine utilise toujours la même interface (l'interface IWewiseAdapter).
Nous ne voyons pas dans la figure ci-dessus les adaptants réels. Cependant, la figure suivante montre comment un appel polymorphe à postSale(...)
dans l'interface IAccountingAdapter est généré, ce qui entraîne l'enregistrement de la vente via SOAP vers un système SAP.
Convertir une interface en une autre interface.
N'importe quel exemple réel de motif d'adaptateur
Afin de connecter l’alimentation, nous avons différentes interfaces dans le monde entier . En utilisant l’adaptateur, nous pouvons nous connecter facilement.
Comment transformer une personne française en une personne normale ...
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; }
}
}
Exemple
var service = new PersonService();
var person = new Person();
var frenchPerson = new FrenchPerson();
service.PrintName(person);
service.PrintName(new FrenchPersonAdapter(frenchPerson));
Voici un exemple qui simule la conversion de analog data
en digit data
.
Il fournit un adaptateur qui convertit les données à virgule flottante en données binaires, ce n’est probablement pas utile dans le monde réel, cela aide simplement à expliquer le concept de modèle d’adaptateur.
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>
Il suffit de lancer le test unitaire.
Le modèle d’adaptateur fonctionne comme un pont entre deux interfaces incompatibles . Ce modèle implique une seule classe appelée adaptateur, qui est responsable de la communication entre deux personnes indépendantes ou incompatibles interfaces.
Des exemples concrets pourraient être un traducteur de langue ou un chargeur de téléphone portable. Plus ici dans cette vidéo youtube:
Youtube - Modèle de conception de l'adaptateur: Introduction
Un exemple réel est Qt-Dbus.
Le qt-dbus dispose d'un utilitaire permettant de générer l'adaptateur et le code d'interface à partir du fichier XML fourni. Voici les étapes à suivre.
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.
Vous pouvez voir l'exemple complet de Qt-Dbus ici -
http://www.tune2wizard.com/linux-qt-signals-and-slots-qt-d-bus/
Vous pouvez trouver une implémentation PHP du modèle Adapter utilisé comme défense contre les attaques par injection ici:
http://www.php5dp.com/category/design-patterns/adapter-composition/
L'un des aspects intéressants du modèle d'adaptateur est qu'il existe en deux versions: un adaptateur de classe reposant sur plusieurs héritages et un adaptateur d'objets reposant sur la composition. L'exemple ci-dessus repose sur la composition.
Vous pouvez utiliser le modèle de conception de l'adaptateur lorsque vous devez traiter différentes interfaces ayant un comportement similaire (ce qui signifie généralement des classes ayant un comportement similaire mais avec des méthodes différentes). Un exemple de cela serait une classe pour se connecter à un téléviseur Samsung et une autre pour se connecter à un téléviseur Sony. Ils partageront des comportements communs comme ouvrir un menu, démarrer la lecture, se connecter à un réseau, etc., mais chaque bibliothèque aura une implémentation différente (avec des noms de méthode et des signatures différents). Ces différentes implémentations spécifiques au fournisseur sont appelées Adaptee dans les diagrammes UML.
Ainsi, dans votre code (appelé Client dans les diagrammes UML), au lieu de coder en dur les appels de méthode de chaque fournisseur (ou Adaptee), vous pouvez ensuite créer une interface générique (appelée Target dans les diagrammes UML) pour encapsuler ces comportements similaires et travailler avec un seul type d'objet.
Les Adapters implémenteront ensuite l'interface Target en déléguant leurs appels de méthode aux Adaptees qui sont transmis au Adapters via le constructeur.
Pour que vous puissiez réaliser cela en code Java, j’ai écrit un projet très simple utilisant exactement le même exemple que celui mentionné ci-dessus, qui utilise des adaptateurs pour gérer plusieurs interfaces de télévision intelligente. Le code est petit, bien documenté et explicite, alors expliquez-le pour voir à quoi ressemblerait une implémentation dans le monde réel.
Il suffit de télécharger le code et de l'importer dans Eclipse (ou votre IDE préféré) en tant que projet Maven. Vous pouvez exécuter le code en exécutant org.example.Main.Java. N'oubliez pas que l'important est de comprendre comment les classes et les interfaces sont assemblées pour concevoir le motif. J'ai également créé de faux Adaptees dans le package com.thirdparty.libs. J'espère que ça aide!
Un exemple concret peut être le reporting de documents dans une application. Code simple comme ici.
Je pense que les adaptateurs sont très utiles pour la structure de programmation.
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");
}
}
}
Les résultats seront:
Trial report1 with one adaptee Word
Trial report 2 Excel
Trial report 2 Word
Il existe de nombreux exemples concrets de modèles de desing adaptateur
Comme un adaptateur chargeur/mobile, un réducteur de conduite d'eau, un traducteur de langue, etc. Pour plus de détails, visitez le site modèle de conception de l'adaptateur en Java
Comme indiqué dans le livre «Modèles de conception C # 3.0» de Judith Bishop, Apple a utilisé le modèle d’adaptateur pour adapter Mac OS à l’utilisation des produits Intel (voir Chapitre 4, extrait ici 2 )
Utilisez Adapter lorsque vous avez une interface que vous ne pouvez pas modifier, mais que vous devez utiliser. Voyez-le comme vous êtes le nouveau type dans un bureau et vous ne pouvez pas obliger les cheveux gris à suivre vos règles - vous devez vous adapter aux leurs. Voici un exemple réel d'un projet réel sur lequel j'ai travaillé parfois, où l'interface utilisateur est un donné.
Vous avez une application qui lit toutes les lignes d'un fichier dans une structure de données List et les affiche dans une grille (appelons l'interface de magasin de données sous-jacente IDataStore). L'utilisateur peut naviguer dans ces données en cliquant sur les boutons "Première page", "Page précédente", "Page suivante", "Dernière page". Tout fonctionne bien.
L'application doit maintenant être utilisée avec des journaux de production qui sont trop volumineux pour être lus en mémoire, mais l'utilisateur doit toujours naviguer à travers! Une solution consisterait à implémenter un cache qui stocke la première page, les pages suivantes, précédentes et précédentes. Ce que nous voulons, c'est que lorsque l'utilisateur clique sur "Page suivante", nous retournons la page à partir du cache et mettons à jour le cache. quand ils cliquent sur la dernière page, nous retournons la dernière page du cache. En arrière-plan, nous avons un flux de fichiers qui fait toute la magie. Ce faisant, nous n’avons que quatre pages en mémoire, par opposition à l’ensemble du fichier.
Vous pouvez utiliser un adaptateur pour ajouter cette nouvelle fonctionnalité de cache à votre application sans que l'utilisateur ne s'en aperçoive. Nous étendons l'IDataStore actuel et l'appelons CacheDataStore. Si le fichier à charger est volumineux, nous utilisons CacheDataStore. Lorsque nous faisons une demande pour les pages Première, Suivante, Précédente et Dernière, les informations sont acheminées vers notre cache.
Et qui sait, demain le patron veut commencer à lire les fichiers depuis une table de base de données. Il ne vous reste plus qu'à étendre IDataStore à SQLDataStore comme vous l'avez fait pour le cache, configurez la connexion en arrière-plan. Lorsqu'ils cliquent sur Page suivante, vous générez la requête SQL nécessaire pour extraire les quelques cent prochaines lignes de la base de données.
Essentiellement, l'interface d'origine de l'application n'a pas changé. Nous avons simplement adapté des fonctionnalités modernes et tendance à son utilisation, tout en préservant l'interface existante.
L'exemple de @Justice o ne parle pas clairement du modèle d'adaptateur. Étendre sa réponse - Nous avons l'interface IDataStore existante que notre code consommateur utilise et nous ne pouvons pas le changer. Nous sommes maintenant invités à utiliser une nouvelle classe de la bibliothèque XYZ qui fait ce que nous voulons implémenter, mais nous ne pouvons pas changer cette classe pour étendre notre IDataStore, vous avez déjà vu le problème? Création d’une nouvelle classe - ADAPTER, qui implémente l’interface attendue par notre code consommateur, c’est-à-dire IDataStore et en utilisant la classe de la bibliothèque dont nous avons besoin - ADAPTEE, en tant que membre de notre ADAPTER, nous pouvons réaliser ce que nous voulions.