Construire un projet avec des dépendances pour Emscripten

Dans cet article, je décrirai la construction d’un projet composé de plusieurs bibliothèques utilisant Emscripten.
Pour le moment, Emscripten ne prend pas en charge la création de bibliothèques partagées, la première étape consiste donc à transférer toutes les bibliothèques de Partagé vers Statique. Emscripten fonctionne avec ses propres fichiers d’inclusion, le problème de visibilité des fichiers d’en-tête doit donc être résolu en transmettant un lien symbolique du répertoire système vers la chaîne d’outils Emscripten :

ln -s /usr/local/include/FlameSteelFramework $EMSDK/fastcomp/emscripten/system/include/FlameSteelFramework

Si vous utilisez CMake, vous devez alors modifier SHARED->STATIC dans le fichier CMakeLists.txt de la méthode add_library. Vous pouvez créer une bibliothèque/application pour des liens statiques supplémentaires à l’aide des commandes :

emcmake cmake .
emmake make

Ensuite, vous devrez construire l’application principale en spécifiant les fichiers de bibliothèque *.a au stade de la liaison. Je n’ai pas pu spécifier un chemin relatif ; la construction s’est terminée correctement uniquement après avoir spécifié les chemins complets dans le fichier CMakeLists.txt :

elseif(EMSCRIPTEN)
target_link_libraries(${FSEGT_PROJECT_NAME} GL GLEW 
/home/demensdeum/Sources/cube-art-project-bootstrap/cube-art-project/sharedLib/libCubeArtProject.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkitFSGL/libFlameSteelEngineGameToolkitFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkit/libFlameSteelEngineGameToolkit.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/libFlameSteelCore.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/libFlameSteelBattleHorn.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FSGL/libFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/libFlameSteelCommonTraits.a)
else()

Sources

https://emscripten.org/ docs/compiling/Building-Projects.html#using-libraries

Bibliothèque partagée CMake C++

J’ai récemment décidé de séparer toutes les parties des bibliothèques partagées FlameSteelFramework, puis je montrerai un exemple de fichier CMakeLists.txt pour FlameSteelCore :

cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

Commandes exécutées par CMake : collecte tous les fichiers avec l’extension *.cpp du répertoire src/FlameSteelCore/ dans une bibliothèque partagée, copie tous les en-têtes avec l’extension *.h de src/FlameSteelCore pour inclure/FlameSteelFramework (dans mon cas c’est /usr/ local/include/FlameSteelFramework), copie la bibliothèque partagée dans le répertoire lib (/usr/local/lib)
Après l’installation, il peut être nécessaire de mettre à jour le cache LD – sudo ldconfig.
Pour construire et installer sur Ubuntu (si vous disposez de la bonne chaîne d’outils de construction), exécutez simplement les commandes suivantes :

cmake . && make && sudo make install

Pour tester le processus d’installation, je passe le préfixe make au dossier local makeInstallTestPlayground :

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

Références

https : //stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install

Interpréteur de langage C++ – S’accrocher

Il n’y a pas longtemps, je suis tombé sur un projet intéressant appelé Cling, un interpréteur de langage C++ qui peut, entre autres, fonctionner de manière interactive depuis la console. Vous pouvez consulter le projet sur le lien suivant : https://github.com/root -projet/ s’accrocher
L’installation pour Ubuntu est simple : téléchargez l’archive pour la version requise, décompressez-la, accédez au dossier bin et exécutez cling dans le terminal.
Vous trouverez ci-dessous un exemple de chargement de la bibliothèque FlameSteelCore, d’initialisation de l’objet, d’impression de l’identifiant :

Exceptions Emscripten perdues et problèmes d’expression régulière

Exception perdue

Une fonctionnalité intéressante d’Emscripten : lorsque vous démarrez une boucle de jeu via emscripten_set_main_loop, vous devez vous rappeler que la gestion des exceptions doit être ré-ajoutée via try catch directement dans la méthode de boucle, car Le runtime perd le bloc try catch de l’extérieur.
Le moyen le plus simple est d’afficher le texte d’erreur à l’aide du navigateur à l’aide de l’alerte javascript :

            catch (const std::exception &exc)
            {
                const char *errorText = exc.what();
                cout << "Exception: " << errorText << "; Stop execution" << endl;

                EM_ASM_(
                {
                    var errorText = UTF8ToString($0);
                    alert(errorText);

                }, errorText);

                abort();

Expression rationnelle trop complexe

L'implémentation std de regex peut lever une exception error_complexity si elle considère que l'expression régulière est trop complexe. Cela se produit dans l'implémentation actuelle d'emscripten, je vous suggère donc d'implémenter des tests d'analyse via des expressions régulières ou d'utiliser des implémentations d'expressions régulières tierces.

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

Masque mortel 1.0 !

Aujourd’hui, il existe une version du jeu Death-Mask, créée à partir de zéro avec Flame Steel Engine, Flame Steel Engine Game Toolkit et d’autres bibliothèques.

https://demensdeum.com/games/deathMask/< /p>

Dans le jeu, vous jouerez le rôle d’un gars nommé Revil-Razorback, il veut prendre possession d’un artefact qui donne une vie sans fin – Masque de la Mort. En vous promenant à la recherche, vous mourrez aux mains de drones de très nombreuses fois jusqu’à ce que vous trouviez ce que vous cherchez.

Des mises à jour des modèles 3D et des modifications mineures du gameplay sont possibles dans un avenir proche.

Masque mortuaire – aventure cyber-fantastique dans un techno-labyrinthe sans fin. Préparez-vous à mourir !

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

Bêta sauvage de Death-Mask

Le jeu Death-Mask entre en version bêta publique (wild beta)
L’écran du menu principal du jeu a été repensé, une vue de la zone bleue du techno-labyrinthe a été ajoutée, avec une musique agréable en fond.

Ensuite, je prévois de retravailler le contrôleur de jeu, d’ajouter des mouvements fluides comme dans les anciens jeux de tir, des modèles 3D de haute qualité de boîtes, d’armes, d’ennemis, la possibilité de passer à d’autres niveaux du techno-labyrinthe non seulement via des portails ( ascenseurs, portes, chutes à travers des trous dans le sol, trous dans les murs), je vais ajouter un peu de variété à l’environnement du labyrinthe généré. Je travaillerai également sur l’équilibre du jeu.
Une animation squelettique sera ajoutée en tant que phase de peaufinage avant la sortie.< /p>

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

Animation squelettique (Partie 1 – shader)

Dans cet article, je décrirai ma compréhension de l’animation squelettique, qui est utilisée dans tous les moteurs 3D modernes pour animer des personnages, des environnements de jeu, etc.
Je vais commencer la description par la partie la plus tangible : vertex shader, car l’ensemble du chemin de calcul, aussi complexe soit-il, se termine par le transfert des données préparées pour l’affichage vers le vertex shader.

L’animation du squelette, après avoir été traitée sur le CPU, passe dans le vertex shader.
Laissez-moi vous rappeler la formule du sommet sans animation squelettique :
gl_Position = projectionMatrix * viewMatrix * modelMatrix * sommet;
Pour ceux qui ne comprennent pas comment est née cette formule, vous pouvez lire mon article décrivant le principe du travail avec des matrices pour afficher du contenu 3D dans le contexte d’OpenGL.
Pour le reste – formule pour implémenter l’animation squelettique :
” vec4 animéVertex = bone0matrix * sommet * bone0weight +”
“bone1matrix * sommet * bone1weight +”
“bone2matrix * sommet * bone2weight +”
“bone3matrix * sommet * bone3weight;\n”
” gl_Position = projectionMatrix * viewMatrix * modelMatrix * animationVertex;\n”

C’est-à-dire que nous multiplions la matrice de transformation osseuse finale par le sommet et par le poids de cette matrice par rapport au sommet. Chaque sommet peut être animé par 4 os, la force de l’impact est régulée par le paramètre poids de l’os, la somme des impacts doit être égale à un.
Que faire si moins de 4 os affectent le sommet ? Nous devons diviser le poids entre eux et rendre l’impact du reste égal à zéro.
Mathématiquement, multiplier un poids par une matrice est appelé « multiplication matricielle-scalaire ». La multiplication par un scalaire vous permet de résumer l’effet des matrices sur le sommet résultant.

Les matrices de transformation osseuse elles-mêmes sont transmises sous forme de tableau. De plus, le tableau contient des matrices pour l’ensemble du modèle dans son ensemble, et non pour chaque maillage séparément.

Mais pour chaque sommet les informations suivantes sont transmises séparément :
– Index de la matrice qui affecte le sommet
– Poids de la matrice qui affecte le sommet
Plus d’un os est transmis, généralement l’effet de 4 os sur le sommet est utilisé.
Aussi, la somme des poids des 4 dés doit toujours être égale à un.
Voyons ensuite à quoi cela ressemble dans le shader.
Tableau matriciel :
“uniform mat4 bonesMatrices[kMaxBones];”

Informations sur l’effet de 4 os sur chaque sommet :
“attribut vec2 bone0info;”
“attribut vec2 bone1info;”
“attribut vec2 bone2info;”
“attribut vec2 bone3info;”

vec2 – dans la coordonnée X, nous stockons l’index de l’os (et le convertissons en int dans le shader), dans la coordonnée Y, nous stockons le poids de l’impact de l’os sur le sommet. Pourquoi devez-vous transmettre ces données dans un vecteur bidimensionnel ? Parce que GLSL ne prend pas en charge la transmission de structures lisibles en C avec des champs valides au shader.

Ci-dessous, je vais donner un exemple d’obtention des informations nécessaires à partir d’un vecteur pour une substitution ultérieure dans la formule animationVertex :

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Maintenant, la structure des sommets renseignée sur le processeur devrait ressembler à ceci :
x, y, z, u, v, bone0index, bone0weight, bone1index, bone1weight, bone2index, bone2weight, bone3index, bone3weight

La structure du vertex buffer est remplie une fois lors du chargement du modèle, mais les matrices de transformation sont transférées du CPU vers le shader à chaque image de rendu.

Dans les parties restantes, je décrirai le principe de calcul de l’animation sur le CPU, avant de la transférer vers le vertex shader, je décrirai l’arbre des nœuds osseux, en parcourant la hiérarchie animation-modèle-nœuds-mesh, la matrice interpolation.

Sources

http://ogldev.atspace.co. fr/www/tutorial38/tutorial38.html

Code source

https://gitlab.com/demensdeum/skeletal-animation

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/

Rapport de développement 1 de Death Mask

Nouvelle section non permanente « Journaux des développeurs » ; ou Dev Diary dans un style étranger.
Le développement du jeu Death-Mask bat son plein, le logo du moteur a été ajouté pour les jeux Flame Steel Engine 2019, écran de sélection de la carte initiale par île (vert, rouge, noir, blanc), sortie de textures pour les murs, le plafond, le sol du labyrinthe, augmentation de la taille de l’aire de jeu.


Carte de la ville de la zone rouge

Ensuite, nous prévoyons d’ajouter des modèles 3D pour l’environnement, au lieu des sprites de style Doom, et nous prévoyons d’ajouter des modèles pour les armes, les boîtes, les ennemis et les amis. Dans le gameplay, il est prévu d’ajouter de la monnaie, des magasins, la possibilité d’acheter des parties de la carte du jeu indiquant des endroits intéressants avec du butin et l’emplacement possible du « Masque de la mort ». Je souhaite également ajouter la possibilité d’embaucher des compagnons pour voyager à travers le cyber-labyrinthe.
Suivez l’actualité.

Swift 4.2.3 – Ubuntu 18.10

Créer Swift avec les bibliothèques nécessaires pour fonctionner sur Ubuntu 18.10. Dernière version disponible sur le site Apple – pour Ubuntu 18.04. Basé sur l’assemblage du site officiel avec l’ajout de bibliothèques d’Ubuntu 18.04. Ajout également d’un exemple de script pour ajouter PATH et LD_LIBRARY_PATH pour le terminal bash :
http://www.mediafire.com/file/lrs74mvoj3fti03/swift4.2.3.ubuntu.18.10.x86_64.tar.gz/file

Langage déclaratif Zakaz

Je présente à votre attention un langage de programmation purement déclaratif – Zakaz. L’idée principale du nouveau langage – l’application contient des commandes d’exécution écrites sous forme libre qui doivent être exécutées par des « exécuteurs ». Si aucun « interprète » ; ne peut pas exécuter la commande, l’exécution du programme s’arrête. Les candidatures sont appelées spécifications techniques (tez) et doivent avoir une extension .tez. La syntaxe Zakaz nécessite deux règles :

  • Chaque commande commence sur une nouvelle ligne
  • Chaque commande doit être écrite dans un langage formel compréhensible par les humains

Exemple Hello World.tez :

Afficher le texte "Hello World" à l'écranAfficher le texte "Exemple Zakaz 'tez'" à l'écran

Un exemple de spécification affichant une description du principe de fonctionnement et d’ouverture du site http://demensdeum.com dans le navigateur Firefox

Afficher le texte "Afficher la démo du site Web" à l'écranAfficher "Vous devez installer Firefox sur votre système pour exécuter ce 'tez', et il doit être appelable via \"system\" Fonction C" texte à l'écranAfficher "Il devrait également y avoir \"FirefoxPerformer\" attribué au Zakaz Runtime, veuillez consulter le manuel pour plus d'informations" texte à l'écranAfficher le site Web avec l'adresse "http://demensdeum.com" dans Firefox

Vous devez exécuter l’exemple ci-dessus avec l'”exécuteur” ; FirefoxPerformer, capable de traiter la dernière commande pour afficher un site via Firefox

./ZakazRuntime openDemensdeumSite.tez FirefoxPerformer

Pour implémenter votre exécuteur, vous devez l’implémenter en tant que bibliothèque dynamique en utilisant la classe abstraite ZakazRuntime::Performer, et le renvoyer avec un pointeur intelligent de la méthode de fonction globale createPerformer(). Vous pouvez utiliser l’implémentation de FirefoxPerformer comme exemple.

Code source

https://gitlab.com/demensdeum/zakaz