Connectez-vous et déconnectez-vous des vues. Modèle Modèle-Mise à jour-Vue et types dépendants Ensemble de vues HTML de déconnexion

Django est livré avec de nombreuses ressources intégrées pour les cas d'utilisation les plus courants d'une application Web. L’application d’enregistrement est un très bon exemple et l’avantage est que les fonctionnalités peuvent être utilisées directement.

Avec l'application d'enregistrement Django, vous pouvez profiter des fonctionnalités suivantes :

  • Se connecter
  • Se déconnecter
  • S'inscrire
  • Réinitialisation du mot de passe

Dans ce didacticiel, nous nous concentrerons sur les fonctionnalités de connexion et de déconnexion. Pour vous inscrire et réinitialiser votre mot de passe, consultez les didacticiels ci-dessous :

Commencer

Avant de commencer, assurez-vous d'avoir django.contrib.auth dans votre INSTALLED_APPS et le middleware d'authentification correctement configuré dans les paramètres MIDDLEWARE_CLASSES.

Les deux sont déjà configurés lorsque vous démarrez un nouveau projet Django à l'aide de la commande startproject . Donc, si vous n’avez pas supprimé les configurations initiales, vous devriez être prêt.

Si vous démarrez un nouveau projet juste pour suivre ce tutoriel, créez un utilisateur en utilisant la ligne de commande afin que nous puissions tester les pages de connexion et de déconnexion.

$ python manage.py créer un superutilisateur

A la fin de cet article je fournirai le code source de l'exemple avec la configuration minimale.

Configurer les routes d'URL

Importez d’abord le module django.contrib.auth.views et ajoutez une route URL pour les vues de connexion et de déconnexion :

à partir de django.conf.urls importer l'URL de django.contrib importer l'administrateur de django.contrib.auth importer des vues en tant que auth_views urlpatterns = [ url (r"^login/$" , auth_views . login , name = "login" ), url ( r"^logout/$" , auth_views . déconnexion , nom = "logout" ), url (r"^admin/" , admin . site . urls ), ]

Créer un modèle de connexion

Par défaut, la vue django.contrib.auth.views.login tentera de restituer le modèle Registration/login.html. La configuration de base consisterait donc à créer un dossier nommé Registration et à y placer un modèle login.html.

Suivant un modèle de connexion minimal :

(% étend "base.html" %) (% titre du bloc %)Connexion(% endblock %) (% contenu du bloc %)

Se connecter

(% csrf_token %) (( form.as_p ))
(%bloc final%)

Cet exemple simple valide déjà le nom d'utilisateur et le mot de passe et authentifie correctement l'utilisateur.

Personnalisation de la vue de connexion

Il existe quelques paramètres que vous pouvez transmettre à la vue de connexion pour l'adapter à votre projet. Par exemple, si vous souhaitez stocker votre modèle de connexion ailleurs que Registration/login.html, vous pouvez transmettre le nom du modèle en paramètre :

url (r"^login/$" , auth_views . login , ( "template_name" : "core/login.html" ), nom = "login" ),

Vous pouvez également transmettre un formulaire d'authentification personnalisé à l'aide du paramètre identifier_form , au cas où vous auriez implémenté un modèle utilisateur personnalisé.

Désormais, une configuration très importante est effectuée dans le fichier settings.py, qui est l'URL que Django redirigera l'utilisateur après une authentification réussie.

Dans le fichier settings.py, ajoutez :

LOGIN_REDIRECT_URL = "accueil"

La valeur peut être une URL codée en dur ou un nom d'URL. La valeur par défaut de LOGIN_REDIRECT_URL est /accounts/profile/ .

Il est également important de noter que Django tentera de rediriger l'utilisateur vers le prochain paramètre GET.

Configuration de la vue de déconnexion

Après avoir accédé à la vue django.contrib.auth.views.logout, Django affichera le modèle Registration/logged_out.html. De la même manière que nous l'avons fait dans la vue de connexion, vous pouvez transmettre un modèle différent comme ceci :

url (r"^logout/$" , auth_views . logout , ( "template_name" : "logged_out.html" ), name = "logout" ),

Habituellement, je préfère utiliser le paramètre next_page et rediriger soit vers la page d'accueil de mon projet, soit vers la page de connexion lorsque cela a du sens.

Cet exemple montre comment se déconnecter automatiquement avec la configuration de sécurité Spring par défaut.

Pour se déconnecter, il suffit d'accéder à l'URL "/logout" avec la requête POST.

Dans le formulaire POST/logout, nous devons également inclure le jeton CSRF, qui est une protection contre les attaques CSRF.

Voyons l'exemple comment procéder.

Classe de configuration Java

@Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan public class AppConfig étend WebSecurityConfigurerAdapter ( protected void configure(HttpSecurity http) lève une exception ( http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin(); ) @Override public void configure (AuthenticationManagerBuilder builder) lève une exception ( builder.inMemoryAuthentication() .withUser("joe") .password("123") .roles("ADMIN"); ) @Bean public ViewResolver viewResolver() ( InternalResourceViewResolver viewResolver = new InternalResourceViewResolver (); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp");

Notez que, dans la configuration ci-dessus, nous remplaçons également configure(HttpSecurity http) pour omettre l'authentification de base par défaut (voir la méthode d'origine dans le code source de WebSecurityConfigurerAdapter) et utiliser l'authentification basée sur un formulaire. Nous le faisons parce que les navigateurs mettent en cache les informations d'authentification de base de manière agressive (après la première connexion réussie) et qu'il n'existe aucun moyen de déconnecter l'utilisateur dans la session en cours. Dans la plupart des exemples, nous n'utiliserons pas le mécanisme d'authentification de base.

Un contrôleur

@Controller public class ExempleController ( @RequestMapping("/") public String handleRequest2(ModelMap map) ( map.addAttribute("time", LocalDateTime.now().toString()); return "ma-page"; ) )

La page JSP

src/main/webapp/WEB-INF/views/my-page.jsp

Exemple de sécurité Spring

Heure : $(heure)

Pour essayer des exemples, exécutez Tomcat intégré (configuré dans pom.xml de l'exemple de projet ci-dessous) :

Mvn tomcat7:run-war

Sortir

L'accès initial à l'URI "/" sera redirigé vers "/login" :

Après avoir soumis le nom d'utilisateur et le mot de passe lors de la configuration dans notre classe AppConfig :

En cliquant sur le bouton « Déconnexion » :


Exemple de projet

Dépendances et technologies utilisées :

  • spring-security-web 4.2.3.RELEASE : spring-security-web.
  • spring-security-config 4.2.3.RELEASE : spring-security-config.
  • spring-webmvc 4.3.9.RELEASE : Spring Web MVC.
  • javax.servlet-api 3.1.0 API de servlet Java
  • JDK 1.8
  • Maven3.3.9

Modifier le fichier URL.py applications compte:

à partir de django.conf.urls, importez l'URL à partir de . importer des vues urlpatterns = [ # vue de connexion précédente # url(r"^login/$", vues.user_login, name="login"),# URL de connexion/déconnexion url(r"^login/$" , "django.contrib.auth.views.login", nom="connexion" ), url(r"^logout/$" , "django.contrib.auth.views.logout", nom="logout" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login", nom="logout_then_login" ), ]

Nous avons commenté le modèle d'URL pour la vue Utilisateur en ligne, créé précédemment pour utiliser la vue se connecter Django.

Créez un nouveau répertoire dans le répertoire des modèles d'application compte et nomme-le inscription. Créez un nouveau fichier dans un nouveau répertoire, nommez-le connexion.html

(% extends "base.html" %) (% titre du bloc %)Connexion(% endblock %) (% contenu du bloc %)

Se connecter

(% si erreurs de formulaire %)

Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Veuillez réessayer.

(%autre%)

Veuillez utiliser le formulaire suivant pour vous connecter :

(%fin si%) (%bloc final%)

Ce modèle de connexion est très similaire à celui créé précédemment. Django utilise Formulaire d'authentification, situé dans django.contrib.auth.forms. Ce formulaire tente d'authentifier l'utilisateur et génère une erreur de validation si le nom d'utilisateur était incorrect. Dans ce cas, nous pouvons rechercher les erreurs à l'aide de la commande (% if form.errors %) . Veuillez noter que nous avons ajouté un élément caché pour envoyer la valeur d'une variable nommée suivant.

Paramètre suivant doit être une URL. Si ce paramètre est spécifié, une fois que l'utilisateur s'est connecté, il est redirigé vers l'URL spécifiée.

Créez maintenant un modèle déconnecté_out.html dans le répertoire des modèles inscription et collez-y le code suivant :

(% extends "base.html" %) (% titre du bloc %)Déconnecté(% endblock %) (% contenu du bloc %)

Déconnecté

Vous avez été déconnecté avec succès. Vous pouvez vous reconnecter.

(%bloc final%)

Il s'agit du modèle qui sera affiché une fois que l'utilisateur se connectera.

Après avoir ajouté les modèles d'URL et les modèles pour les vues d'entrée et de sortie, le site est prêt pour les connexions à l'aide des vues d'authentification de Django.

Veuillez noter que la présentation logout_then_login inclus dans notre urlconf, n'a pas besoin de modèle puisqu'il redirige vers se connecter voir.

Créons maintenant une nouvelle vue pour afficher un tableau de bord pour l'utilisateur afin que nous sachions quand l'utilisateur se connecte à son compte. Ouvrez le fichier vues.py applications compte et ajoutez-y le code suivant :

depuis django.contrib.auth.decorators import login_required @login_required tableau de bord def (demande) : return render(request, "account/dashboard.html" , ("section" : "dashboard" ))

Nous ajoutons un décorateur à notre vue Connexion requise cadre d'authentification. Décorateur Connexion requise vérifie si l'utilisateur actuel est authentifié. Si l'utilisateur est authentifié, la soumission sera exécutée ; Si l'utilisateur n'est pas authentifié, il sera redirigé vers la page de connexion.

Nous avons également défini une variable section. Nous allons utiliser cette variable pour suivre quelle section du site l'utilisateur regarde.

Vous devez maintenant créer un modèle pour la vue du tableau de bord. Créer un nouveau fichier dans les modèles/compte modèles/compte/ et nomme-le tableau de bord.html :

(% étend "base.html" %) (% titre du bloc %)Dashboard(% endblock %) (% contenu du bloc %)

Tableau de bord

Bienvenue sur votre tableau de bord.

(%bloc final%)

Ajoutez ensuite le modèle d'URL suivant pour ce fichier de modification URL.py applications compte:

Modèles d'URL = [ # ... url(r"^$" , vues.dashboard, name="dashboard" ), ]

Maintenant, éditez le fichier paramètres.py:

depuis django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy("dashboard" ) LOGIN_URL = reverse_lazy("login" ) LOGOUT_URL = reverse_lazy("logout" )
  • LOGIN_REDIRECT_URL: Indique vers quelle URL rediriger l'utilisateur après la connexion.
  • LOGIN_URL: URL pour rediriger l'utilisateur vers la connexion (par exemple, en utilisant un décorateur Connexion requise)
  • LOGOUT_URL: URL pour rediriger l'utilisateur vers la sortie

Nous allons maintenant ajouter des liens de connexion et de déconnexion à notre modèle de base.

Pour ce faire, il est nécessaire de déterminer si l'utilisateur actuel est connecté ou non afin d'afficher un lien correspondant à l'état actuel de l'utilisateur. L'utilisateur actuel est spécifié dans Demande Http objet de la classe intermédiaire d'authentification. On peut y accéder en utilisant demande.utilisateur. La requête trouvera un objet utilisateur, même si l'utilisateur n'est pas authentifié. Utilisateur non authentifié, spécifié dans la demande en tant qu'instance Utilisateur anonyme. La meilleure façon de vérifier l'état d'authentification de l'utilisateur actuel est d'appeler requête.user.is_authenticated()

Modifier dans le modèle base.html

avec en-tête ID :

Comme vous pouvez le constater, le menu du site s'affiche uniquement pour les utilisateurs authentifiés. Nous vérifions également la section actuelle pour ajouter l'attribut de classe sélectionné à l'élément correspondant

  • pour mettre en évidence la section actuelle dans le menu en utilisant CSS. Il affiche également le nom d'utilisateur et un lien pour se déconnecter si l'utilisateur est authentifié, ou un lien pour se connecter.

    Ouvrez http://127.0.0.1:8000/account/login/ dans votre navigateur. Vous devriez voir une page de connexion. Entrez un identifiant et un mot de passe valides. Vous verrez ce qui suit :

    Vous pouvez voir que la section Mon tableau de bord est mise en évidence avec CSS car elle possède une classe choisi. Une fois l'utilisateur authentifié, le nom d'utilisateur est affiché sur le côté droit de l'en-tête. Cliquer sur le lien Se déconnecter. Vous verrez la page suivante :

    Sur cette page, vous pouvez voir que l'utilisateur est déconnecté et que le menu du site Web n'est donc plus affiché. Le lien sur le côté droit de l'en-tête s'affiche désormais Se connecter.

    Si vous voyez la page de déconnexion du site d'administration de Django plutôt que votre propre page de déconnexion, vérifiez vos paramètres INSTALLED_APPS et assurez-vous que django.contrib.admin c'est après compte. Les deux modèles se trouvent dans le même chemin relatif et le chargeur de modèles Django utilisera le premier qu'il trouvera.

    Principalement pour développer des interfaces utilisateur. Pour l'utiliser, vous devez créer un type Modèle qui représente l'état complet du programme, un type Message qui décrit les événements environnementaux externes auxquels le programme doit répondre en changeant son état, une fonction de mise à jour qui crée un nouvel état du programme. à partir de l'ancien état et du message, et une fonction de visualisation qui, en fonction de l'état du programme, calcule les impacts requis sur l'environnement externe, qui génèrent des événements de type Message. Le modèle est très pratique, mais il présente un petit inconvénient : il ne vous permet pas de décrire quels événements ont du sens pour des états de programme spécifiques.

    Un problème similaire se pose (et est résolu) lors de l’utilisation du modèle State OO.

    Le langage Elm est simple, mais très strict : il vérifie que la fonction de mise à jour gère d'une manière ou d'une autre toutes les combinaisons possibles d'états de modèle et d'événements de message. Par conséquent, vous devez écrire du code supplémentaire, quoique trivial, qui laisse généralement le modèle inchangé. Je veux démontrer comment cela peut être évité dans des langages plus complexes - Idris, Scala, C++ et Haskell.

    Tout le code présenté ici est disponible sur GitHub pour expérimentation. Regardons les endroits les plus intéressants.


    La fonction msg est inhabituelle : elle renvoie un type, pas une valeur. Pendant l'exécution, on ne sait rien des types valeur - le compilateur efface toutes les informations inutiles. Autrement dit, une telle fonction ne peut être appelée qu'au stade de la compilation.

    MUV est un constructeur. Il accepte des paramètres : model - l'état initial du programme, updater - une fonction de mise à jour de l'état lors d'un événement externe et view - une fonction de création d'une vue externe. Notez que le type des fonctions de mise à jour et d'affichage dépend de la valeur du modèle (en utilisant la fonction msg à partir des paramètres de type).

    Voyons maintenant comment lancer cette application

    MuvRun : (Application modelType msgType IO) -> IO a muvRun (vue de mise à jour du modèle MUV) = do msg<- view model muvRun (MUV (updater model msg) updater view)
    Nous avons choisi une opération d'entrée/sortie comme représentation externe (vue) (dans Idris, comme dans Haskell, les opérations d'entrée/sortie sont des valeurs de première classe ; pour qu'elles soient exécutées, des actions supplémentaires doivent être entreprises, renvoyant généralement une telle opération de la fonction principale).

    En bref sur IO

    Lors de l'exécution d'une opération de type (IO a), un certain impact sur le monde extérieur se produit, éventuellement vide, et une valeur de type a est renvoyée au programme, mais les fonctions de la bibliothèque standard sont conçues de telle manière qu'elle puisse être traité uniquement en générant une nouvelle valeur de type IO b. De cette façon, les fonctions pures sont séparées des fonctions ayant des effets secondaires. C'est inhabituel pour de nombreux programmeurs, mais cela permet d'écrire du code plus fiable.


    Puisque la fonction muvRun génère des E/S, elle devrait renvoyer IO, mais comme elle ne se terminera jamais, le type d'opération peut être n'importe quoi - IO a.

    Décrivons maintenant les types d'entités avec lesquelles nous allons travailler

    Modèle de données = Déconnecté | Données de chaîne connectées MsgOuted = Données de chaîne de connexion MsgIned = Déconnexion | Accueil msgType total : Modèle -> Type msgType Logouted = MsgOuted msgType (Connecté _) = MsgIned
    Nous décrivons ici un type de modèle qui reflète la présence de deux états d'interface : l'utilisateur n'est pas connecté et l'utilisateur avec un nom de type String est connecté.

    Nous décrivons ensuite deux différents types de messages pertinents pour différentes variantes du modèle - si nous sommes déconnectés, nous ne pouvons nous connecter que sous un certain nom, et si nous sommes déjà connectés, nous pouvons soit nous déconnecter, soit dire bonjour. Idris est un langage fortement typé qui ne permet pas de mélanger différents types.

    Et enfin, une fonction qui définit la correspondance de la valeur du modèle avec le type de message.

    La fonction est déclarée totale - c'est-à-dire qu'elle ne doit pas planter ou se bloquer, le compilateur essaiera de surveiller cela. msgType est appelé au moment de la compilation, et sa totalité signifie que la compilation ne se bloquera pas à cause de notre erreur, bien qu'elle ne puisse pas garantir que l'exécution de cette fonction épuisera les ressources du système.
    Il est également garanti qu'il n'exécutera pas "rm -rf /" car il n'y a pas d'IO dans sa signature.

    Décrivons le programme de mise à jour :

    Programme de mise à jour total : (m:Modèle) -> (msgType m) -> Programme de mise à jour du modèle Déconnecté (Nom de connexion) = Nom de connexion du programme de mise à jour (Nom de connexion) Déconnexion = Programme de mise à jour déconnecté (Nom de connexion) Greet = Nom de connexion
    Je pense que la logique de cette fonction est claire. Je voudrais souligner encore une fois la totalité - cela signifie que le compilateur Idris vérifiera que nous avons pris en compte toutes les alternatives autorisées par le système de types. Elm effectue également cette vérification, mais il ne peut pas savoir que nous ne pouvons pas nous déconnecter si nous ne sommes pas encore connectés et nécessitera un traitement explicite de la condition.

    Programme de mise à jour déconnecté Déconnexion = ???
    Idris trouvera des incompatibilités de type lors d'une vérification inutile.

    Passons maintenant à la vue - comme d'habitude dans l'interface utilisateur, ce sera la partie la plus difficile du code.

    Total loginPage : IO MsgOuted loginPage = do putStr "Login : " map Login getLine total genMsg : String -> MsgIned genMsg "" = Déconnexion genMsg _ = Saluer total workPage : String -> IO MsgIned workPage name = do putStr ("Bonjour, " ++ nom ++ "\n") putStr "Saisissez une chaîne vide pour la déconnexion ou non vide pour le message d'accueil\n" map genMsg getLine vue totale : (m : modèle) -> vue IO (msgType m) Logouted = loginPage view (Nom de connexion ) = nom de la page de travail
    view doit créer une opération d’E/S qui renvoie des messages, dont le type dépend encore une fois de la valeur du modèle. Nous avons deux options : loginPage, qui imprime un message "Login :", lit une chaîne du clavier et l'enveloppe dans un message de connexion, et workPage avec un paramètre de nom d'utilisateur, qui imprime un message d'accueil et renvoie des messages différents (mais du même type). - MsgIned) selon que l'utilisateur saisit une chaîne vide ou non vide. view renvoie l'une de ces opérations en fonction de la valeur du modèle, et le compilateur vérifie leur type, même s'il est différent.

    Nous pouvons maintenant créer et exécuter notre application

    Application : Modèle d'application Main.msgType IO app = MUV Vue du programme de mise à jour déconnecté principale : IO () main = application muvRun
    Un point subtil doit être noté ici : la fonction muvRun renvoie IO un, où a n'a pas été spécifié et la valeur main est de type IO(), Où () est le nom d'un type habituellement appelé Unité, qui a une valeur unique, également écrite sous forme de tuple vide () . Mais le compilateur peut gérer cela facilement. en remplaçant a() à la place.

    Types Scala et dépendants du chemin

    Scala ne prend pas entièrement en charge les types dépendants, mais il existe des types qui dépendent de l'instance de l'objet par lequel il est référencé (types dépendants du chemin). Dans la théorie des types dépendants, ils peuvent être décrits comme une variante du type sigma. Les types dépendants du chemin permettent d'interdire l'ajout de vecteurs issus de différents espaces vectoriels, ou de décrire qui peut embrasser qui. Mais nous les utiliserons pour des tâches plus simples.

    Classe abstraite scellée Classe de cas MsgLogouted Login (nom : chaîne) étend la classe abstraite scellée MsgLogouted Classe de cas MsgLogined Logout() étend la classe de cas MsgLogined Greet() étend la classe abstraite MsgLogined View ( def run() : Msg ) classe abstraite scellée Modèle ( type Message def view() : View ) la classe de cas Logouted() étend le modèle (type Message = MsgLogined remplace la vue def() : View .... ) la classe de cas Logined(nom : String) étend le modèle (type Message = MsgLogined remplace la vue def( ): Voir .... )
    Les types algébriques dans Scala sont modélisés par héritage. Le type correspond à certains classe abstraite scellée, et chaque constructeur en a hérité classe de cas. Nous allons essayer de les utiliser exactement comme des types algébriques, décrivant toutes les variables comme appartenant au parent classe abstraite scellée.

    Les classes MsgLogined et MsgLogouted de notre programme n'ont pas d'ancêtre commun. La fonction de visualisation devait être répartie sur différentes classes du modèle afin d'avoir accès à un type de message spécifique. Cela a ses avantages, que les partisans d'OO apprécieront : le code est regroupé selon la logique métier, tout ce qui concerne un cas d'utilisation est à proximité. Mais je préfère séparer le regard en une fonction distincte, dont le développement pourrait être transféré à une autre personne.

    Maintenant, implémentons le programme de mise à jour

    Objet Updater ( def update(model: Model)(msg: model.Message) : Model = ( correspondance du modèle ( case Logouted() => msg match ( case Login(name) => Logined(name) ) case Logined(name) => correspondance du msg ( case Logout() => Logouted() case Greet() => model ) ) ) )
    Ici, nous utilisons des types dépendants du chemin pour décrire le type du deuxième argument à partir de la valeur du premier. Pour que Scala accepte de telles dépendances, les fonctions doivent être décrites sous forme curry, c'est-à-dire comme une fonction du premier argument, qui renvoie une fonction du deuxième argument. Malheureusement, Scala n'effectue pas actuellement beaucoup de vérifications de type pour lesquelles le compilateur dispose de suffisamment d'informations.

    Donnons maintenant une implémentation complète du modèle et de la vue

    La classe de cas Logouted() étend le modèle ( tapez Message = MsgLogouted override def view() : View = new View ( override def run() = ( println("Enter name ") val name = scala.io.StdIn.readLine() Connexion (nom) ) ) case class Logined(name: String) extends Model ( type Message = MsgLogined override def view() : View = new View ( override def run() = ( println(s"Bonjour, $name") println ( "Chaîne vide pour la déconnexion, non empy pour le message d'accueil.") scala.io.StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) classe abstraite View ( def run( ) : Msg ) Visionneuse d'objets ( def view(model: Model): View = ( model.view() ) )
    Le type de retour d'une fonction de vue dépend de l'instance de son argument. Mais pour la mise en œuvre, on se tourne vers le modèle.

    L'application ainsi créée se lance ainsi :

    Objet principal ( import scala.annotation.tailrec @tailrec def process(m: Model) ( val msg = Viewer.view(m).run() process(Updater.update(m)(msg)) ) def main(args: Tableau) = (processus (Déconnecté()) ) )
    Le code système d'exécution ne sait donc rien de la structure interne des modèles et des types de messages, mais le compilateur peut vérifier que le message correspond au modèle actuel.

    Ici, nous n'avions pas besoin de toutes les capacités fournies par les types dépendants du chemin. Des propriétés intéressantes apparaîtront si l’on travaille en parallèle avec plusieurs instances de systèmes Model-Updater-View, par exemple lors de la simulation d’un monde multi-agents (la vue représenterait alors l’influence de l’agent sur le monde et la réception de feedback). Dans ce cas, le compilateur a vérifié que le message a été traité exactement par l'agent auquel il était destiné, malgré le fait que tous les agents ont le même type.

    C++

    Le C++ est toujours sensible à l'ordre des définitions, même si elles sont toutes créées dans le même fichier. Cela crée certains désagréments. Je présenterai le code dans un ordre pratique pour démontrer des idées. Une version compilable peut être trouvée sur GitHub.

    Les types algébriques peuvent être implémentés de la même manière qu'en Scala - une classe abstraite correspond à un type, et les descendants concrets correspondent aux constructeurs (appelons-les « classes de constructeurs », pour ne pas être confondus avec les constructeurs C++ ordinaires) du type algébrique. taper.

    C++ prend en charge les types dépendants du chemin, mais le compilateur ne peut pas utiliser le type dans l'abstrait sans connaître le type réel auquel il est associé. Par conséquent, il est impossible d'implémenter Model-Updater-View avec leur aide.

    Mais C++ dispose d’un puissant système de modèles. La dépendance du type à la valeur du modèle peut être masquée dans un paramètre de modèle d'une version spécialisée du système exécutif.

    Processeur Struct ( Processeur const virtuel *next() const = 0; ); modèle struct ProcessorImpl : processeur public ( const CurModel * modèle ; ProcessorImpl (const CurModel* m) : modèle(m) ( ); const Processeur *next() const ( const Vue * view = modèle->view(); const typename CurModel::Message * msg = view->run(); supprimer la vue ; const Modèle * newModel = msg->process(model); supprimer le message ; return newModel->processor(); ) );
    Nous décrivons un système d'exécution abstrait, avec une méthode unique pour faire tout ce qui est requis et renvoyer un nouveau système d'exécution adapté à l'itération suivante. La version spécifique possède un paramètre de modèle et sera spécialisée pour chaque « classe de constructeur » du modèle. Il est important ici que toutes les propriétés de type CurModel soient vérifiées lors de la spécialisation du modèle avec un paramètre de type spécifique, et au moment de la compilation du modèle lui-même, il n'est pas nécessaire de les décrire (bien qu'il soit possible d'utiliser concepts ou d'autres moyens d'implémenter des classes de types). Scala dispose également d'un système assez puissant de types paramétrés, mais il vérifie les propriétés des types de paramètres lors de la compilation du type paramétré. Là, la mise en œuvre d'un tel modèle est difficile, mais possible, grâce au support des classes de types.

    Décrivons le modèle.

    Modèle Struct ( virtual ~Model() (); virtual const Processeur *processor() const = 0; ); struct Connecté : modèle public ( struct Message ( const modèle virtuel * process(const Connecté * m) const = 0; virtual ~Message() (); struct Déconnexion : message public ( const Model * process(const Connecté * m) const; struct Greet: public Message ( const Model * process(const Logined * m) const; ); const std::string name; (...); vue const * view() const ( return new LoginedView(name); ); const Processeur *processor() const (retourne un nouveau ProcessorImpl (ce); (...); vue const ); ); struct Déconnecté : modèle public ( struct Message ( const modèle virtuel * processus (const déconnecté * m) const = 0; virtuel ~Message() (); struct Connexion : message public ( const std::nom de chaîne; Connexion (std :: string lname) : name(lname) ( ); const Modèle * process(const Déconnecté * m) const ; * view() const ( return new LogoutedView(); ); const Processeur *processor() const (retourne un nouveau ProcessorImpl
    (ce);

    ); );

    Modèle Const * Logouted::Login::process(const Logouted * m) const ( delete m; return new Logined(name); ); const Modèle * Logined::Logout::process(const Logined * m) const ( delete m; return new Logouted(); ); const Modèle * Logined::Greet::process(const Logined * m) const ( return m; );
    Rassemblons maintenant tout ce qui concerne la vue, y compris les entités internes des modèles

    Modèle struct View ( message const virtuel * run() const = 0; virtuel ~View () (); ); struct Connecté : modèle public ( struct LoginedView : vue publique ( const std::string name; LoginedView(std::string lname) : name(lname) (); virtual const Message * run() const ( char buf; printf("Bonjour %s", name.c_str()) ; fgets(buf, 15, stdin); return (*buf == 0 || *buf == "\n" || *buf == "\r") ? (nouvelle déconnexion()) : static_cast (nouveau salut); ); ); vue const * view() const ( return new LoginedView(name); ); ); struct Logouted : modèle public ( struct LogoutedView : vue publique ( virtual const Message * run() const ( char buf; printf("Connexion: "); fgets(buf, 15, stdin); return new Login(buf); ); ); vue const * view() const ( return new LogoutedView(); ); );
    Et enfin, écrivons main

    Int main(int argc, char ** argv) ( const Processeur * p = new ProcessorImpl (nouveau Déconnecté()); while(true) ( ​​​​const Processeur * pnew = p->next(); delete p; p = pnew; ) return 0; )

    Et encore Scala, cette fois avec des classes de types

    En termes de structure, cette implémentation reproduit presque entièrement la version C++.

    Partie similaire du code

    classe abstraite View ( def run(): Message ) classe abstraite Processeur ( def next() : Processeur; ) classe abstraite scellée Modèle ( def processeur() : Processeur ) classe abstraite scellée Classe de cas LoginedMessage Logout() étend la classe de cas LoginedMessage Greet( ) étend la classe de cas LoginedMessage Logined (nom de valeur : String) étend le modèle (remplace le processeur def (): Processor = new ProcessorImpl (this)) classe abstraite scellée Classe de cas LogoutedMessage Login (nom : chaîne) étend la classe de cas LogoutedMessage Logouted () étend le modèle ( override def processeur() : Processeur = new ProcessorImpl(this) ) object Main ( import scala.annotation.tailrec @tailrec def process(p: Processor) ( process(p.next()) ) def main(args: Array) = (processus(nouveau ProcessorImpl(Déconnecté())) ) )


    Mais dans la mise en œuvre de l'environnement d'exécution, des subtilités surviennent.

    Classe ProcessorImpl(model: M)(mise à jour implicite: (M, Message) => Modèle, vue: M => View) extends Processor ( def next(): Processor = ( val v = view(model) val msg = v. run() val newModel = updater(model,msg) newModel.processor() ) )
    Ici nous voyons de nouveaux paramètres mystérieux (mise à jour implicite : (M, Message) => Modèle, vue : M => Vue). Le mot-clé implicite signifie que lors de l'appel de cette fonction (plus précisément, le constructeur de classe), le compilateur recherchera des objets de types appropriés marqués comme implicites dans le contexte et les transmettra comme paramètres appropriés. Il s'agit d'un concept assez complexe dont l'une des applications est l'implémentation de classes de types. Ici, ils promettent au compilateur que pour des implémentations spécifiques du modèle et du message, nous fournirons toutes les fonctions nécessaires. Maintenant, tenons cette promesse.

    Mises à jour d'objets ( implicite def logoutedUpdater(model: Logouted, msg: LogoutedMessage): Model = ( (model, msg) match ( case (Logouted(), Login(name)) => Logined(name) ) ) implicite def viewLogouted(model : Logouted) = new View (remplacer def run() : LogoutedMessage = ( println("Enter name ") val name = scala.io.StdIn.readLine() Login(name) ) ) implicite def loginedUpdater(model: Connecté, msg : LoginedMessage): Model = ( (model, msg) match ( case (Logined(name), Logout()) => Logouted() case (Logined(name), Greet()) => model ) ) implicite def viewLogined( model: Connecté) = new View ( val name = model.name override def run() : LoginedMessage = ( println(s"Bonjour, $name") println("Chaîne vide pour la déconnexion, nonempy pour le message d'accueil.") scala.io .StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) importer les mises à jour._

    Haskell

    Il n'y a pas de types dépendants dans Haskell traditionnel. Il manque également d'héritage, que nous avons largement utilisé lors de l'implémentation du modèle en Scala et C++. Mais l'héritage à un seul niveau (avec des éléments de types dépendants) peut être modélisé à l'aide d'extensions de langage plus ou moins standard - TypeFamilies et ExistentialQuantification. Pour l'interface commune des classes POO enfants, une classe de types est créée, dans laquelle se trouve un type « familial » dépendant, les classes enfants elles-mêmes sont représentées par un type distinct, puis enveloppées dans un type « existentiel » avec un seul constructeur. .

    Modèle de données = pour tous m. (M pouvant être mis à jour, m visualisable) => Classe modèle m M pouvant être mis à jour où données Message m:: * update:: m -> (Message m) -> Classe de modèle (M pouvant être mis à jour) => M visualisable où vue :: m -> (Afficher (Message m)) données Déconnecté = Données déconnectées Connecté = Chaîne connectée
    J'ai essayé de séparer le programme de mise à jour et l'affichage autant que possible, j'ai donc créé deux classes de types différentes, mais jusqu'à présent, cela n'a pas bien fonctionné.

    L'implémentation du programme de mise à jour est simple

    Instance pouvant être mise à jour Déconnectée où les données Message Déconnecté = Mise à jour de la chaîne de connexion Déconnecté (Nom de connexion) = Modèle (Nom de connexion) Instance pouvant être mise à jour Connecté où les données Message Connecté = Déconnexion | Mise à jour du message d'accueil m Déconnexion = Modèle Mise à jour déconnectée m Message d'accueil = Modèle m
    J'ai dû corriger IO en tant que View. Les tentatives pour le rendre plus abstrait ont tout compliqué et augmenté le couplage du code - le type Model doit savoir quelle vue nous allons utiliser.

    Importer le type System.IO View a = IO une instance Visualisable Déconnecté où view Logouted = do putStr "Connexion : " hFlush stdout fmap Connexion getLine instance Visualisable Connecté où view (Nom connecté) = do putStr $ "Bonjour " ++ nom ++ " !\n" hFlush sortie standard l<- getLine pure $ if l == "" then Logout else Greeting
    Eh bien, l'environnement exécutable diffère peu de celui similaire d'Idris

    RunMUV :: Model -> IO a runMUV (Modèle m) = faire msg<- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)

  • mob_info