Salut! Aujourd'hui, nous aborderons un nouveau sujet important : les modèles de conception . Quels sont ces motifs ? Je pense que vous devez connaître l'expression " ne réinventez pas la roue ". En programmation, comme dans bien d'autres domaines, il existe un grand nombre de situations courantes. Au fur et à mesure que le développement de logiciels a évolué, des solutions prêtes à l'emploi qui fonctionnent ont été créées pour chacun d'entre eux. Ces solutions sont appelées patrons de conception. Par convention, un modèle est une solution formulée comme ceci : "si vous devez faire X dans votre programme, alors c'est la meilleure façon de le faire". Il existe de nombreux modèles. L'excellent livre "Head First Design Patterns", qu'il faut absolument connaître, leur est dédié. En termes concis, un modèle se compose d'un problème commun et d'une solution correspondante qui peut être considérée comme une sorte de norme. Dans la leçon d'aujourd'hui, nous rencontrerons l'un de ces modèles : Adaptateur. Son nom dit tout, et vous avez rencontré des adaptateurs à plusieurs reprises dans la vraie vie. Certains des adaptateurs les plus courants sont les lecteurs de cartes dont disposent de nombreux ordinateurs et ordinateurs portables. Supposons que nous ayons une sorte de carte mémoire. Donc quel est le problème? Il ne sait pas comment interagir avec l'ordinateur. Ils ne partagent pas une interface commune. L'ordinateur a un port USB, mais nous ne pouvons pas y insérer la carte mémoire. La carte ne peut pas être branchée à l'ordinateur, nous ne pouvons donc pas enregistrer nos photos, vidéos et autres données. Un lecteur de carte est un adaptateur qui résout ce problème. Après tout, il a un câble USB ! Contrairement à la carte elle-même, le lecteur de carte peut être branché sur l'ordinateur. Ils partagent une interface commune avec l'ordinateur : USB. Voyons à quoi cela ressemble en pratique:
public interface USB {
void connectWithUsbCable();
}
Il s'agit de notre interface USB avec une seule méthode de connexion via USB.
public class MemoryCard {
public void insert() {
System.out.println("Memory card successfully inserted!");
}
public void copyData() {
System.out.println("The data has been copied to the computer!");
}
}
Ceci est notre classe représentant la carte mémoire. Il a déjà les 2 méthodes dont nous avons besoin, mais voici le problème : il n'implémente pas l'interface USB. La carte ne peut pas être insérée dans le port USB.
public class CardReader implements USB {
private MemoryCard memoryCard;
public CardReader(MemoryCard memoryCard) {
this.memoryCard = memoryCard;
}
@Override
public void connectWithUsbCable() {
this.memoryCard.insert();
this.memoryCard.copyData();
}
}
Et voici notre adaptateur ! Que fait leCardReader
que fait la classe et qu'est-ce qui en fait exactement un adaptateur ? C'est tout simple. La classe en cours d'adaptation (MemoryCard) devient l'un des champs de l'adaptateur. C'est logique. Lorsque nous plaçons une carte mémoire dans un lecteur de carte dans la vraie vie, elle en fait également partie. Contrairement à la carte mémoire, l'adaptateur partage une interface avec l'ordinateur. Il dispose d'un câble USB, c'est-à-dire qu'il peut être connecté à d'autres appareils via USB. C'est pourquoi notre classe CardReader implémente l'interface USB. Mais que se passe-t-il exactement dans cette méthode ? Exactement ce dont nous avons besoin! L'adaptateur délègue le travail à notre carte mémoire. En effet, l'adaptateur ne fait rien lui-même. Un lecteur de carte n'a aucune fonctionnalité indépendante. Son travail consiste uniquement à connecter l'ordinateur et la carte mémoire afin de permettre à la carte de faire son travail - copier des fichiers !connectWithUsbCable()
méthode) pour répondre aux "besoins" de la carte mémoire. Créons un programme client qui simulera une personne souhaitant copier des données depuis une carte mémoire :
public class Main {
public static void main(String[] args) {
USB cardReader = new CardReader(new MemoryCard());
cardReader.connectWithUsbCable();
}
}
Alors qu'avons-nous obtenu? Sortie console :
Memory card successfully inserted!
The data has been copied to the computer!
Excellent. Nous avons atteint notre objectif ! Voici un lien vers une vidéo contenant des informations sur le modèle d'adaptateur :
Classes abstraites Reader et Writer
Nous allons maintenant revenir à notre activité préférée : apprendre quelques nouvelles classes pour travailler avec l'entrée et la sortie :) Je me demande combien nous en avons déjà appris. Aujourd'hui, nous allons parler des classesReader
et Writer
. Pourquoi précisément ces cours ? Parce qu'ils sont liés à notre section précédente sur les adaptateurs. Examinons-les plus en détail. Nous allons commencer par Reader
. Reader
est une classe abstraite, nous ne pourrons donc pas créer d'objets explicitement. Mais vous le connaissez déjà ! Après tout, vous connaissez bien les classes BufferedReader
et InputStreamReader
, qui en sont les descendantes :)
public class BufferedReader extends Reader {
…
}
public class InputStreamReader extends Reader {
…
}
La InputStreamReader
classe est un adaptateur classique. Comme vous vous en souvenez probablement, nous pouvons passer un InputStream
objet à son constructeur. Pour ce faire, nous utilisons généralement la System.in
variable :
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
Mais qu'est-ce que ça InputStreamReader
fait ? Comme tout adaptateur, il convertit une interface en une autre. Dans ce cas, l' InputStream
interface à l' Reader
interface. Au départ, nous avons la InputStream
classe. Cela fonctionne bien, mais vous ne pouvez l'utiliser que pour lire des octets individuels. De plus, nous avons une Reader
classe abstraite. Il a des fonctionnalités très utiles - il sait lire les caractères ! Nous avons certainement besoin de cette capacité. Mais ici, nous sommes confrontés au problème classique généralement résolu par les adaptateurs - les interfaces incompatibles. Qu'est-ce que cela signifie? Jetons un coup d'œil à la documentation d'Oracle. Voici les méthodes de la InputStream
classe. Un ensemble de méthodes est précisément ce qu'est une interface. Comme vous pouvez le voir, cette classe a unread()
méthode (quelques variantes, en fait), mais il ne peut lire que des octets : soit des octets individuels, soit plusieurs octets à l'aide d'un tampon. Mais cette option ne nous convient pas — nous voulons lire des caractères. Nous avons besoin de la fonctionnalité déjà implémentée dans la Reader
classe abstraite . Nous pouvons également le voir dans la documentation. Cependant, les interfaces InputStream
et Reader
sont incompatibles ! Comme vous pouvez le voir, chaque implémentation de la read()
méthode a des paramètres et des valeurs de retour différents. Et c'est là qu'il nous faut InputStreamReader
! Il servira d'adaptateur entre nos classes. Comme dans l'exemple avec le lecteur de carte, que nous avons considéré ci-dessus, nous plaçons une instance de la classe à adapter "à l'intérieur" de la classe adaptateur, c'est-à-dire que nous en passons une à son constructeur. Dans l'exemple précédent, nous avons mis un MemoryCard
objet à l'intérieur de CardReader
. Nous passons maintenant un InputStream
objet au InputStreamReader
constructeur ! Nous utilisons notre System.in
variable familière comme InputStream
:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
Et en effet, en regardant la documentation de InputStreamReader
, nous pouvons voir que l'adaptation a réussi :) Nous avons maintenant des méthodes pour lire les caractères à notre disposition. Et bien que notre System.in
objet (le flux lié au clavier) ne le permette pas initialement, les créateurs du langage ont résolu ce problème en implémentant le modèle d'adaptateur. La Reader
classe abstraite, comme la plupart des classes d'E/S, a un frère jumeau — Writer
. Il a le même grand avantage que Reader
- il fournit une interface pratique pour travailler avec des personnages. Avec les flux de sortie, le problème et sa solution ressemblent à ceux des flux d'entrée. Il y a une OutputStream
classe qui ne peut écrire que des octets, il y a unWriter
classe abstraite qui sait travailler avec des caractères, et il existe deux interfaces incompatibles. Ce problème est à nouveau résolu par le modèle d'adaptateur. Nous utilisons la OutputStreamWriter
classe pour adapter facilement les deux interfaces des classes Writer
et OutputStream
entre elles. Après avoir passé un OutputStream
flux d'octets au constructeur, nous pouvons utiliser an OutputStreamWriter
pour écrire des caractères plutôt que des octets !
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt"));
streamWriter.write(32144);
streamWriter.close();
}
}
Nous avons écrit le caractère avec le code 32144 (綐) dans notre fichier, éliminant ainsi le besoin de travailler avec des octets :) C'est tout pour aujourd'hui. A bientôt dans les prochains cours ! :)
GO TO FULL VERSION