"Salut l'ami!"

"Salut, Bilaabo !"

"Il nous reste encore du temps, alors je vais vous parler de trois autres modèles."

« Trois de plus ? Combien y en a-t-il en tout ?

"Il existe actuellement des dizaines de modèles populaires, mais le nombre de "solutions réussies" est illimité."

"Je vois. Alors je dois apprendre plusieurs dizaines de modèles ?"

"Tant que vous n'aurez pas une réelle expérience en programmation, ils ne vous donneront pas grand-chose."

"Vous feriez mieux d'acquérir un peu plus d'expérience, puis, dans un an, de revenir sur ce sujet et d'essayer de les comprendre plus en profondeur. Au moins quelques dizaines de modèles de conception les plus populaires."

"C'est un péché de ne pas utiliser l'expérience de quelqu'un d'autre et d'inventer quelque chose pour la 110e fois."

"Je suis d'accord."

"Alors commençons."

Modèle d'adaptateur (ou d'emballage)

Modèles : adaptateur, proxy, pont - 1

"Imaginez que vous veniez en Chine et que vous constatiez que les prises électriques suivent une norme différente. Les trous ne sont pas ronds, mais plats. Dans ce cas, vous aurez besoin d'un adaptateur."

"Quelque chose de similaire peut également se produire en programmation. Les classes fonctionnent sur des interfaces similaires mais différentes. Nous devons donc créer un adaptateur entre elles."

"Voilà à quoi ça ressemble :"

Exemple
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

"Supposons que nous ayons deux interfaces : Time  et  TotalTime ."

"L'interface Time vous permet d'obtenir l'heure actuelle à l'aide des méthodes getSeconds (),  getMinutes () et  getHours ()."

"L' interface TotalTime vous permet d'obtenir le nombre de secondes qui se sont écoulées entre minuit et le moment actuel."

"Que devons-nous faire si nous avons un objet TotalTime , mais nous avons besoin d'un objet Time ou vice versa ?"

"Nous pouvons écrire des classes d'adaptateur pour cela. Par exemple :"

Exemple
 class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 int getSeconds()
 {
  return totalTime.getTotalSeconds() % 60; // seconds
 }

 int getMinutes()
 {
  return totalTime.getTotalSeconds() / 60; // minutes
 }

 int getHours()
 {
  return totalTime.getTotalSeconds() / (60 * 60); // hours
 }
}
 
Usage
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

"Et un adaptateur dans l'autre sens :"

Exemple
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 int getTotalSeconds()
 {
  return time.getHours() * 60 * 60 + time.getMinutes() * 60 + time.getSeconds();
 }
}
Usage
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"Ah. J'aime ça. Mais y a-t-il des exemples ?"

"Bien sûr ! Par exemple, InputStreamReader est un adaptateur classique. Il convertit un InputStream en Reader."

"Parfois, ce modèle est également appelé wrapper, car la nouvelle classe "enveloppe" un autre objet."

"Vous pouvez lire d'autres choses intéressantes ici ."

Modèle de proxy

"Le modèle de proxy est quelque peu similaire au modèle wrapper. Mais son but n'est pas de convertir les interfaces, mais de contrôler l'accès à l'objet d'origine stocké dans la classe proxy. De plus, la classe d'origine et le proxy ont généralement la même interface, ce qui facilite le remplacement d'un objet de la classe d'origine par un objet proxy."

"Par exemple:"

Interface de la vraie classe
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implémentation de la classe d'origine
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implémentation de la classe proxy
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

"Dans l'exemple ci-dessus, nous avons décrit l' interface Bank et la classe CitiBank , une implémentation de cette interface."

"L'interface vous permet d'obtenir ou de modifier le solde du compte d'un utilisateur."

Et puis nous avons créé BankSecurityProxy , qui implémente également l' interface Bank et stocke une référence à une interface Bank différente. Les méthodes de cette classe vérifient si l'utilisateur est le titulaire du compte ou un directeur de banque. Si ce n'est pas le cas, une SecurityException est levée."

"Voici comment cela fonctionne en pratique :"

Code sans contrôle de sécurité :
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Code avec contrôles de sécurité :
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"Dans le premier exemple, nous créons un objet banque et appelons sa méthode setUserMoney .

"Dans le deuxième exemple, nous encapsulons l'objet bancaire d'origine dans un objet BankSecurityProxy . Ils ont la même interface, donc le code suivant continue de fonctionner comme avant. Mais maintenant, un contrôle de sécurité sera effectué chaque fois qu'une méthode est appelée."

"Cool!"

"Oui. Vous pouvez avoir plusieurs mandataires de ce type. Par exemple, vous pouvez ajouter un autre mandataire qui vérifie si le solde du compte est trop important. Le directeur de la banque pourrait décider de mettre beaucoup d'argent sur son propre compte et de s'enfuir à Cuba avec les fonds. ."

"De plus... La création de toutes ces chaînes d'objets peut être placée dans une classe BankFactory , où vous pouvez activer/désactiver ceux dont vous avez besoin."

" BufferedReader fonctionne selon un principe similaire. C'est un Reader , mais il fait un travail supplémentaire."

"Cette approche vous permet d'"assembler" un objet avec les fonctionnalités nécessaires à partir de différentes "pièces"."

"Oh, j'ai presque oublié. Les procurations sont beaucoup plus utilisées que ce que je viens de vous montrer. Vous pouvez en savoir plus sur d'autres utilisations ici ."

Modèle de pont

Modèles : adaptateur, proxy, pont - 2

"Parfois, lorsqu'un programme s'exécute, il est nécessaire de modifier considérablement la fonctionnalité d'un objet. Par exemple, supposons que vous ayez un jeu avec un personnage d'âne qui est ensuite transformé en dragon par un mage. Le dragon a un comportement et des propriétés complètement différents, mais c'est le même objet !"

« Ne pouvons-nous pas simplement créer un nouvel objet et en finir avec lui ?

"Pas toujours. Supposons que votre âne soit ami avec un groupe de personnages, ou peut-être qu'il était sous l'influence de plusieurs sorts, ou qu'il était impliqué dans certaines quêtes. En d'autres termes, l'objet peut déjà être utilisé dans de nombreux endroits - et lié à beaucoup d'autres objets. Donc, dans ce cas, il n'est pas possible de simplement créer un nouvel objet."

"Eh bien, que peut-on faire alors ?"

"Le modèle Bridge est l'une des solutions les plus réussies."

"Ce modèle implique de scinder un objet en deux objets : un "objet d'interface" et un "objet d'implémentation"."

"Quelle est la différence entre l'interface et la classe qui l'implémente ?"

"Avec une interface et une classe, nous nous retrouvons avec un objet. Mais ici, nous en avons deux. Regardez cet exemple :"

Exemple
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //Run
 {
  realUser.run();
 }

 public void fly() //Fly
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run()
 {
 }

 public void fly()
 {
 }
}

"Et puis vous pouvez déclarer plusieurs sous-classes de UserImpl, par exemple UserDonkey (âne) et UserDragon (dragon)."

"Tout de même, je ne comprends pas vraiment comment cela va fonctionner."

"Eh bien, quelque chose comme ça :"

Exemple
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
Comment ça fonctionne
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

"Donc, c'est quelque chose comme un proxy."

"Oui, mais dans un proxy, l'objet principal pourrait être stocké quelque part séparément, et le code fonctionne avec des proxys à la place. Ici, nous disons que tout le monde travaille avec l'objet principal, mais ses parties changent en interne."

« Ah. Merci. Pourriez-vous me donner un lien pour en savoir plus ? »

"Bien sûr, Amigo, mon ami. Et voilà : modèle de pont ."