En savoir plus sur le problème
Tout d'abord, nous allons simuler le comportement de l'ancien système. Supposons qu'il génère des excuses pour être en retard au travail ou à l'école. Pour ce faire, il a uneExcuse
interface qui a generateExcuse()
, likeExcuse()
et dislikeExcuse()
des méthodes.
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
La WorkExcuse
classe implémente cette interface :
public class WorkExcuse implements Excuse {
private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
"the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
"my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};
@Override
public String generateExcuse() { // Randomly select an excuse from the array
String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
apologies[(int) Math.round(Math.random() + 1)];
return result;
}
@Override
public void likeExcuse(String excuse) {
// Duplicate the element in the array so that its chances of being chosen are higher
}
@Override
public void dislikeExcuse(String excuse) {
// Remove the item from the array
}
}
Testons notre exemple :
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Sortir:
"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Imaginez maintenant que vous avez lancé un service générateur d'excuses, collecté des statistiques et remarqué que la plupart de vos utilisateurs sont des étudiants universitaires. Pour mieux servir ce groupe, vous avez demandé à un autre développeur de créer un système qui génère des excuses spécifiquement pour les étudiants universitaires. L'équipe de développement a mené des études de marché, classé les excuses, connecté une intelligence artificielle et intégré le service avec des rapports de trafic, des bulletins météorologiques, etc. Vous avez maintenant une bibliothèque pour générer des excuses pour les étudiants universitaires, mais elle a une interface différente : StudentExcuse
.
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
Cette interface a deux méthodes : generateExcuse
, qui génère une excuse, et dislikeExcuse
, qui empêche l'excuse de réapparaître à l'avenir. La bibliothèque tierce ne peut pas être modifiée, c'est-à-dire que vous ne pouvez pas modifier son code source. Nous avons maintenant un système avec deux classes qui implémentent l' Excuse
interface, et une bibliothèque avec une SuperStudentExcuse
classe qui implémente l' StudentExcuse
interface :
public class SuperStudentExcuse implements StudentExcuse {
@Override
public String generateExcuse() {
// Logic for the new functionality
return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
}
@Override
public void dislikeExcuse(String excuse) {
// Adds the reason to a blacklist
}
}
Le code ne peut pas être modifié. La hiérarchie actuelle des classes ressemble à ceci : Cette version du système ne fonctionne qu'avec l'interface Excuse. Vous ne pouvez pas réécrire le code : dans une application volumineuse, effectuer de telles modifications pourrait devenir un processus long ou casser la logique de l'application. Nous pourrions introduire une interface de base et étendre la hiérarchie : Pour ce faire, nous devons renommer l' Excuse
interface. Mais la hiérarchie supplémentaire n'est pas souhaitable dans les applications sérieuses : l'introduction d'un élément racine commun casse l'architecture. Vous devez implémenter une classe intermédiaire qui nous permettra d'utiliser à la fois la nouvelle et l'ancienne fonctionnalité avec un minimum de pertes. Bref, il vous faut un adaptateur .
Le principe du modèle d'adaptateur
Un adaptateur est un objet intermédiaire qui permet aux appels de méthode d'un objet d'être compris par un autre. Implémentons un adaptateur pour notre exemple et appelons-leMiddleware
. Notre adaptateur doit implémenter une interface compatible avec l'un des objets. Laissez faire Excuse
. Cela permet Middleware
d'appeler les méthodes du premier objet. Middleware
reçoit les appels et les renvoie de manière compatible vers le second objet. Voici l' Middleware
implémentation avec les méthodes generateExcuse
et dislikeExcuse
:
public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
}
@Override
public void dislikeExcuse(String excuse) {
// The method first adds the excuse to the blacklist,
// Then passes it to the dislikeExcuse method of the superStudentExcuse object.
}
// The likeExcuse method will appear later
}
Test (en code client) :
public class Test {
public static void main(String[] args) {
Excuse excuse = new WorkExcuse(); // We create objects of the classes
StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
System.out.println("An ordinary excuse for an employee:");
System.out.println(excuse.generateExcuse());
System.out.println("\n");
Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
System.out.println("Using new functionality with the adapter:");
System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
}
}
Sortir:
An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position.
Using new functionality with the adapter:
Une excuse incroyable adaptée aux conditions météorologiques actuelles, aux embouteillages ou aux retards dans les horaires des transports en commun. La generateExcuse
méthode passe simplement l'appel à un autre objet, sans aucune modification supplémentaire. La dislikeExcuse
méthode nous obligeait à mettre d'abord l'excuse sur liste noire. La possibilité d'effectuer un traitement de données intermédiaire est une raison pour laquelle les gens aiment le modèle d'adaptateur. Mais qu'en est-il de la likeExcuse
méthode, qui fait partie de l' Excuse
interface mais pas de l' StudentExcuse
interface ? La nouvelle fonctionnalité ne prend pas en charge cette opération. Le UnsupportedOperationException
a été inventé pour cette situation. Elle est levée si l'opération demandée n'est pas prise en charge. Utilisons-le. Voici à quoi Middleware
ressemble la nouvelle implémentation de la classe :
public class Middleware implements Excuse {
private StudentExcuse superStudentExcuse;
public Middleware(StudentExcuse excuse) {
this.superStudentExcuse = excuse;
}
@Override
public String generateExcuse() {
return superStudentExcuse.generateExcuse();
}
@Override
public void likeExcuse(String excuse) {
throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
}
@Override
public void dislikeExcuse(String excuse) {
// The method accesses a database to fetch additional information,
// and then passes it to the superStudentExcuse object's dislikeExcuse method.
}
}
À première vue, cette solution ne semble pas très bonne, mais imiter la fonctionnalité peut compliquer la situation. Si le client fait attention et que l'adaptateur est bien documenté, une telle solution est acceptable.
Quand utiliser un adaptateur
-
Lorsque vous devez utiliser une classe tierce, mais que son interface est incompatible avec l'application principale. L'exemple ci-dessus montre comment créer un objet adaptateur qui encapsule les appels dans un format qu'un objet cible peut comprendre.
-
Lorsque plusieurs sous-classes existantes ont besoin de fonctionnalités communes. Au lieu de créer des sous-classes supplémentaires (ce qui entraînera une duplication de code), il est préférable d'utiliser un adaptateur.
Avantages et inconvénients
Avantage : L'adaptateur cache au client les détails du traitement des requêtes d'un objet à un autre. Le code client ne pense pas au formatage des données ni à la gestion des appels à la méthode cible. C'est trop compliqué, et les programmeurs sont paresseux :) Inconvénient : La base de code du projet est compliquée par des classes supplémentaires. Si vous avez beaucoup d'interfaces incompatibles, le nombre de classes supplémentaires peut devenir ingérable.Ne confondez pas un adaptateur avec une façade ou un décorateur
Avec seulement une inspection superficielle, un adaptateur pourrait être confondu avec les motifs de façade et de décorateur. La différence entre un adaptateur et une façade est qu'une façade introduit une nouvelle interface et enveloppe l'ensemble du sous-système. Et un décorateur, contrairement à un adaptateur, modifie l'objet lui-même plutôt que l'interface.Algorithme pas à pas
-
Tout d'abord, assurez-vous que vous avez un problème que ce modèle peut résoudre.
-
Définissez l'interface client qui sera utilisée pour interagir indirectement avec des objets incompatibles.
-
Faites en sorte que la classe d'adaptateur hérite de l'interface définie à l'étape précédente.
-
Dans la classe adaptateur, créez un champ pour stocker une référence à l'objet adapté. Cette référence est transmise au constructeur.
-
Implémentez toutes les méthodes d'interface client dans l'adaptateur. Une méthode peut :
-
Passer les appels sans apporter de modifications
-
Modifier ou compléter des données, augmenter/diminuer le nombre d'appels à la méthode cible, etc.
-
Dans les cas extrêmes, si une méthode particulière reste incompatible, lancez une UnsupportedOperationException. Les opérations non prises en charge doivent être strictement documentées.
-
-
Si l'application utilise uniquement la classe d'adaptateur via l'interface client (comme dans l'exemple ci-dessus), l'adaptateur peut être étendu sans problème à l'avenir.
GO TO FULL VERSION