Modèles GoF

Liste des modèles Gang of Four – les mêmes schémas qui peuvent vous faire échouer lors d’un entretien.

Modèles génératifs

Modèles structurels

Modèles de comportement

Interprète de modèle

Ce qui est inclus

Le modèle Interpreter fait référence aux modèles de conception comportementale. Ce modèle vous permet d’implémenter votre propre langage de programmation en travaillant avec un arbre AST dont les sommets sont des expressions terminales et non terminales qui implémentent la méthode Interpret, qui fournit les fonctionnalités du langage.

  • Expression de terminal : par exemple, constante de chaîne – “Bonjour tout le monde”
  • Expression non terminale : par exemple Print(“Hello World”), contient Print et un argument de l’expression terminale “Hello World”

Quelle est la différence ? La différence est que l’interprétation se termine sur les expressions terminales, mais pour les expressions non terminales, elle se poursuit en profondeur sur tous les sommets/arguments entrants. Si l’arborescence AST était constituée uniquement d’expressions non terminales, l’application ne se terminerait jamais, car une certaine finitude de tout processus est requise, cette finitude est ce que sont les expressions terminales, elles contiennent généralement des données, par exemple des chaînes.

Un exemple d’arborescence AST est ci-dessous :


Dcoetzee, CC0, via Wikimedia Commons

Comme vous pouvez le voir, les expressions terminales sont constantes et variables, les expressions non terminales sont le reste.

Ce qui n’est pas inclus

L’implémentation de l’Interpreter n’inclut pas l’analyse de la chaîne de langue entrée dans l’arborescence AST. Il suffit d’implémenter des classes d’expressions terminales et non terminales, des méthodes Interpret avec l’argument Context en entrée, de créer un arbre d’expressions AST et d’exécuter la méthode Interpret au niveau de l’expression racine. Un contexte peut être utilisé pour stocker l’état de l’application au moment de l’exécution.

Mise en œuvre

Le modèle implique :

  • Client – ​​​​renvoie l’arborescence AST et exécute Interpret(context) pour le nœud racine (Client)
  • Contexte : contient l’état de l’application, transmis aux expressions lorsqu’elles sont interprétées (Contexte)
  • Expression abstraite : une classe abstraite contenant la méthode Interpret(context) (Expression)
  • L’expression terminale est une expression finale, descendante d’une expression abstraite (TerminalExpression)
  • Une expression non terminale n’est pas une expression finie ; elle contient des pointeurs vers des sommets profonds dans l’arborescence AST ; les sommets subordonnés affectent généralement le résultat de l’interprétation de l’expression non terminale (NonTerminalExpression)

Exemple client en C#

        static void Main(string[] args)
        {
            var context = new Context();
            var initialProgram = new PerformExpression(
                new IExpression[] {
                    new SetExpression("alpha", "1"),
                    new GetExpression("alpha"),
                    new PrintExpression(
                        new IExpression[] {
                            new ConstantExpression("Hello Interpreter Pattern")
                        }
                    )
                }
            );
            System.Console.WriteLine(initialProgram.interpret(context));
        }
}

Exemple d’expression abstraite en C#

{
    String interpret(Context context);
}

Exemple d’expression de terminal en C# (constante de chaîne)

{
    private String constant;

    public ConstantExpression(String constant) {
        this.constant = constant;
    }

    override public String interpret(Context context) {
        return constant;
    }
}

Exemple d’expression non terminale en C# (démarrage et concaténation des résultats des sommets subordonnés, en utilisant le délimiteur « ; »

{
    public PerformExpression(IExpression[] leafs) : base(leafs) {
        this.leafs = leafs;
    }
    
    override public String interpret(Context context) {
        var output = "";
        foreach (var leaf in leafs) {
            output += leaf.interpret(context) + ";";
        }
        return output;
    }
}

Pouvez-vous le faire fonctionnellement ?

Comme on le sait, tous les langages Turing-complets sont équivalents. Est-il possible de transférer le modèle orienté objet vers le langage de programmation fonctionnel ?

Pour une expérience, prenons un langage FP pour le Web appelé Elm. Il n’y a pas de classes dans Elm, mais il y a des enregistrements et des types, donc les enregistrements et types suivants sont impliqués dans l’implémentation :

  • Expression : liste de toutes les expressions linguistiques possibles (Expression)
  • Expression subordonnée : expression subordonnée à l’expression non terminale (ExpressionLeaf)
  • Contexte : un enregistrement stockant l’état de l’application (Contexte)
  • Fonctions qui implémentent les méthodes Interpret(context) : toutes les fonctions nécessaires qui implémentent les fonctionnalités des expressions terminales et non terminales
  • Enregistrements auxiliaires de l’état de l’interprète – nécessaires au bon fonctionnement de l’interprète, ils stockent l’état de l’interprète, le contexte

Un exemple de fonction qui implémente l’interprétation pour l’ensemble des expressions possibles dans Elm :

  case input.expression of
    Constant text ->
      { 
        output = text, 
        context = input.context 
      }
    Perform leafs ->
      let inputs = List.map (\leaf -> { expressionLeaf = leaf, context = input.context } ) leafs in
        let startLeaf = { expressionLeaf = (Node (Constant "")), context = { variables = Dict.empty } } in
          let outputExpressionInput = List.foldl mergeContextsAndRunLeafs startLeaf inputs in
            {
              output = (runExpressionLeaf outputExpressionInput).output,
              context = input.context
            }
    Print printExpression ->
      run 
      { 
        expression = printExpression, 
        context = input.context 
      }
    Set key value ->
      let variables = Dict.insert key value input.context.variables in
      {
        output = "OK",
        context = { variables = variables }
      }
    Get key ->
      {
        output = Maybe.withDefault ("No value for key: " ++ key) (Dict.get key input.context.variables),
        context = input.context
      }

Et l’analyse syntaxique ?

L’analyse du code source dans une arborescence AST n’est pas incluse dans le modèle Interpreter ; il existe plusieurs approches pour analyser le code source, mais nous y reviendrons une autre fois.
Dans l’implémentation de l’Interpreter for Elm, j’ai écrit un analyseur simple dans l’arborescence AST, composé de deux fonctions : analyser un sommet et analyser les sommets subordonnés.

parseLeafs state =
    let tokensQueue = state.tokensQueue in
        let popped = pop state.tokensQueue in
            let tokensQueueTail = tail state.tokensQueue in
                if popped == "Nothing" then
                    state
                else if popped == "Perform(" then
                    {
                        tokensQueue = tokensQueue,
                        result = (state.result ++ [Node (parse tokensQueue)])
                    }
                else if popped == ")" then
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = state.result
                    }
                else if popped == "Set" then
                    let key = pop tokensQueueTail in
                        let value = pop (tail tokensQueueTail) in
                            parseLeafs {
                                tokensQueue = tail (tail tokensQueueTail),
                                result = (state.result ++ [Node (Set key value)])
                            }
                else if popped == "Get" then
                    let key = pop tokensQueueTail in
                        parseLeafs {
                            tokensQueue = tail tokensQueueTail,
                            result = (state.result ++ [Node (Get key)])
                        }
                else 
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = (state.result ++ [Node (Constant popped)])
                    }

parse tokensQueue =
    let popped = pop tokensQueue in
        let tokensQueueTail = tail tokensQueue in
            if popped == "Perform(" then
                Perform (
                    parseLeafs {
                        tokensQueue = tokensQueueTail, 
                        result = []
                    }
                ).result
            else if popped == "Set" then
                let key = pop tokensQueueTail in
                    let value = pop (tail tokensQueueTail) in
                        Set key value
            else if popped == "Print" then
                Print (parse tokensQueueTail)
            else
                Constant popped

Liens

https://gitlab.com/demensdeum /patterns/-/tree/master/interpreter/elm
https://gitlab.com/demensdeum/patterns/-/tree/master/interpreter/csharp

Sources

https://en.wikipedia.org/wiki/Interpreter_pattern
https://elm-lang.org/
https://docs.microsoft.com/en-us/dotnet/csharp/

Comment j’ai raté le gars sur le poteau ou une histoire d’ingéniosité incroyable

Dans cette note, j’écrirai sur l’importance des décisions architecturales lors du développement, de la prise en charge d’une application et dans un environnement de développement en équipe.

Auto- serviette d’opération Professeur Lucifer Gorgonzola. Rubé Goldberg

Dans ma jeunesse, j’ai travaillé sur une application de commande de taxi. Dans le programme, vous pouvez sélectionner un point de prise en charge, un point de dépôt, calculer le coût du trajet, le type de tarif et, effectivement, commander un taxi. J’ai reçu l’application lors de la dernière étape du pré-lancement ; après avoir ajouté plusieurs correctifs, l’application a été publiée dans l’AppStore. Déjà à ce stade, toute l’équipe a compris qu’il était très mal implémenté, que les modèles de conception n’étaient pas utilisés, que tous les composants du système étaient étroitement liés, en général, il était possible de l’écrire dans une grande classe continue (objet Dieu), rien n’aurait changé, de même la manière dont les classes ont mélangé leurs frontières de responsabilité et, dans leur masse totale, se sont superposées dans un couplage mort. Plus tard, la direction a décidé d’écrire l’application à partir de zéro, en utilisant la bonne architecture, ce qui a été fait et le produit final a été implémenté chez plusieurs dizaines de clients B2B.

Cependant, je vais décrire un curieux incident de l’architecture passée, dont je me réveille parfois avec des sueurs froides au milieu de la nuit, ou dont je me souviens soudainement au milieu de la journée et me mets à rire hystériquement. Le problème, c’est que je n’ai pas réussi à frapper le gars sur le poteau du premier coup, ce qui a fait échouer la plupart des applications, mais avant tout.

C’était une journée de travail ordinaire, l’un des clients a reçu pour tâche d’affiner légèrement la conception de l’application – Il est simple de déplacer l’icône au centre de l’écran de sélection de l’adresse de retrait de quelques pixels. Eh bien, après avoir professionnellement estimé la tâche à 10 minutes, j’ai élevé l’icône de 20 pixels, ne me doutant absolument de rien, j’ai décidé de vérifier la commande de taxi.

Quoi ? L’application n’affiche plus le bouton de commande ? Comment est-ce arrivé ?

Je n’en croyais pas mes yeux ; après avoir augmenté l’icône de 20 pixels, l’application a cessé d’afficher le bouton Continuer la commande. Après avoir annulé le changement, j’ai revu le bouton. Quelque chose n’allait pas ici. Après avoir passé 20 minutes dans le débogueur, j’en avais un peu marre de dérouler les spaghettis d’appels à des classes qui se chevauchent, mais j’ai découvert que *déplacer l’image change vraiment la logique de l’application*

Tout était question de l’icône au centre – un homme sur un poteau, en déplaçant la carte il sautait pour animer le mouvement de la caméra, cette animation était suivie de la disparition du bouton en bas. Apparemment, le programme pensait que l’homme décalé de 20 pixels était en train de sauter, donc selon sa logique interne, il a caché le bouton de confirmation.

Comment cela peut-il arriver ? L’*état* de l’écran ne dépend-il vraiment pas du modèle de la machine à états, mais de la *représentation* de la position de l’homme sur le poteau ?

Il s’est avéré que chaque fois que la carte est dessinée, l’application *a poussé visuellement* au milieu de l’écran et a vérifié ce qu’il y avait là, s’il y a un homme sur un poteau, cela signifie que l’animation de changement de carte est terminée et doit être affichée bouton. Si l’homme n’est pas là, alors la carte est décalée et le bouton doit être masqué.

Dans l’exemple ci-dessus, tout va bien, d’une part, c’est un exemple de machines Goldberg (machines abscons), et d’autre part, un exemple de la réticence du développeur à interagir d’une manière ou d’une autre avec d’autres développeurs de l’équipe (essayez de le comprendre sans moi), troisièmement, vous pouvez lister tous les problèmes selon SOLID, les modèles (odeurs de code), les violations MVC et bien plus encore.

Essayez de ne pas faire cela, développez-vous dans toutes les directions possibles, aidez vos collègues dans leur travail. Bonne année à tous)

Liens

https://ru.wikipedia.org/wiki/Goldberg_Machine

https://ru.wikipedia.org/wiki/SOLID

https://refactoring.guru/ru/refactoring/smells

https://ru.wikipedia.org/wiki/Model -View-Controller

https://refactoring.guru/ru/design-patterns/state

Façade à motifs


La façade fait référence aux modèles de conception structurelle. Il fournit une interface unique qui permet de travailler avec des systèmes complexes, permettant aux clients de ne pas avoir de détails d’implémentation sur ces systèmes, simplifiant ainsi leur code et implémentant un couplage lâche entre les clients et les systèmes de niveau inférieur. GoF a un bon exemple de façade – un compilateur de langage de programmation qui offre à différents clients poursuivant différents objectifs la possibilité d’assembler du code via une interface de façade de compilateur unique.

Sources

https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Modèle d’usine abstrait

Usine abstraite– fournit une interface pour créer des objets associés, sans spécifier de classes spécifiques.

J’aime vraiment le nom alternatif de ce modèle : ; Kit (Kit)

Elle est très similaire à la Méthode d’usine, cependant, les Usines abstraites doivent décrire la relation entre les objets en cours de création, sinon il s’agit simplement d’un Objet divin. l’anti-modèle qui crée tout est aléatoire.

Imaginez développer un framework AR pour lunettes ; nous affichons sur l’écran des flèches de navigation intérieures, des icônes de magasins, des lieux intéressants, des fenêtres et des boutons avec des informations sur tout endroit où se trouve actuellement l’utilisateur.

Dans le même temps, nous avons besoin de pouvoir personnaliser l’apparence et le comportement des contrôles de l’environnement AR. C’est précisément dans ce cas que vous devez utiliser le modèle Set.

Écrivons l’interface de Abstract Factory et des Abstract Products – protocoles parents, éléments de l’environnement AR :

protocol ARFactory {
    func arrow() -> ARArrow
    func icon() -> ARIcon
    func button() -> ARButton
    func window() -> ARWindow
}

protocol ARArrow {
    var image: { get }
    func handleSelection()
}

protocol ARIcon {
    var image: { get }
    var title: String
}

protocol ARButton {
    var title: String
    func handleSelection()
}

protocol ARWindow {
    var title: String
    var draw(canvas: Canvas)
}

Les développeurs de kits devront désormais implémenter une Concrete Factory basée sur l’interface Abstract Factory, et ils devront implémenter tous les éléments ensemble ; le reste de l’application pourra travailler avec l’usine sans changer leur code.< /p>

Sources

https://refactoring.guru/ru/design-patterns /usine-abstraite
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Méthode d’usine

Le modèle Factory Method fait référence à des modèles de conception génératifs.
Ce modèle décrit la création d’une interface pour créer un objet d’une classe spécifique. Cela semble simple, non ?

En théorie

Supposons que nous développions un cadre pour travailler avec des lunettes AR. Lorsque vous inclinez la tête sur le côté, un menu d’applications disponibles devrait apparaître devant les yeux de l’utilisateur. Les applications seront développées par des sociétés tierces, clientes de notre framework. Naturellement, nous ne savons pas quelles applications, icônes, noms doivent apparaître, nous devons donc fournir une interface pour implémenter l’icône et les informations associées sur l’application. Appelons-le Produit :

protocol Product {
 var name: String { get }
 var image: Image { get }
 var executablePath: String { get }
}

Ensuite, nous devons fournir une interface afin que nos clients puissent mettre en œuvre la délivrance d’une gamme d’applications pour leur Produit spécifique – un tableau d’icônes d’application avec des noms, que nous dessinerons déjà dans le framework.

Écrivons cette interface – Interface Creator contenant une Méthode Factory renvoyant un tableau de Produits.

protocol Creator {
 func factoryMethod() -> [Product]
}

En pratique

Le premier client de notre framework AR était la société 7B – principal fournisseur de logiciels pour cafetières au Honduras. Ils souhaitent vendre des lunettes de réalité augmentée capables de préparer du café, de vérifier si l’eau ou les grains sont pleins et d’indiquer le chemin jusqu’à la cafetière la plus proche en utilisant le mode carte intérieure.

Ils se chargent du développement du logiciel ; nous sommes uniquement tenus de fournir une documentation sur les interfaces Creator et Produit pour le bon affichage de la liste des applications et leurs suites. lancer.

Après avoir transféré la documentation, la société 7B, à l’aide de l’interface Creator , implémente le Specific Creator – classe renvoyant un tableau d’icônes d’application. Les applications d’icônes elles-mêmes sont des classes Produit spécifique qui implémentent l’interface Produit.

Exemple de code pour Produits spécifiques :

class CoffeeMachineLocator: implements Product {
 let name = “7B Coffee Machine Locator v.3000”
 let image = Image.atPath(“images/locator.tga”)
 let executablePath = “CoffeeMachineLocator.wasm”
}

class iPuchinno: implements Product {
 let name = “iPuchinno 1.0.3”
 let image = Image.atPath(“images/puchino.pvrtc”)
 let executablePath = “neutron/ipuchBugFixFinalNoFreezeFixAlpha4.js”
}

Classe Concrete Creator, offrant un éventail de deux applications :

class 7BAppsCreator: implements Creator {
 func factoryMethod() -> [Product] {
  return [CoffeeMachineLocator(), iPuchinno()]
 }
}

Après cela, la société 7B compile la bibliothèque de Concrete Products, Concrete Creator et la combine avec notre framework, commence à vendre des lunettes AR pour ses cafetières, ajouts de notre part non requis.

Sources

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Commande de modèle

Le modèle de commande fait référence aux modèles de conception comportementale.

C’est le modèle avec lequel je suis resté le plus longtemps, il est si simple qu’il en est très complexe. Mais personnellement, je trouve que la beauté de l’auto-apprentissage est que vous avez tout le temps du monde pour rechercher un certain sujet sous tous les angles.

Ainsi, dans le GoF, l’applicabilité est décrite de manière assez succincte et claire :
Encapsule une requête en tant qu’objet, vous permettant de paramétrer des clients avec différentes requêtes, d’utiliser des files d’attente, de consigner les requêtes et d’effectuer des opérations d’annulation.

Implémentons maintenant une version simple de la commande à partir de la description :

string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”

Nous avons encapsulé la requête dans un objet de classe chaîne, il peut être utilisé pour configurer les clients, ajouter des commandes à la file d’attente, enregistrer, annuler (en utilisant le modèle « Snapshot »)

Il me semble que cela suffit amplement pour effectuer des requêtes SQL et autres, mais il y a ensuite des détails d’implémentation, différentes options d’application, la base de code du modèle, les rôles client et les classes auxiliaires.

Pièces matérielles

Le

modèle de commande commence par un protocole de commande, qui contient une seule méthode execute(). Vient ensuite la Commande spécifique et le récepteur. Le CC implémente l’opération sur le récepteur, décrit la connexion entre le récepteur et l’action. Quelque chose n’est pas clair ? Moi aussi, mais passons à autre chose. Le Client crée une instance d’une Commande Spécifique, l’associe au Récepteur. Invocateur – objet qui exécute le processus de lancement des Commandes.

Essayons maintenant de comprendre à l’aide d’un exemple, disons que nous voulons mettre à jour myOS sur myPhone, pour ce faire, nous lançons l’application myOS_Update !, nous y appuyons sur le bouton Mettre à jour maintenant, après 10 secondes, le système le fera ! signaler une mise à jour réussie.

Le client dans l’exemple ci-dessus est l’application myOS_Update!, l’Invoker est le bouton “Mettre à jour maintenant !”, il lance la Commande spécifique mettre à jour le système à l’aide de la méthode execute(), qui accède au Récepteur– Démon de mise à jour du système d’exploitation.

Utiliser un exemple

Acceptons l’interface utilisateur de l’application myOS_Update ! si bon qu’ils ont décidé de le vendre en tant que produit distinct pour fournir une interface permettant de mettre à jour d’autres systèmes d’exploitation. Dans ce cas, nous implémenterons une application avec prise en charge des extensions via des bibliothèques, dans les bibliothèques il y aura des implémentations de Commandes spécifiques, récepteurs, nous laisserons Invoker statique/immuable. , Client, protocole Commandes.

Ainsi, il n’est pas nécessaire de prendre en charge le code mutable, puisque notre code restera inchangé, des problèmes ne peuvent survenir que lorsqu’il est implémenté côté client, en raison d’erreurs dans le code de leurs Commandes spécifiques et Récepteurs. De plus, dans cette implémentation, il n’est pas nécessaire de transférer le code source de l’application principale, c’est-à-dire que nous avons encapsulé les commandes et les interactions de l’interface utilisateur à l’aide du modèle Command.

Sources

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Générateur de modèles

Le pattern Builder appartient à un groupe de patterns dont l’existence ne m’est pas particulièrement claire, je constate la redondance évidente de celui-ci. Appartient au groupe des modèles de conception génératifs. Utilisé pour implémenter une interface simple pour créer des objets complexes.

Applicabilité

Simplification de l’interface. Cela peut faciliter la création d’un objet dans des constructeurs avec un grand nombre d’arguments, et améliorer objectivement la lisibilité du code.

Exemple en C++ sans builder :

auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:

let monster = Monster(weapon: “Claws”, health: 100)

Immuabilité. Grâce au générateur, vous pouvez assurer l’encapsulation de l’objet créé jusqu’à l’étape d’assemblage final. Ici, vous devez bien réfléchir pour savoir si l’utilisation d’un modèle vous évitera la dynamique élevée de l’environnement dans lequel vous travaillez ; peut-être que l’utilisation du modèle ne donnera rien, en raison du simple manque de culture d’utilisation de l’encapsulation au sein de l’équipe de développement ; .

Interaction avec les composants à différentes étapes de la création de l’objet. En utilisant également le modèle, il est possible d’assurer la création étape par étape d’un objet lors de l’interaction avec d’autres composants du système. Très probablement, c’est très utile (?)

Critique

Bien sûr, vous devez réfléchir *soigneusement* à la question de savoir s’il vaut la peine de généraliser l’utilisation du modèle dans votre projet. Des langages dotés d’une syntaxe moderne et d’un IDE avancé éliminent le besoin d’utiliser le Builder, en termes d’amélioration de la lisibilité du code (voir le point sur les arguments nommés)
Ce modèle aurait-il dû être utilisé en 1994, lorsque le livre du GoF a été publié ? Très probablement oui, cependant, à en juger par la base de code Open source de ces années-là, peu de gens l’utilisaient.

Sources

https://refactoring.guru/ru/design-patterns/builder

Composition de motifs

Le modèle composite fait référence à des modèles de conception structurelle ; dans les sources nationales, il est connu sous le nom de « compositeur ».
Disons que nous développons une application – photo album. L’utilisateur peut créer des dossiers, y ajouter des photos et effectuer d’autres manipulations. Vous avez absolument besoin de pouvoir afficher le nombre de fichiers dans les dossiers, le nombre total de tous les fichiers et dossiers.
Il est évident qu’il faut utiliser un arbre, mais comment implémenter une architecture arborescente avec une interface simple et pratique ? Le motif Composite vient à la rescousse.

Sheila dans Moonducks

Ensuite, dans le répertoire, nous implémentons la méthode dataCount() – en parcourant tous les éléments du tableau de composants, en additionnant tous leurs dataCount.
Tout est prêt !
Voici un exemple dans Go :
package main

import "fmt"

type component interface {

dataCount() int

}

type file struct {

}

type directory struct {

c []component

}

func (f file) dataCount() int {

return 1

}

func (d directory) dataCount() int {

var outputDataCount int = 0

for _, v := range d.c {
outputDataCount += v.dataCount()
}

return outputDataCount

}

func (d *directory) addComponent(c component) {

d.c = append(d.c, c)

}

func main() {

var f file
var rd directory
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)

fmt.Println(rd.dataCount())

var sd directory
sd.addComponent(f)

rd.addComponent(sd)
rd.addComponent(sd)
rd.addComponent(sd)

fmt.Println(sd.dataCount())
fmt.Println(rd.dataCount())

}

Sources

https://refactoring.guru/ru/design-patterns/ composite

Adaptateur de motif

Benjamín Núñez González

Le modèle Adaptateur fait référence aux modèles de conception structurelle.

L’adaptateur permet la conversion de données/interface entre deux classes/interfaces.

Supposons que nous développions un système permettant de déterminer les objectifs d’un acheteur dans un magasin, basé sur des réseaux de neurones. Le système reçoit un flux vidéo d’une caméra du magasin, identifie les clients par leur comportement et les classe en groupes. Types de groupes – est venu acheter (acheteur potentiel), juste pour regarder (spectateur), est venu voler quelque chose (voleur), est venu rendre la marchandise (acheteur insatisfait), est venu ivre / défoncé (chahuteur potentiel).

Comme tous les développeurs expérimentés, nous trouvons un réseau neuronal prêt à l’emploi qui peut classer les espèces de singes dans une cage sur la base d’un flux vidéo, que l’Institut zoologique du zoo de Berlin a aimablement mis à disposition gratuitement, le recycler sur un flux vidéo du magasin et obtenez un système de pointe fonctionnel.

Il y a juste un petit problème – le flux vidéo est encodé au format mpeg2 et notre système ne prend en charge que OGG Theora. Nous n’avons pas le code source du système, la seule chose que nous pouvons faire est de « » modifier l’ensemble de données et entraîner le réseau neuronal. Ce qu’il faut faire? Écrivez une classe d’adaptateur qui transférera le flux depuis mpeg2 -> OGG Theora et l’enverra au réseau neuronal.

Selon le schéma classique, le modèle implique le client, la cible, l’adapté et l’adaptateur. Le client dans ce cas est un réseau de neurones qui reçoit un flux vidéo dans OGG Theora, cible – l’interface avec laquelle il interagit, adaptée – interface d’envoi de flux vidéo en mpeg2, adaptateur – convertit mpeg2 en OGG Theora et l’envoie via l’interface cible.

Est-ce que tout semble simple ?

Sources

https://ru.wikipedia.org/wiki/Adapter_ (design_pattern)
https://refactoring.guru/ru/design-patterns/adapter

Modèle de délégué

Le modèle de délégué est l’un des principaux modèles de conception.
Disons que nous développons une application pour salon de coiffure. L’application dispose d’un calendrier pour sélectionner un jour d’enregistrement ; en appuyant sur la date, une liste de barbiers avec un choix devrait s’ouvrir.
Implémentons une liaison naïve des composants du système, combinons le calendrier et l’écran à l’aide de pointeurs les uns vers les autres pour implémenter un affichage de liste :


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

Au bout de quelques jours, les exigences changent ; avant d’afficher la liste, vous devez présenter des offres avec un choix de prestations (taille de barbe, etc.), mais pas toujours, tous les jours sauf le samedi.
On ajoute au calendrier une vérification si c’est samedi ou non, selon cela, on appelle la méthode de la liste des barbiers ou de la liste des services, pour plus de clarté je vais démontrer :


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

Une semaine plus tard, on nous demande d’ajouter un calendrier à l’écran de commentaires, et à ce moment-là, les premiers oups architecturaux se produisent !
Ce qu’il faut faire? Le calendrier est étroitement lié à l’écran de rendez-vous chez la coupe de cheveux.
Ouah! Pouah! oh-oh
Si vous continuez à travailler avec cette architecture d’application folle, vous devez faire une copie de l’intégralité de la classe de calendrier et associer cette copie à l’écran de commentaires.
Ok, ça a l’air bien, puis nous avons ajouté quelques écrans supplémentaires et plusieurs copies du calendrier, et puis le moment X est arrivé. On nous a demandé de modifier la conception du calendrier, ce qui signifie que nous devons maintenant trouver toutes les copies du calendrier et ajouter les mêmes modifications à toutes. Cette « approche » affecte grandement la vitesse de développement et augmente le risque de commettre une erreur. En conséquence, de tels projets se retrouvent dans un état d’échec, lorsque même l’auteur de l’architecture originale ne comprend plus comment fonctionnent les copies de ses classes, et que d’autres hacks ajoutés en cours de route s’effondrent à la volée.
Que fallait-il faire, ou mieux encore, que n’était-il pas trop tard pour commencer à faire ? Utilisez le modèle de délégation !
La délégation est un moyen de transmettre les événements de classe via une interface commune. Vous trouverez ci-dessous un exemple de délégué pour un calendrier :

protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

Ajoutons maintenant le code permettant de travailler avec le délégué à l’exemple de code :


// псевдокод

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

En conséquence, nous avons complètement dissocié le calendrier de l’écran ; lors de la sélection d’une date dans le calendrier, il transmet l’événement de sélection de date – *délègue* le traitement des événements à l’abonné ; L’abonné est l’écran.
Quels avantages tirons-nous de cette approche ? Nous pouvons désormais modifier la logique du calendrier et de l’écran indépendamment les uns des autres sans dupliquer les classes, ce qui simplifie davantage la prise en charge ; De cette manière, le « principe de responsabilité exclusive » pour la mise en œuvre des composants du système est mis en œuvre et le principe DRY est respecté.
Lors de l’utilisation de la délégation, vous pouvez ajouter, modifier la logique d’affichage des fenêtres, l’ordre de tout ce qui apparaît à l’écran, et cela n’affectera en rien le calendrier et les autres classes, qui ne devraient objectivement pas participer à des processus qui ne leur sont pas directement liés.< br />Alternativement, les programmeurs qui ne se dérangent pas trop utilisent l’envoi de messages via un bus commun, sans écrire d’interface protocole/délégué séparée, où il serait préférable d’utiliser la délégation. J’ai parlé des inconvénients de cette approche dans un article précédent : “Modèle d’observateur.”

Sources

https://refactoring.guru/ru/replace-inheritance -avec-délégation

Modèle d’observateur

Le modèle Observer fait référence à des modèles de conception comportementale.
Le modèle permet d’envoyer un changement d’état d’un objet aux abonnés à l’aide d’une interface commune.
Disons que nous développons un messager pour les programmeurs, nous avons un écran de discussion dans l’application. Lorsque vous recevez un message avec le texte « problème » et « erreur » ou « quelque chose ne va pas », vous devez colorer l’écran de la liste d’erreurs et l’écran des paramètres en rouge.
Ensuite, je décrirai 2 options pour résoudre le problème, la première est simple mais extrêmement difficile à prendre en charge, et la seconde est beaucoup plus stable en termes de support, mais vous oblige à tourner la tête lors de la mise en œuvre initiale.

Bus commun

Toutes les implémentations du modèle contiennent l’envoi de messages lorsque les données changent, l’abonnement aux messages et un traitement ultérieur dans les méthodes. L’option de bus partagé contient un seul objet (généralement un singleton) qui distribue les messages aux destinataires.
La simplicité de mise en œuvre est la suivante :

  1. L’objet envoie un message abstrait au bus partagé
  2. Un autre objet abonné au bus partagé récupère le message et décide de le traiter ou non.

L’une des options d’implémentation disponibles auprès d’Apple (sous-système NSNotificationCenter), ajout d’une correspondance entre l’en-tête du message et le nom de la méthode appelée par le destinataire lors de la livraison.
Le plus gros inconvénient de cette approche – Si vous modifiez davantage le message, vous devrez d’abord vous souvenir, puis modifier manuellement tous les endroits où il est traité et envoyé. Il existe un cas de mise en œuvre initiale rapide, suivie d’un support long et complexe qui nécessite une base de connaissances pour un fonctionnement correct.

Délégué multidiffusion

Dans cette implémentation, nous créerons la classe déléguée multicast finale ; tout comme dans le cas d’un bus partagé, les objets peuvent s’y abonner pour recevoir des « messages » ou des « événements », mais le travail d’analyse et de filtrage des messages est non affecté aux objets. Au lieu de cela, les classes d’abonnés doivent implémenter les méthodes de multidiffusion du délégué avec lesquelles il les notifie.
Ceci est implémenté en utilisant des interfaces/protocoles délégués ; lorsque l’interface générale change, l’application ne sera plus construite, il sera alors nécessaire de refaire tous les emplacements de traitement d’un message donné, sans avoir besoin de maintenir une base de connaissances distincte. pour me souvenir de ces lieux. Le compilateur est votre ami.
Cette approche augmente la productivité de l’équipe, car il n’est pas nécessaire d’écrire ou de stocker de la documentation, il n’est pas nécessaire qu’un nouveau développeur essaie de comprendre comment un message et ses arguments sont traités, mais ils travaillent avec une interface pratique et compréhensible. , c’est ainsi qu’est implémenté le paradigme de la documentation par le code.
Le délégué de multidiffusion lui-même est basé sur le modèle de délégué, dont je parlerai dans le prochain article.

Sources

https://refactoring.gu/ru/design-patterns/observer

Modèle de proxy

Le modèle Proxy fait référence à des modèles de conception structurelle.
Le modèle décrit la technique de travail avec une classe via une couche de classe – ; procuration. Un proxy vous permet de modifier les fonctionnalités de la classe d’origine, avec la possibilité de conserver le comportement d’origine, tout en conservant l’interface de classe d’origine.
Imaginons la situation – en 2015, l’un des pays d’Europe occidentale a décidé d’enregistrer toutes les demandes adressées aux sites Internet des utilisateurs du pays, afin d’améliorer les statistiques et une compréhension approfondie des sentiments politiques des citoyens.
Imaginons le pseudocode d’une implémentation naïve de la passerelle que les citoyens utilisent pour accéder à Internet :

class InternetRouter {

    private let internet: Internet

    init(internet: Internet) {
        self.internet = internet
    }

    func handle(request: Request, from client: Client) -> Data {
        return self.internet.handle(request)
    }

}

Dans le code ci-dessus, nous créons une classe de routeur Internet avec un pointeur vers un objet qui fournit un accès à Internet. Lorsqu’un client fait une demande de site Web, nous renvoyons une réponse depuis Internet.

En utilisant le modèle Proxy et l’anti-modèle singleton, nous ajouterons une fonctionnalité pour enregistrer le nom et l’URL du client :

class InternetRouterProxy {

    private let internetRouter: InternetRouter

    init(internet: Internet) {
        self.internetRouter = InternetRouter(internet: internet)
    }

    func handle(request: Request, from client: Client) -> Data {

        Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)

        return self.internetRouter.handle(request: request, from: client)
    }

}

En raison de la préservation de l’interface InternetRouter d’origine dans la classe proxy InternetRouterProxy, il suffit de remplacer la classe d’initialisation d’InternerRouter par son proxy, aucune autre modification de la base de code n’est requise.

Sources

https://refactoring.guru/ru/design-patterns/ mandataire

Modèle Prototype

Le modèle prototype appartient au groupe des modèles de conception génératifs.
Disons que nous développons des applications de rencontres Tender. Selon notre modèle commercial, nous avons la possibilité payante de faire des copies de votre propre profil, en changeant automatiquement le nom et l’ordre des photos par endroits. Cela a été fait pour que l’utilisateur puisse avoir la possibilité de gérer plusieurs profils à la fois avec un groupe d’amis différent dans l’application.
En cliquant sur le bouton pour créer une copie du profil, nous devons mettre en œuvre la copie du profil, la génération automatique d’un nom et le retriage des photos.
Implémentation naïve du pseudocode :

fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

Imaginons maintenant que d’autres membres de l’équipe aient fait un copier-coller du code de copie ou l’aient créé à partir de zéro, et qu’après cela, un nouveau champ ait été ajouté – ; goûts. Ce champ stocke le nombre de likes du profil, vous devez maintenant mettre à jour *tous* les endroits où la copie s’effectue manuellement en ajoutant un nouveau champ. La maintenance du code, ainsi que les tests, prennent beaucoup de temps et sont difficiles.
Pour résoudre ce problème, le modèle de conception Prototype a été inventé. Créons un protocole de copie général, avec une méthode copy() qui renvoie une copie d’un objet avec les champs nécessaires. Après avoir modifié les champs d’entité, vous n’aurez besoin de mettre à jour qu’une seule méthode copy(), au lieu de rechercher et de mettre à jour manuellement tous les emplacements contenant du code de copie.

Sources

https://refactoring.guru/ru/design-patterns/prototype

Machine à états et modèle Condition

Dans cet article, je vais décrire l’utilisation de la machine à états (State Machine), montrer une implémentation simple, une implémentation utilisant le modèle State. Il convient de mentionner qu’il n’est pas souhaitable d’utiliser le modèle d’État s’il y a moins de trois États, car cela conduit généralement à une complexité inutile dans la lisibilité du code et à des problèmes de support associés – tout doit être modéré.

MEAACT PHOTO / STUART PRICE.

Seigneur des drapeaux

Supposons que nous développions un écran de lecteur vidéo pour le système multimédia d’un avion civil, le lecteur doit être capable de charger un flux vidéo, de le lire, de permettre à l’utilisateur d’arrêter le processus de téléchargement, de rembobiner et d’effectuer d’autres opérations habituelles pour un joueur.
Supposons que le lecteur ait mis en cache le morceau suivant du flux vidéo, vérifié qu’il y a suffisamment de morceaux pour la lecture, commencé à lire le fragment à l’utilisateur et continue en même temps à télécharger le suivant.
À ce stade, l’utilisateur revient au milieu de la vidéo, c’est-à-dire qu’il doit maintenant arrêter de lire le fragment en cours et commencer le chargement à partir d’une nouvelle position. Cependant, il existe des situations dans lesquelles cela ne peut pas être fait : l’utilisateur ne peut pas contrôler la lecture du flux vidéo pendant qu’on lui montre une vidéo sur la sécurité aérienne. Vérifions l’indicateur isSafetyVideoPlaying pour vérifier cette situation.
Le système doit également être capable de mettre en pause la vidéo en cours et de diffuser une alerte du capitaine et de l’équipage du navire via le lecteur. Ajoutons un autre indicateur isAnnouncementPlaying. De plus, il est obligatoire de ne pas mettre la lecture en pause lors de l’affichage de l’aide sur l’utilisation du lecteur, un autre indicateur est HelpPresenting.

Exemple de pseudocode du lecteur multimédia :

class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

L’exemple ci-dessus est difficile à lire et à maintenir en raison d’une grande variabilité (entropie). Cet exemple est basé sur mon expérience de travail avec la base de code de *de nombreux* projets qui n’utilisaient pas de machine à états.
Chaque case à cocher doit spécifiquement « contrôler » les éléments de l’interface et la logique métier de l’application ; le développeur, en ajoutant une autre case à cocher, doit être capable de jongler avec eux, en vérifiant et revérifiant le tout plusieurs fois avec toutes les options possibles.
En remplaçant dans la formule « 2 ^ nombre de cases à cocher », vous pouvez obtenir 2 ^ 6 = 64 options pour le comportement de l’application pour seulement 6 cases à cocher, toutes ces combinaisons de cases à cocher devront être cochées et gérées manuellement.
Du côté du développeur, l’ajout de nouvelles fonctionnalités avec un tel système ressemble à ceci :
– Nous devons ajouter la possibilité d’afficher la page du navigateur de la compagnie aérienne, et elle devrait être réduite comme pour les films si les membres de l’équipage annoncent quelque chose.
– D’accord, je vais le faire. (Oh putain, je vais devoir ajouter un autre drapeau et revérifier tous les endroits où les drapeaux se croisent, ça fait beaucoup de choses à changer !)

C’est aussi un point faible du système de drapeaux : ; apporter des modifications au comportement de l’application. Il est très difficile d’imaginer comment modifier rapidement et de manière flexible un comportement en fonction des indicateurs, si après avoir modifié un seul indicateur, vous devez tout revérifier. Cette approche du développement entraîne beaucoup de problèmes, une perte de temps et d’argent.

Entrez dans la machine

Si vous regardez attentivement les drapeaux, vous comprendrez qu’en fait nous essayons de traiter des processus spécifiques se produisant dans le monde réel. Nous les listons : mode normal, affichage d’une vidéo de sécurité, diffusion d’un message du capitaine ou des membres d’équipage. Pour chaque processus, un ensemble de règles est connu qui modifie le comportement de l’application.
Selon les règles du modèle de machine à états (machine à états), nous listerons tous les processus en tant qu’états dans l’énumération, ajouterons un concept tel qu’un état au code du joueur, implémenterons un comportement basé sur l’état en supprimant les combinaisons sur les drapeaux. Ainsi, nous réduirons les options de test exactement au nombre d’états.

Pseudocode :

enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

L’énorme différence entre un système de drapeaux et une machine à états réside dans l’entonnoir de commutation d’état logique dans la méthode set(state: ..), il vous permet de traduire la compréhension humaine de l’état en code de programme, sans avoir à jouer à la logique. jeux de conversion de drapeaux en états avec prise en charge ultérieure du code.

État du modèle

Ensuite, je montrerai la différence entre l’implémentation naïve de la machine à états et le modèle d’état. Imaginons que nous devions ajouter 10 états ; en conséquence, la classe des machines à états atteindrait la taille d’un objet divin, ce qui serait difficile et coûteux à maintenir. Bien sûr, cette implémentation est meilleure que l’implémentation du flag (avec le système de flag, le développeur se tirera une balle en premier, et sinon, voyant 2 ^ 10 = 1024 variations, QA se pendra, mais si les deux *ne le font pas remarquerez* la complexité de la tâche, alors l’utilisateur dont l’application est simple remarquera qu’il refusera de travailler avec une certaine combinaison de drapeaux)
S’il y a un grand nombre d’états, il est nécessaire d’utiliser le modèle State.
Ajoutons un ensemble de règles au protocole d’État :

protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

Déplaçons l’implémentation de l’ensemble de règles dans des états distincts, par exemple, le code d’un état :

class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

Ensuite, créons un contexte avec lequel chaque état fonctionnera et intégrons la machine à états :

final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	остальные возможные события
}

Les composants d’application fonctionnent avec le contexte via des méthodes publiques ; les objets d’état décident eux-mêmes de l’état vers lequel effectuer la transition à l’aide de la machine à états à l’intérieur du contexte.
Ainsi, nous avons implémenté la décomposition God Object, maintenir un état changeant sera beaucoup plus facile, grâce au compilateur qui suit les modifications du protocole, réduisant la complexité de compréhension des états en raison de la réduction du nombre de lignes de code, et se concentrant sur résoudre un problème d’État spécifique. Vous pouvez également désormais partager le travail en équipe, en confiant la mise en œuvre d’un état spécifique aux membres de l’équipe, sans vous soucier de la nécessité de « résoudre » les conflits, ce qui se produit lorsque vous travaillez avec une grande classe de machines à états.

Sources

https://refactoring.guru/ru/design-patterns/state

Méthode de modèle

La méthode des modèles fait référence aux modèles de conception comportementale. Le modèle décrit une manière de remplacer une partie de la logique d’une classe à la demande, laissant la partie globale inchangée pour les descendants.

Cuban Cars

Supposons que nous développions une banque client, considérons la tâche de développer un module d’autorisation – ; l’utilisateur doit pouvoir se connecter à l’application à l’aide de données de connexion abstraites.
Le module d’autorisation doit être multiplateforme, prenant en charge différentes technologies d’autorisation et stockant les données cryptées de différentes plateformes. Pour implémenter le module, nous choisissons le langage Kotlin multiplateforme, en utilisant la classe abstraite (protocole) du module d’autorisation, nous écrirons une implémentation pour le téléphone MyPhone :

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Maintenant, pour chaque téléphone/plateforme, nous devrons dupliquer le code d’envoi de l’autorisation au serveur, c’est une violation du principe DRY. L’exemple ci-dessus est très simple ; dans les classes plus complexes, il y aura encore plus de duplications. Pour éliminer la duplication de code, vous devez utiliser le modèle de méthode modèle.
Déplaçons les parties communes du module vers des méthodes immuables et transférons la fonctionnalité de transfert de données cryptées vers des classes de plateforme spécifiques :

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

Sources

https://refactoring.guru/ru/design- modèles/méthode-modèle

Code source

https://gitlab.com/demensdeum/patterns/< /p>

Pont à motifs

Le modèle Bridge fait référence à des modèles de conception structurelle. Il vous permet d’abstraire l’implémentation de la logique de classe en déplaçant la logique dans une classe abstraite distincte. Cela semble simple, n’est-ce pas ?

Supposons que nous implémentions un robot spam capable d’envoyer des messages à différents types de messagers.
Nous l’implémentons en utilisant un protocole commun :

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class SpamBot {
    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

Imaginons maintenant la sortie d’un nouveau protocole plus rapide pour envoyer des messages pour le messager iSekU. Pour ajouter un nouveau protocole, vous devrez dupliquer l’implémentation du bot iSekU, en n’en modifiant qu’une petite partie. On ne sait pas pourquoi faire cela si seulement une petite partie de la logique de classe a changé. Avec cette approche, le principe DRY est violé ; avec le développement ultérieur du produit, le manque de flexibilité se fera sentir par des erreurs et des retards dans la mise en œuvre de nouvelles fonctionnalités.
Déplaçons la logique du protocole dans une classe abstraite, implémentant ainsi le modèle Bridge :

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

protocol MessagesSender {
    func send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekUFastMessengerSender: MessagesSender {
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?
    var messagesSender: MessengerMessagesSender?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        messagesSender?.send(message: message, to: user)
    }
}

class SpamBot {

    var messagesSender: MessagesSender?

    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

L’un des avantages de cette approche est sans aucun doute la possibilité d’étendre les fonctionnalités de l’application en écrivant des plugins/bibliothèques qui implémentent une logique abstraite sans changer le code de l’application principale.
Quelle est la différence avec le modèle Stratégie ? Les deux modèles sont très similaires, cependant, Strategy décrit la commutation d’*algorithmes*, tandis que Bridge vous permet de changer de grandes parties de *n’importe quelle logique complexe*.

Sources

https://refactoring.guru/ru/design-patterns/bridge

Code source

https://gitlab.com/demensdeum/patterns/ p>

Modèle de chaîne de responsabilité

La chaîne de responsabilité fait référence aux modèles de conception comportementale.


Ganna Dolbieva

La société cinématographique Jah-Pictures a réalisé un film documentaire sur les rastafariens communistes du Libéria intitulé « Red Dawn of Marley ». Le film est très long (8 heures), intéressant, mais avant sa sortie, il s’est avéré que dans certains pays, les plans et les phrases du film peuvent être considérés comme une hérésie et ne recevront pas de licence de distribution. Les producteurs du film décident de supprimer manuellement et automatiquement les moments contenant des phrases douteuses du film. Une double vérification est nécessaire afin que les représentants du distributeur ne soient pas simplement abattus dans certains pays en cas d’erreur lors de l’inspection manuelle et de l’installation.
Les pays sont divisés en quatre groupes : pays sans censure, avec une censure modérée, moyenne et très stricte. Il est décidé d’utiliser des réseaux de neurones pour classer le niveau d’hérésie dans le fragment regardé du film. Pour le projet, des neurones de pointe très coûteux sont achetés, entraînés pour différents niveaux de censure, la tâche du développeur – la tâche du développeur. divisez le film en fragments et transmettez-les à travers une chaîne de réseaux neuronaux, de libre à strict, jusqu’à ce que l’un d’eux détecte une hérésie, puis le fragment est transféré pour examen manuel en vue d’un montage ultérieur. Il est impossible de traverser tous les neurones, car leur travail nécessite trop de puissance de calcul (après tout, il faut quand même payer l’électricité), il suffit de s’arrêter au premier qui fonctionne.
Implémentation naïve du pseudocode :

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
        self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        return hnnclassifier.shouldBeCensored(movieChunk)
    }
}

let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))

let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    for classifier in classifiers {
        if classifier.shouldBeCensored(chunk) == true {
            print("Should censor movie chunk: \(chunk), reported by \(classifier)")
        }
   }
}

En général, la solution avec un tableau de classificateurs n’est pas si mauvaise, cependant ! Imaginons que nous ne puissions pas créer de tableau, nous avons la possibilité de créer une seule entité classificatrice, qui détermine déjà le type de censure pour un fragment de film. De telles restrictions sont possibles lors du développement d’une bibliothèque qui étend les fonctionnalités de l’application (plugin).
Utilisons le modèle de décorateur – Ajoutons une référence au classificateur suivant de la chaîne à la classe du classificateur et arrêtons le processus de vérification à la première classification réussie.
Ainsi, nous implémentons le modèle de chaîne de responsabilité :

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let nextClassifier: CensorshipClassifier?
    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
            self.nextClassifier = nextClassifier
            self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        let result = hnnclassifier.shouldBeCensored(movieChunk)
        
        print("Should censor movie chunk: \(movieChunk), reported by \(self)")
        
        if result == true {
                return true
        }
        else {
                return nextClassifier?.shouldBeCensored(movieChunk) ?? false
        }
    }
}

let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    censorshipClassifier.shouldBeCensored(chunk)
}

Références

https://refactoring.guru/ru/ modèles de conception/chaîne de responsabilité

Code source

https://gitlab.com/demensdeum/patterns/< /p>

Décorateur de motifs

Le modèle Decorator fait référence à des modèles de conception structurelle.

Le décorateur est utilisé comme alternative à l’héritage pour étendre les fonctionnalités des classes.
Il existe une tâche consistant à étendre les fonctionnalités de l’application en fonction du type de produit. Le client a besoin de trois types de produits : Basique, Professionnel, Ultime.
De base– compte le nombre de caractères, Professionnel – capacités Basic + imprime le texte en majuscules, Ultimate – Basic + Professional + imprime le texte disant ULTIMATE.
Nous l’implémentons en utilisant l’héritage :

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

Il est désormais nécessaire de mettre en œuvre le produit « Ultimate Light » – 8211 ; Basic + Ultimate mais sans les capacités de la version Professionnelle. Le premier OH! arrive, parce que… vous devrez créer une classe distincte pour une tâche aussi simple et dupliquer le code.
Continuons l’implémentation en utilisant l’héritage :

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

class UltimateLightVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")	
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = UltimateLightVersionFeature()
ultimateLightProduct.textOperation(text: textToFormat)

L’exemple peut être développé davantage pour plus de clarté, mais même maintenant, la complexité de la prise en charge d’un système basé sur une base d’héritage est visible – encombrant et manque de flexibilité.
Un décorateur est un ensemble de protocoles décrivant la fonctionnalité, une classe abstraite contenant une référence à une instance concrète enfant de la classe décorateur qui étend la fonctionnalité.
Réécrivons l’exemple ci-dessus en utilisant le modèle :

protocol Feature {
	func textOperation(text: String)
}

class FeatureDecorator: Feature {
	private var feature: Feature?
	
	init(feature: Feature? = nil) {
		self.feature = feature
	}
	
	func textOperation(text: String) {
		feature?.textOperation(text: text)
	}
}

class BasicVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature(feature: UltimateVersionFeature())
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature(feature: UltimateVersionFeature())
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = BasicVersionFeature(feature: UltimateVersionFeature(feature: ProfessionalVersionFeature()))
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = BasicVersionFeature(feature: UltimateVersionFeature())
ultimateLightProduct.textOperation(text: textToFormat)

Nous pouvons désormais créer des variantes de n’importe quel type de produit – il suffit d’initialiser les types combinés au stade du lancement de l’application ; l’exemple ci-dessous est la création de la version Ultimate + Professional :

ultimateProfessionalProduct.textOperation(text: textToFormat)

Sources

https://refactoring.guru/ru/design-patterns/decorator

Code source

https://gitlab.com/demensdeum/patterns

Médiateur de modèle

Le modèle Mediator fait référence à des modèles de conception comportementale.

Un jour, vous recevez une commande pour développer une application de blague – l’utilisateur appuie sur un bouton au milieu de l’écran et un drôle de bruit de canard cancan se fait entendre.
Une fois téléchargée sur l’App Store, l’application fait un carton : tout le monde cancane avec votre application, Elon Musk cancane sur son Instagram lors du prochain lancement d’un tunnel à très grande vitesse sur Mars, Hillary Clinton devance Donald Trump lors du débat. et remporte les élections en Ukraine, succès !
L’implémentation naïve de l’application ressemble à ceci :

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

Ensuite, vous décidez d’ajouter le son d’un chien qui aboie, pour cela, vous devez afficher deux boutons pour sélectionner le son – avec un canard et un chien. Créons deux classes de boutons : DuckButton et DogButton.
Changez le code :

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

Après un autre succès, nous ajoutons le son d’un cri de cochon, il y a maintenant trois classes de boutons :

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

class PigButton {
    func didPress() {
        print("oink!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

Les utilisateurs se plaignent du fait que les sons se chevauchent.
Nous ajoutons une vérification pour éviter que cela ne se produise, et en même temps nous présentons les classes les unes aux autres :

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

Suite au succès de votre candidature, le gouvernement décide de promulguer une loi selon laquelle les cancans, aboiements et grognements sur les appareils mobiles ne peuvent être effectués que de 9h00 à 15h00 le reste de la semaine ; Dans ce cas, l’utilisateur de votre application risque une peine de 5 ans de prison pour production sonore obscène à l’aide d’appareils électroniques personnels.
Changez le code :

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

Soudain, l'application de lampe de poche commence à chasser la nôtre du marché, ne la laissons pas nous vaincre et ajoutez une lampe de poche en appuyant sur le bouton « oink-oink » et sur le reste des boutons :

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class Flashlight {

    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let flashlight = Flashlight()
let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton
duckButton.flashlight = flashlight

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton
dogButton.flashlight = flashlight

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton
pigButton.flashlight = flashlight

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

En conséquence, nous avons une énorme application qui contient beaucoup de code copié-collé, les classes à l'intérieur sont reliées entre elles par un lien mort - il n'y a pas de couplage faible, un tel miracle est très difficile à maintenir et changement dans le futur en raison des fortes chances de commettre une erreur.

Utiliser le médiateur

Ajoutons une classe de médiateur intermédiaire : ApplicationController. Cette classe fournira un couplage lâche des objets, garantira la séparation des responsabilités entre les classes et éliminera le code en double.
Réécrivons :

import Foundation

class ApplicationController {

    private var isMakingSound = false
    private let flashlight = Flashlight()
    private var soundButtons: [SoundButton] = []

    func add(soundButton: SoundButton) {
        soundButtons.append(soundButton)
    }
    
    func didPress(soundButton: SoundButton) {
        flashlight.turn(on: true)
        guard Date().mobileDeviceAllowedSoundTime() && 
                isMakingSound == false else { return }
        isMakingSound = true
        soundButton.didPress()
        isMakingSound = false
    }
}

class SoundButton {
    let soundText: String
    
    init(soundText: String) {
        self.soundText = soundText
    }
    
    func didPress() {
        print(soundText)
    }
}

class Flashlight {
    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

let applicationController = ApplicationController()
let pigButton = SoundButton(soundText: "oink!")
let dogButton = SoundButton(soundText: "bark!")
let duckButton = SoundButton(soundText: "quack!")

applicationController.add(soundButton: pigButton)
applicationController.add(soundButton: dogButton)
applicationController.add(soundButton: duckButton)

pigButton.didPress()
dogButton.didPress()
duckButton.didPress()

De nombreux articles sur les architectures d'applications d'interface utilisateur décrivent le modèle MVC et ses dérivés. Le modèle est utilisé pour travailler avec des données de logique métier, la vue ou la représentation montre des informations à l'utilisateur dans l'interface/fournit une interaction avec l'utilisateur, le contrôleur est un médiateur qui assure l'interaction des composants du système.

Sources

https://refactoring.guru/ru/design-patterns/ médiateur

Code source

https://gitlab.com/demensdeum/patterns/< /p>

Strategy pattern

The Strategy pattern allows you to select the type of algorithm that implements a common interface, right while the application is running. This pattern refers to the behavioral design patterns.

Sun Tzu

Suppose we are developing a music player with embedded codecs. The built-in codecs imply reading music formats without using external sources of the operating system (codecs), the player should be able to read tracks of different formats and play them. VLC player has such capabilities, it supports various types of video and audio formats, it runs on popular and not very operating systems.

Imagine what a naive player implementation looks like:

var player: MusicPlayer?

func play(filePath: String) {
    let extension = filePath.pathExtension

    if extension == "mp3" {
        playMp3(filePath)
    }
    else if extension == "ogg" {
        playOgg(filePath)
    }
}

func playMp3(_ filePath: String) {
    player = MpegPlayer()
    player?.playMp3(filePath)
}

func playOgg(_ filePath: String) {
    player = VorbisPlayer()
    player?.playMusic(filePath)
}

Next, we add several formats, which leads to the need to write additional methods. Plus, the player must support plug-in libraries, with new audio formats that will appear later. There is a need to switch the music playback algorithm, the Strategy pattern is used to solve this problem.

Let’s create a common protocol MusicPlayerCodecAlgorithm, write the implementation of the protocol in two classes MpegMusicPlayerCodecAlgorithm and VorbisMusicPlayerCodecAlgorithm, to play mp3 and ogg files with-but. Create a class MusicPlayer, which will contain a reference for the algorithm that needs to be switched, then by the file extension we implement codec type switching:

import Foundation

class MusicPlayer {
    var playerCodecAlgorithm: MusicPlayerCodecAlgorithm?
    
	func play(_ filePath: String) {
            playerCodecAlgorithm?.play(filePath)
	}
}

protocol MusicPlayerCodecAlgorithm {
    func play(_ filePath: String)
}

class MpegMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("mpeg codec - play")
	}
}

class VorbisMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("vorbis codec - play")	
	}
}

func play(fileAtPath path: String) {
	guard let url = URL(string: path) else { return }
	let fileExtension = url.pathExtension
		
	let musicPlayer = MusicPlayer()
	var playerCodecAlgorithm: MusicPlayerCodecAlgorithm? 
		
	if fileExtension == "mp3" {
                playerCodecAlgorithm = MpegMusicPlayerCodecAlgorithm()
	}
	else if fileExtension == "ogg" {
                playerCodecAlgorithm = VorbisMusicPlayerCodecAlgorithm()
	}
		
	musicPlayer.playerCodecAlgorithm = playerCodecAlgorithm
	musicPlayer.playerCodecAlgorithm?.play(path)
}

play(fileAtPath: "Djentuggah.mp3")
play(fileAtPath: "Procrastinallica.ogg")

The above example also shows the simplest example of a factory (switching the codec type from the file extension) It is important to note that the Strategy strategy does not create objects, it only describes how to create a common interface for switching the family of algorithms.

Documentation

https://refactoring.guru/en/design-patterns/strategy

Source code

https://gitlab.com/demensdeum/patterns/

Iterator pattern

In this article I will describe the Iterator pattern.
This pattern refers to the behavioral design patterns.

Print it

Suppose we need to print a list of tracks from the album “Procrastinate them all” of the group “Procrastinallica”.
The naive implementation (Swift) looks like this:

for i=0; i < tracks.count; i++ {
    print(tracks[i].title)
}

Suddenly during compilation, it is detected that the class of the tracks object does not give the number of tracks in the count call, and moreover, its elements cannot be accessed by index. Oh…

Filter it

Suppose we are writing an article for the magazine “Wacky Hammer”, we need a list of tracks of the group “Djentuggah” in which bpm exceeds 140 beats per minute. An interesting feature of this group is that its records are stored in a huge collection of underground groups, not sorted by albums, or for any other grounds. Let’s imagine that we work with a language without functionality:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Suddenly, a couple of tracks of the group are found in the collection of digitized tapes, and the editor of the magazine suggests finding tracks in this collection and writing about them. A Data Scientist friend suggests to use the Djentuggah track classification algorithm, so you don’t need to listen to a collection of 200 thousand tapes manually. Try:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Mistakes

Now, just before sending to print, the editor reports that 140 beats per minute are out of fashion, people are more interested in 160, so the article should be rewritten by adding the necessary tracks.
Apply changes:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 160 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

The most attentive ones noticed an error; the bpm parameter was changed only for the first pass through the list. If there were more passes through the collections, then the chance of a mistake would be higher, that is why the DRY principle should be used. The above example can be developed further, for example, by adding the condition that you need to find several groups with different bpm, by the names of vocalists, guitarists, this will increase the chance of error due to duplication of code.

Behold the Iterator!

In the literature, an iterator is described as a combination of two protocols / interfaces, the first is an iterator interface consisting of two methods – next(), hasNext(), next() returns an object from the collection, and hasNext() reports that there is an object and the list is not over. However in practice, I observed iterators with one method – next(), when the list ended, null was returned from this object. The second is a collection that should have an interface that provides an iterator – the iterator() method, there are variations with the collection interface that returns an iterator in the initial position and in end – the begin() and end() methods are used in C ++ std.
Using the iterator in the example above will remove duplicate code, eliminate the chance of mistake due to duplicate filtering conditions. It will also be easier to work with the collection of tracks on a single interface – if you change the internal structure of the collection, the interface will remain old and the external code will not be affected.
Wow!

let bandFilter = Filter(key: "band", value: "Djentuggah")
let bpmFilter = Filter(key: "bpm", value: 140)
let iterator = tracksCollection.filterableIterator(filters: [bandFilter, bpmFilter])

while let track = iterator.next() {
    print("\(track.band) - \(track.title)")
}

Changes

While the iterator is running, the collection may change, thus causing the iterator’s internal counter to be invalid, and generally breaking such a thing as “next object”. Many frameworks contain a check for changing the state of the collection, and in case of changes they return an error / exception. Some implementations allow you to remove objects from the collection while the iterator is running, by providing the remove() method in the iterator.

Documentation

https://refactoring.guru/en/design-patterns/iterator

Source code

https://gitlab.com/demensdeum/patterns/

Modèle “Instantané”

Dans cet article, je décrirai le modèle « Snapshot » ; ou “Memento”

Ce modèle fait référence au modèle « comportemental ». modèles de conception.

Supposons que nous développions un éditeur graphique et que nous devions ajouter la possibilité d’annuler des actions sur une commande utilisateur. Il est également très important que les composants du système n’aient pas accès à l’état interne des « actions » annulées lors de l’implémentation de ce modèle ; les autres composants du système n’ont accès qu’à l’objet instantané sans possibilité de le modifier. son état interne, fournissant une interface externe claire et simple. Pour résoudre ce problème, le modèle « Snapshot » est utilisé. ou “Gardien”.

Exemple de travail “Snapshot” présenté ci-dessous :


Lorsque vous cliquez dessus, un sprite apparaît, lorsque vous cliquez sur la flèche courbée, l’action est annulée – Le sprite disparaît. L’exemple se compose de trois classes :

  1. Toile sur laquelle les sprites et l’interface graphique sont affichés.
  2. Contrôleur d’écran, il traite les clics et contrôle la logique de l’écran.
  3. Les états du canevas qui persistent à chaque modification sont annulés si nécessaire à l’aide du contrôleur d’écran.

Dans le contexte du modèle “Snapshot” les cours sont :

  1. Toile – source, les états de cette classe sont enregistrés sous forme de « instantanés », pour une restauration ultérieure sur demande. De plus, la source doit être capable de restaurer l’état lors du transfert d’un « instantané » vers celle-ci.
  2. Contrôleur – dépositaire, cette classe sait comment et quand enregistrer/annuler les états.
  3. État – instantané, une classe qui stocke l’état de la source, ainsi que des informations sur la date ou un index à partir duquel l’ordre de restauration peut être établi avec précision.

Une caractéristique importante du modèle est que seule la source doit avoir accès aux champs internes de l’état enregistré dans l’instantané ; cela est nécessaire pour protéger les instantanés des modifications venant de l’extérieur (des développeurs expérimentés qui souhaitent modifier quelque chose en contournant l’encapsulation). , brisant la logique du système). Pour implémenter l’encapsulation, des classes intégrées sont utilisées et, en C++, elles utilisent la possibilité de spécifier des classes amies. Personnellement, j’ai implémenté une version simple sans encapsulation pour Rise et en utilisant Generic lors de l’implémentation pour Swift. Dans ma version – Memento donne son état interne uniquement aux entités du même état de classe :

Sources

https://refactoring.guru/design-patterns/memento

Code source

https://gitlab.com/demensdeum/patterns/< /p>

Modèle de visiteur

Dans cet article, je décrirai un modèle de conception appelé « Visiteur » ; ou “Visiteur”
Ce modèle appartient au groupe des Modèles comportementaux.

Trouvons un problème

Ce modèle est principalement utilisé pour contourner la limitation de l’envoi unique dans les langues à liaison précoce.

Alice X par NFGPhoto (CC-2.0)
Créons une classe/protocole abstrait Band, créons une sous-classe de MurpleDeep, créons une classe Visitor avec deux méthodes – un pour afficher n’importe quel descendant de Band sur la console, le second pour afficher n’importe quel MurpleDeep, l’essentiel est que les noms (signatures) des méthodes soient les mêmes et que les arguments ne diffèrent que par classe. En utilisant la méthode d’impression intermédiaire avec l’argument Band, nous créons une instance de Visitor et appelons la méthode de visite pour MurpleDeep.
Vous trouverez ci-dessous le code en Kotlin :

Le résultat sera “Ceci est la classe Band

Comment est-ce possible ?!

La raison pour laquelle cela se produit est décrite avec des mots intelligents dans de nombreux articles, y compris en russe, mais je vous suggère d’imaginer comment le compilateur voit le code, peut-être que tout deviendra clair tout de suite :

Résoudre le problème

Il existe de nombreuses solutions pour résoudre ce problème. Nous examinerons ensuite une solution utilisant le modèle Visiteur.
Nous ajoutons la méthode accept avec l’argument Visitor à la classe/protocole abstrait, appelons Visitors.visit(this) à l’intérieur de la méthode, puis ajoutons une substitution/implémentation de la méthode accept à la classe MurpleDeep, violant de manière décisive et calme DRY, en écrivant à nouveau visiteur.visit(this).< br />Code final :

Sources

https://refactoring.guru/ru/ modèles de conception/double expédition des visiteurs

Code source

https://gitlab.com/demensdeum/patterns

Modèle de poids mouche

Dans cet article, je décrirai le modèle structurel « Léger » ; ou « Opportuniste » (Poids mouche)
Ce modèle appartient au groupe des Modèles structurels.

Regardons un exemple du fonctionnement du modèle ci-dessous :


Pourquoi est-ce nécessaire ? Pour économiser de la RAM. Je suis d’accord qu’à l’heure de l’utilisation généralisée de Java (qui consomme du CPU et de la mémoire pour rien), ce n’est plus si important, mais cela vaut la peine d’être utilisé.
Dans l’exemple ci-dessus, seuls 40 objets sont générés, mais si vous augmentez le nombre à 120 000, la consommation de mémoire augmentera en conséquence.
Examinons la consommation de mémoire sans utiliser le modèle flyweight dans le navigateur Chromium :

Sans utiliser de modèle, la consommation de mémoire est d’environ 300 Mo.

Ajoutons maintenant un modèle à l’application et voyons la consommation de mémoire :

En utilisant le modèle, la consommation de mémoire est d’environ 200 Mo, nous avons donc économisé 100 Mo de mémoire dans l’application de test ; dans les projets sérieux, la différence peut être beaucoup plus grande.

Comment ça marche ?

Dans l’exemple ci-dessus, nous dessinons 40 chats, ou pour plus de clarté, 120 000. Chaque chat est chargé en mémoire sous forme d’image png, puis dans la plupart des rendus, il est converti en bitmap pour le rendu (en fait bmp), ceci est fait pour la vitesse, car un png compressé prend très longtemps à rendre. Sans utiliser le modèle, nous chargeons 120 000 images de chats dans la RAM et dessinons, mais lorsque nous utilisons le modèle « léger » ; nous chargeons un chat en mémoire et le dessinons 120 000 fois avec différentes positions et transparence. Toute la magie réside dans le fait que nous implémentons les coordonnées et la transparence séparément de l’image du chat ; lors du rendu, le rendu prend un seul chat et utilise un objet avec des coordonnées et une transparence pour un rendu correct.

À quoi ça ressemble dans le code ?

Voici des exemples pour le langage Rise< /p>

Sans utiliser de modèle :


L’image du chat est chargée séparément pour chaque objet de la boucle – catImage.

Utilisation du modèle :

Une image d’un chat est utilisée par 120 000 objets.

Où est-il utilisé ?

Utilisé dans les frameworks GUI, par exemple la « réutilisation » d’Apple. (réutilisation) Cellules du tableau UITableViewCell, ce qui élève la barrière à l’entrée pour les débutants qui ne connaissent pas ce modèle. Également couramment utilisé dans le développement de jeux.

Code source

https://gitlab.com/demensdeum/patterns/< /p>

Sources

https://refactoring.guru/ru/design-patterns/ poids mouche
http://gameprogrammingpatterns.com/flyweight.html

Le Bon, la Brute et le Truand Singleton

Dans cette note, je décrirai mon expérience et celle de mes collègues lorsque j’ai travaillé avec le modèle Singleton (Singleton dans la littérature étrangère), tout en travaillant sur divers projets (réussis et moins réussis). Je décrirai pourquoi je pense personnellement que ce modèle ne peut être utilisé nulle part, et je décrirai également quels facteurs psychologiques dans l’équipe influencent l’intégration de cet anti-modèle. Dédié à tous les développeurs déchus et paralysés qui essayaient de comprendre pourquoi tout a commencé lorsqu’un des membres de l’équipe a amené un petit chiot mignon, facile à manipuler, ne nécessitant pas de soins ni de connaissances particulières pour en prendre soin, et s’est terminé avec la bête élevée. prendre votre projet en otage, nécessite de plus en plus d’heures de travail et ronge les nerfs de l’utilisateur, votre argent et crée des chiffres absolument monstrueux pour évaluer la mise en œuvre de choses apparemment simples des choses.


Wolf in sheep’s clothing by SarahRichterArt

L’histoire se déroule dans un univers alternatif, toutes les coïncidences sont aléatoires…

Carotter un chat à la maison avec Cat@Home

Toute personne, parfois dans la vie, a un désir irrésistible de caresser un chat. Les analystes du monde entier prédisent que la première startup qui a créé une application pour la livraison et la location de chats deviendra extrêmement populaire et sera rachetée dans un avenir proche par Moogle pour des milliards de dollars. Bientôt, cela arrive – un gars de Tioumen crée l’application Cat@Home et devient bientôt un milliardaire, la société Moogle obtient une nouvelle source de profit et des millions de personnes stressées ont l’opportunité de commandez un chat chez eux pour un repassage supplémentaire et calmez-vous.

Attaque des clones

Alexey Goloborodko, un dentiste extrêmement riche de Mourmansk, impressionné par un article de Forbes sur Cat@Home, décide qu’il veut aussi devenir astronomiquement riche. Pour atteindre cet objectif, grâce à ses amis, il trouve une entreprise à Goldfield – un fournisseur de services. Wakeboard DevPops, qui fournit des services de développement de logiciels, leur commande le développement d’un clone Cat@Home.

Équipe gagnante

Le projet s’appelle Fur&Pure, confié à une talentueuse équipe de développement de 20 personnes ; Concentrons-nous ensuite sur une équipe de développement mobile de 5 personnes. Chaque membre de l’équipe obtient sa part du travail, armé d’agile et de Scrum, l’équipe termine le développement dans les délais (en six mois), sans bugs, publie l’application dans l’iStore, où elle est notée 5 par 100 000 utilisateurs, il y en a beaucoup des commentaires sur la qualité de l’application et l’excellence du service (univers alternatif après tout). Les chats sont repassés, l’application est sortie, tout semble bien se passer. Cependant, Moogle n’est pas pressé d’acheter une startup pour des milliards de dollars, car non seulement des chats mais aussi des chiens sont déjà apparus dans Cat@Home.

Le chien aboie, la caravane avance

Le propriétaire de l’application décide qu’il est temps d’ajouter des chiens à l’application, demande une évaluation à l’entreprise et dispose d’environ au moins six mois pour ajouter des chiens à l’application. En fait, l’application sera réécrite à partir de zéro. Pendant ce temps, Moogle ajoutera des serpents, des araignées et des cobayes à l’application, et Fur&Pur ne recevra que des chiens.
Pourquoi est-ce arrivé ? Le manque d’architecture d’application flexible est à l’origine de tout ; l’un des facteurs les plus courants est l’anti-modèle Singleton.

Qu’est-ce qui ne va pas ?

Pour commander un chat à la maison, le consommateur doit créer une demande et l’envoyer au bureau, où le bureau la traitera et enverra un coursier avec le chat, le coursier recevra déjà le paiement du service.
L’un des programmeurs décide de créer une classe « Cat Application » ; avec les champs nécessaires, introduit cette classe dans l’espace d’application global via un singleton. Pourquoi fait-il ça ? Pour gagner du temps (une économie d’un centime d’une demi-heure), car il est plus facile de rendre une application publique que de réfléchir à l’architecture de l’application et d’utiliser l’injection de dépendances. Ensuite, d’autres développeurs récupèrent cet objet global et y lient leurs classes.
Par exemple, tous les écrans eux-mêmes accèdent à l’objet global “Cat Request” et afficher les données sur l’application. En conséquence, une telle application monolithique est testée et publiée.
Tout semble aller bien, mais tout à coup, un client apparaît avec l’obligation d’ajouter des demandes de chiens à l’application. L’équipe commence frénétiquement à évaluer combien de composants du système seront affectés par ce changement. A l’issue de l’analyse, il s’avère qu’il faut refaire de 60 à 90% du code afin d’apprendre à l’application à accepter non seulement “Request For Cat” mais aussi “Demande pour un Chien”, il est déjà inutile d’évaluer l’ajout d’autres animaux à ce stade, pour en gérer au moins deux.

Comment empêcher le singleton

Tout d’abord, au stade de la collecte des exigences, indiquez explicitement la nécessité de créer une architecture flexible et extensible. Deuxièmement, il convient de procéder en parallèle à un examen indépendant du code du produit, avec une recherche obligatoire des points faibles. Si vous êtes développeur et que vous aimez les singletons, alors je vous suggère de reprendre vos esprits avant qu’il ne soit trop tard, sinon les nuits blanches et les nerfs à vif sont garantis. Si vous travaillez sur un projet existant comportant de nombreux singletons, essayez de vous en débarrasser le plus rapidement possible, ainsi que du projet.
Vous devez passer de l’anti-modèle d’objets/variables globaux singletons à l’injection de dépendances – le modèle de conception le plus simple dans lequel toutes les données nécessaires sont fournies à une instance d’une classe au stade de l’initialisation, sans qu’il soit nécessaire de les lier à l’espace global.

Sources

https://stackoverflow. com/questions/137975/what-is-so-bad-about-singletons
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
https://blog.ndepend.com/singleton-pattern-costs/