Prikazi za prijavu i odjavu. Model-Ažuriranje-Prikaz uzorak i zavisni tipovi Odjava HTML skup pogleda

Django dolazi s puno ugrađenih resursa za najčešće slučajeve korištenja web aplikacije. Aplikacija za registraciju je vrlo dobar primjer i dobra stvar u vezi nje je to što se funkcije mogu koristiti izvan kutije.

Pomoću aplikacije za registraciju Django možete iskoristiti prednosti sljedećih funkcija:

  • Ulogovati se
  • Odjaviti se
  • Prijaviti se
  • Poništavanje lozinke

U ovom vodiču ćemo se fokusirati na funkcije za prijavu i odjavu. Za registraciju i poništavanje lozinke, pogledajte tutorijale u nastavku:

Počinjemo

Prije nego što počnemo, uvjerite se da imate django.contrib.auth u vašim INSTALLED_APPS i da je međuverzija za autentifikaciju ispravno konfigurisana u MIDDLEWARE_CLASSES postavkama.

Oba dolaze već konfigurisani kada pokrenete novi Django projekat koristeći naredbu startproject. Dakle, ako niste uklonili početne konfiguracije, sve bi trebalo biti podešeno.

U slučaju da pokrećete novi projekat samo da biste pratili ovaj vodič, kreirajte korisnika koristeći komandnu liniju samo da bismo mogli testirati stranice za prijavu i odjavu.

$ python manage.py createsuperuser

Na kraju ovog članka dat ću izvorni kod primjera s minimalnom konfiguracijom.

Konfigurirajte URL rute

Prvo uvezite django.contrib.auth.views modul i dodajte URL rutu za prikaze za prijavu i odjavu:

iz django.conf.urls uvezite url iz django.contrib uvezite admin iz django.contrib.auth uvezite poglede kao auth_views urlpatterns = [ url (r"^login/$" , auth_views . login , name = "login" ), url ( r"^logout/$" , auth_views , name = "logout" ), url (r"^admin/" , admin . site . urls ), ]

Kreirajte predložak za prijavu

Podrazumevano, django.contrib.auth.views.login prikaz će pokušati da prikaže registracioni/login.html šablon. Dakle, osnovna konfiguracija bi bila kreiranje fascikle pod nazivom registracija i postavljanje predloška login.html unutra.

Slijedeći minimalni predložak za prijavu:

(% proširuje "base.html" %) (% naslov bloka %)Prijava (% endblock %) (% sadržaja bloka %)

Ulogovati se

(% csrf_token %) (( form.as_p ))
(%endblock%)

Ovaj jednostavan primjer već potvrđuje valjanost korisničkog imena i lozinke i ispravnu autentikaciju korisnika.

Prilagođavanje prikaza za prijavu

Postoji nekoliko parametara koje možete prenijeti u prikaz za prijavu kako bi se prilagodio vašem projektu. Na primjer, ako želite pohraniti svoj predložak za prijavu negdje drugdje osim registracije/login.html, možete proslijediti ime predloška kao parametar:

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

Također možete proslijediti prilagođeni obrazac za autentifikaciju koristeći parametar authentication_form , u slučaju da ste implementirali prilagođeni korisnički model.

Sada je vrlo važna konfiguracija urađena u datoteci settings.py, a to je URL koji će Django preusmjeriti korisnika nakon uspješne autentifikacije.

Unutar datoteke settings.py dodajte:

LOGIN_REDIRECT_URL = "kuća"

Vrijednost može biti tvrdo kodirani URL ili URL ime. Zadana vrijednost za LOGIN_REDIRECT_URL je /accounts/profile/.

Takođe je važno napomenuti da će Django pokušati preusmjeriti korisnika na sljedeći GET param.

Podešavanje prikaza odjave

Nakon pristupa prikazu django.contrib.auth.views.logout, Django će prikazati predložak register/logged_out.html. Na sličan način kao što smo to učinili u prikazu za prijavu, možete proslijediti drugačiji predložak na sljedeći način:

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

Obično radije koristim parametar next_page i preusmjeravam ili na početnu stranicu mog projekta ili na stranicu za prijavu kada to ima smisla.

Ovaj primjer pokazuje kako se automatski odjaviti sa zadanom Spring sigurnosnom konfiguracijom.

Da bismo se odjavili, samo trebamo pristupiti URL-u "/logout" sa POST zahtjevom.

U obrazac POST /logout, takođe moramo uključiti CSRF token, koji je zaštita od CSRF napada.

Pogledajmo na primjeru kako se to radi.

Java Config klasa

@Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan javna klasa AppConfig proširuje WebSecurityConfigurerAdapter ( protected void configure(HttpSecurity http) izbacuje izuzetak ( http.authorizeRequests() .anyRequest().authenticated() .andOver() public() .andOver(). void configure(AuthenticationManagerBuilder builder) izbacuje izuzetak (builder.inMemoryAuthentication() .withUser("joe") .password("123") .roles("ADMIN"); ) @Bean javni ViewResolver viewResolver() ( InternalviewResourceResourceViewResol ("/WEB-INF/views/");

Imajte na umu da, u gornjoj konfiguraciji, također nadjačavamo configure(HttpSecurity http) da izostavimo zadanu osnovnu autentifikaciju (pogledajte originalnu metodu u izvornom kodu WebSecurityConfigurerAdapter) i koristimo autentifikaciju zasnovanu na obrascu. To radimo zato što pretraživači agresivno keširaju informacije o osnovnoj autentifikaciji (nakon prve uspješne prijave) i ne postoji način da se korisnik odjavi u trenutnoj sesiji. U većini primjera nećemo koristiti mehanizam osnovne provjere autentičnosti.

Kontrolor

@Controller javna klasa ExampleController ( @RequestMapping("/") public String handleRequest2(ModelMap map) ( map.addAttribute("time", LocalDateTime.now().toString()); vrati "my-page"; ) )

JSP stranica

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

Spring Security Primer

Vrijeme: $(vrijeme)

Da isprobate primjere, pokrenite embedded tomcat (konfiguriran u pom.xml primjera projekta ispod):

Mvn tomcat7:run-war

Izlaz

Početni pristup URI-ju "/" će preusmjeriti na "/login":

Nakon slanja korisničkog imena i lozinke dok postavljamo u našoj AppConfig klasi:

Klikom na dugme "Odjava":


Primjer projekta

Zavisnosti i korištene tehnologije:

  • 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 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

Uredite datoteku urls.py aplikacije račun:

from django.conf.urls import url iz . import views urlpatterns = [ # prethodni prikaz za prijavu # url(r"^login/$", views.user_login, name="login"),# login / logout urls url(r"^login/$" , "django.contrib.auth.views.login", name="login" ), url(r"^logout/$" , "django.contrib.auth.views.logout", name="odjava" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login", name="logout_then_login" ), ]

Prokomentirali smo URL predložak za prikaz user_login, kreiran ranije za korištenje pogleda Ulogovati se Django.

Kreirajte novi direktorij u direktoriju predložaka aplikacije račun i imenuj ga registracija. Kreirajte novu datoteku u novom direktoriju, dajte joj naziv login.html

(% proširuje "base.html" %) (% naslov bloka %)Prijava (% endblock %) (% sadržaja bloka %)

Ulogovati se

(% ako form.errors %)

Vaše korisničko ime i lozinka se ne podudaraju. Pokušajte ponovo.

(%else%)

Molimo, koristite sljedeći obrazac za prijavu:

(%endif%) (%endblock%)

Ovaj predložak za prijavu je vrlo sličan onom kreiranom ranije. Django koristi AuthenticationForm, nalazi se u django.contrib.auth.forms. Ovaj obrazac pokušava autentifikovati korisnika i generira grešku validacije ako korisničko ime nije ispravno. U ovom slučaju možemo tražiti greške pomoću naredbe (% if form.errors %). Imajte na umu da smo dodali skriveni element za slanje vrijednosti varijable named sljedeći.

Parametar sljedeći mora biti URL. Ako je ovaj parametar specificiran, nakon što se korisnik prijavi, preusmjerava se na navedeni URL.

Sada kreirajte šablon logged_out.html unutar direktorija šablona registracija i zalijepite sljedeći kod u njega:

(% proširuje "base.html" %) (% naslov bloka %) Odjavljen (% endblock %) (% sadržaja bloka %)

Odjavljen

Uspješno ste odjavljeni. Možete se ponovo prijaviti.

(%endblock%)

Ovo je šablon koji će se prikazati nakon što se korisnik prijavi.

Nakon dodavanja URL predložaka i šablona za ulazne i izlazne poglede, stranica je spremna za prijavu koristeći Django poglede za autentifikaciju.

Napominjemo da je prezentacija logout_then_login, uključeno u našu urlconf, ne treba šablon jer preusmjerava na log in view.

Sada napravimo novi prikaz da prikažemo kontrolnu tablu za korisnika tako da znamo kada se korisnik prijavljuje na svoj nalog. Otvorite datoteku views.py aplikacije račun i dodajte mu sljedeći kod:

iz django.contrib.auth.decorators import login_required @login_required def kontrolna tabla (zahtjev) : vrati render(request, "account/dashboard.html" , ("section" : "dashboard" ))

U naš pogled dodajemo dekorater login_required okvir za autentifikaciju. Dekorator login_required provjerava da li je trenutni korisnik autentificiran. Ako je korisnik autentifikovan, podnošenje će biti izvršeno; Ako korisnik nije autentificiran, bit će preusmjeren na stranicu za prijavu.

Također smo definirali varijablu odjeljak. Koristit ćemo ovu varijablu da pratimo koji dio stranice korisnik gleda.

Sada morate kreirati predložak za prikaz kontrolne ploče. Kreirajte novi fajl unutar šablona/nalog šabloni/račun/ i imenuj ga dashboard.html :

(% proširuje "base.html" %) (% naslov bloka %) Kontrolna tabla (% endblock %) (% sadržaja bloka %)

Dashboard

Dobrodošli na svoju kontrolnu tablu.

(%endblock%)

Zatim dodajte sljedeći URL obrazac za ovu datoteku promjene urls.py aplikacije račun:

Urlpatterns = [ # ... url(r"^$" , views.dashboard, name="dashboard"), ]

Sada uredite datoteku settings.py:

sa 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: Govori na koji URL da preusmjeri korisnika nakon prijave.
  • LOGIN_URL: URL za preusmjeravanje korisnika na prijavu (na primjer, korištenjem dekoratora login_required)
  • LOGOUT_URL: URL za preusmjeravanje korisnika na izlaz

Sada ćemo dodati veze za prijavu i odjavu našem osnovnom predlošku.

Da biste to učinili, potrebno je utvrditi da li je trenutni korisnik prijavljen ili ne kako bi se prikazao link koji odgovara trenutnom korisničkom stanju. Trenutni korisnik je naveden u HttpRequest objekt srednje klase autentifikacije. Može se pristupiti pomoću zahtjev.korisnik. Zahtjev će pronaći korisnički objekt, čak i ako korisnik nije autentificiran. Korisnik koji nije autentificiran, naveden u zahtjevu kao instanca AnonymousUser. Najbolji način da provjerite status autentifikacije trenutnog korisnika je poziv request.user.is_authenticated()

Uredite u šablonu base.html

sa ID zaglavljem:

Kao što vidite, meni sajta se prikazuje samo za autentifikovane korisnike. Također provjeravamo trenutni odjeljak da bismo dodali odabrani atribut klase odgovarajućem elementu

  • da označite trenutni odeljak u meniju pomoću CSS-a. Također prikazuje korisničko ime i vezu za odjavu ako je korisnik autentificiran, ili link za prijavu.

    Otvorite http://127.0.0.1:8000/account/login/ u svom pretraživaču. Trebali biste vidjeti stranicu za prijavu. Unesite važeću prijavu i lozinku. Vidjet ćete sljedeće:

    Možete vidjeti da je odjeljak Moja kontrolna tabla označen CSS-om jer ima klasu odabrano. Pošto je korisnik autentifikovan, korisničko ime se prikazuje na desnoj strani zaglavlja. Kliknite na link Odjaviti se. Vidjet ćete sljedeću stranicu:

    Na ovoj stranici možete vidjeti da je korisnik odjavljen i da se meni web stranice više ne prikazuje. Sada se prikazuje veza na desnoj strani zaglavlja Ulogovati se.

    Ako vidite stranicu za odjavu sa Django administratorske stranice, a ne svoju stranicu za odjavu, provjerite postavke INSTALLED_APPS i uvjerite se da django.contrib.admin je poslije račun. Oba predloška se nalaze na istoj relativnoj putanji, a Django učitavač šablona će koristiti prvi koji pronađe.

    Uglavnom za razvoj korisničkih interfejsa. Da biste ga koristili, trebate kreirati tip modela koji predstavlja kompletno stanje programa, tip poruke koji opisuje vanjske događaje iz okruženja na koje program mora odgovoriti promjenom svog stanja, funkciju ažuriranja koja kreira novo stanje programa iz starog stanja i poruke, te funkciju prikaza koja Na osnovu stanja programa izračunava potrebne utjecaje na vanjsko okruženje, koji generiše događaje tipa Poruka. Obrazac je vrlo zgodan, ali ima mali nedostatak - ne dozvoljava vam da opišete koji događaji imaju smisla za određena stanja programa.

    Sličan problem se javlja (i rješava se) kada se koristi State OO obrazac.

    Elm jezik je jednostavan, ali vrlo strog - provjerava da li funkcija ažuriranja na neki način rukuje svim mogućim kombinacijama stanja modela i poruka-događaja. Stoga morate napisati dodatni, iako trivijalan, kod koji obično ostavlja model nepromijenjen. Želim pokazati kako se to može izbjeći u složenijim jezicima - Idris, Scala, C++ i Haskell.

    Sav kod prikazan ovdje je dostupan na GitHubu za eksperimentiranje. Pogledajmo najzanimljivija mjesta.


    Funkcija msg je neobična - vraća tip, a ne vrijednost. Tokom izvršavanja, ništa se ne zna o tipovima vrijednosti - kompajler briše sve nepotrebne informacije. Odnosno, takva funkcija se može pozvati samo u fazi kompilacije.

    MUV je konstruktor. Prihvata parametre: model - početno stanje programa, updater - funkciju za ažuriranje stanja nakon eksternog događaja i view - funkciju za kreiranje eksternog pogleda. Imajte na umu da tip ažuriranja i funkcije pregleda ovisi o vrijednosti modela (koristeći funkciju msg iz parametara tipa).

    Sada da vidimo kako pokrenuti ovu aplikaciju

    MuvRun: (Application modelType msgType IO) -> IO a muvRun (MUV model updater view) = uradi msg<- view model muvRun (MUV (updater model msg) updater view)
    Izabrali smo ulazno/izlaznu operaciju kao eksternu reprezentaciju (pogled) (u Idrisu, kao i u Haskell-u, ulazno/izlazne operacije su prvoklasne vrijednosti; da bi se izvršile, moraju se poduzeti dodatne akcije, obično vraćajući takvu operaciju iz glavne funkcije).

    Ukratko o IO

    Prilikom izvođenja operacije tipa (IO a) dolazi do nekog utjecaja na vanjski svijet, eventualno prazan, i vrijednost tipa a se vraća programu, ali funkcije standardne biblioteke su dizajnirane tako da mogu biti obrađen samo generiranjem nove vrijednosti tipa IO b. Na ovaj način se čiste funkcije odvajaju od funkcija sa nuspojavama. Ovo je neobično za mnoge programere, ali pomaže pri pisanju pouzdanijeg koda.


    Pošto funkcija muvRun generiše I/O, trebalo bi da vrati IO, ali pošto se nikada neće završiti, tip operacije može biti bilo koji - IO a.

    Hajde da sada opišemo tipove entiteta sa kojima ćemo raditi

    Model podataka = Odjavljen | Prijavljeni string podaci MsgOuted = Podaci stringa za prijavu MsgIned = Odjava | Pozdravite ukupni msgType: Model -> Tip msgType Odjavljen = MsgOuted msgType (Prijavljen _) = MsgIned
    Ovdje opisujemo tip modela koji odražava prisustvo dva stanja interfejsa - korisnik nije prijavljen, a korisnik sa imenom tipa String je prijavljen.

    Zatim ćemo opisati dva različita vrste poruka relevantnih za različite varijante modela - ako smo odjavljeni, onda se možemo prijaviti samo pod određenim imenom, a ako smo već prijavljeni možemo se ili odjaviti ili pozdraviti. Idris je jezik jakog tipa koji neće dopustiti mogućnost miješanja različitih tipova.

    I konačno, funkcija koja postavlja korespondenciju vrijednosti modela s tipom poruke.

    Funkcija je deklarisana kao total - to jest, ne bi trebalo da se sruši ili zamrzne, kompajler će to pokušati da nadgleda. msgType se poziva u vrijeme kompilacije, a njegova ukupnost znači da se kompilacija neće zamrznuti zbog naše greške, iako ne može jamčiti da će izvršavanje ove funkcije iscrpiti sistemske resurse.
    Takođe je zagarantovano da neće izvršiti "rm -rf /" jer nema IO u njegovom potpisu.

    Hajde da opišemo updater:

    Ukupno ažuriranje: (m:Model) -> (msgType m) -> Ažuriranje modela Odjavljeno (Prijavno ime) = Ažurirano prijavljeno ime (Prijavljeno ime) Odjava = Odjavljeno ažuriranje (Prijavljeno ime) Greet = Prijavljeno ime
    Mislim da je logika ove funkcije jasna. Želio bih još jednom napomenuti totalitet - to znači da će Idris kompajler provjeriti da li smo uzeli u obzir sve alternative koje dozvoljava sistem tipova. Elm također obavlja ovu provjeru, ali ne može znati da se ne možemo odjaviti ako još nismo prijavljeni i zahtijevat će eksplicitnu obradu uvjeta

    Ažurer odjavljen Odjava = ???
    Idris će pronaći nepodudaranje tipova u nepotrebnoj provjeri.

    Sada pređimo na prikaz – kao i obično u korisničkom sučelju, ovo će biti najteži dio koda.

    Ukupna stranica za prijavu: IO MsgOuted loginPage = do putStr "Prijava: " mapa Prijava getLine total genMsg: String -> MsgIned genMsg "" = Odjava genMsg _ = Pozdravi ukupnu radnu stranicu: String -> IO MsgIned naziv radne stranice = do putStr ("Zdravo, " ++ name ++ "\n") putStr "Unesite prazan niz za odjavu ili neprazan za pozdrav\n" mapa genMsg getLine ukupan prikaz: (m: Model) -> IO (msgType m) prikaz Odjavljen = login Prikaz stranice (Prijavljeno ime ) = naziv radne stranice
    pogled mora kreirati I/O operaciju koja vraća poruke, čiji tip opet zavisi od vrijednosti modela. Imamo dvije opcije: loginPage, koja ispisuje "Login:" poruku, čita string sa tastature i umotava ga u poruku za prijavu, i workPage sa parametrom korisničkog imena, koja ispisuje pozdrav i vraća različite poruke (ali istog tipa - MsgIned) u zavisnosti od toga da li korisnik unese prazan ili neprazan string. view vraća jednu od ovih operacija ovisno o vrijednosti modela, a kompajler provjerava njihov tip, iako je drugačiji.

    Sada možemo kreirati i pokrenuti našu aplikaciju

    Aplikacija: Model aplikacije Main.msgType IO aplikacija = MUV Odjavljeni program za ažuriranje, prikaz glavnog: IO () main = aplikacija muvRun
    Ovdje treba napomenuti suptilnu stvar - funkcija muvRun se vraća IO a, gdje a nije specificirano, a vrijednost main je tipa IO(), Gdje () je naziv tipa koji se obično naziva Jedinica, koji ima jednu vrijednost, također napisan kao prazan tuple () . Ali kompajler to može lako riješiti. umjesto toga zamjenjujući a().

    Scala i tipovi zavisni od putanje

    Scala nema potpunu podršku za zavisne tipove, ali postoje tipovi koji zavise od instance objekta kroz koji je referenciran (tipovi zavisni od putanje). U teoriji zavisnih tipova, oni se mogu opisati kao varijanta sigma tipa. Tipovi zavisni od putanje omogućavaju da se zabrani dodavanje vektora iz različitih vektorskih prostora ili da se opiše ko koga može poljubiti. Ali mi ćemo ih koristiti za jednostavnije zadatke.

    Zapečaćena apstraktna klasa MsgLogouted case class Login(name: String) proširuje MsgLogouted zapečaćenu apstraktnu klasu MsgLogined case class Logout() proširuje MsgLogined klasu slučaja Greet() proširuje MsgLogined apstraktnu klasu View ( def run() : Msg ) zapečaćena klasa apstraktne poruke def view() : View ) case class Logouted() proširuje model ( tip Poruka = ​​MsgLogined nadjača def view() : Pogled .... ) case class Logined(name: String) proširuje model ( tip Poruka = ​​MsgLogined nadjačava def view( ) : Pogledaj .... )
    Algebarski tipovi u Scali su modelirani putem nasljeđivanja. Tip odgovara nekima zapečaćena apstraktna klasa, i svaki konstruktor naslijedio od njega klasa slučaja. Pokušat ćemo ih koristiti upravo kao algebarske tipove, opisujući sve varijable kao da pripadaju roditelju zapečaćena apstraktna klasa.

    Klase MsgLogined i MsgLogouted unutar našeg programa nemaju zajedničkog pretka. Funkcija pregleda je morala biti raspoređena na različite klase modela da bi imala pristup određenoj vrsti poruke. Ovo ima svoje prednosti, koje će pristalice OO-a cijeniti - kod je grupiran u skladu s poslovnom logikom, sve što je vezano za jedan slučaj upotrebe je u blizini. Ali ja bih radije odvojio pogled u posebnu funkciju, čiji razvoj se može prenijeti na drugu osobu.

    Sada implementirajmo ažuriranje

    Object Updater ( def update(model: Model)(msg: model.Message) : Model = ( podudaranje modela ( case Logouted() => msg match ( case Login(name) => Logined(name) ) case Logined(name) => msg match ( case Logout() => Logouted() case Greet() => model ) ) ) )
    Ovdje koristimo tipove zavisne od putanje da opišemo tip drugog argumenta iz vrijednosti prvog. Da bi Scala prihvatila takve zavisnosti, funkcije moraju biti opisane u curried obliku, odnosno kao funkcija iz prvog argumenta, koja vraća funkciju iz drugog argumenta. Nažalost, Scala u ovom trenutku ne radi mnogo provjera tipa za koje kompajler ima dovoljno informacija.

    Sada dajemo kompletnu implementaciju modela i pogleda

    Klasa slučaja Logouted() proširuje model ( tip Poruka = ​​MsgLogouted nadjača def view() : Pogled = novi Pogled ( nadjača def run() = ( println("Unesite ime") val name = scala.io.StdIn.readLine() Prijava (ime) ) ) case class Logined(name: String) proširuje Model ( tip Poruka = ​​MsgLogined nadjačati def view() : Pogled = novi Pogled ( nadjačati def run() = ( println(s"Zdravo, $name") println ( "Prazan niz za odjavu, nonempy za pozdrav.") scala.io.StdIn.readLine() odgovara ( case "" => Odjava() case _ => Greet() ) ) ) apstraktna klasa View ( def run( ) : Msg ) Pregledač objekata ( def view(model: Model): View = ( model.view() )
    Tip povratka funkcije pogleda ovisi o instanci njenog argumenta. Ali za implementaciju se okreće modelu.

    Ovako kreirana aplikacija se pokreće ovako:

    Glavni objekat ( import scala.annotation.tailrec @tailrec def process(m: Model) ( val msg = Viewer.view(m).run() process(Updater.update(m)(msg)) ) def main(args: Niz) = (proces(Odjavljen())))
    Izvršni sistemski kod stoga ne zna ništa o internoj strukturi modela i tipovima poruka, ali kompajler može provjeriti da li poruka odgovara trenutnom modelu.

    Ovdje nam nisu bile potrebne sve mogućnosti koje pružaju tipovi zavisni od putanje. Zanimljiva svojstva će se pojaviti ako radimo paralelno s nekoliko instanci sistema Model-Updater-View, na primjer, kada simuliramo svijet s više agenata (pogled bi tada predstavljao utjecaj agenta na svijet i primanje povratnih informacija). U ovom slučaju, kompajler je provjerio da li je poruku obradio upravo onaj agent za koji je namijenjena, uprkos činjenici da svi agenti imaju isti tip.

    C++

    C++ je i dalje osjetljiv na redosljed definicija, čak i ako su sve napravljene u istoj datoteci. To stvara određene neugodnosti. Predstaviću kod u nizu pogodnom za demonstriranje ideja. Verzija za kompajliranje može se naći na GitHubu.

    Algebarski tipovi se mogu implementirati na isti način kao u Scali - apstraktna klasa odgovara tipu, a konkretni potomci odgovaraju konstruktorima (nazovimo ih "klase konstruktora", da se ne bi brkali sa običnim C++ konstruktorima) algebarskog tip.

    C++ ima podršku za tipove zavisne od putanje, ali kompajler ne može koristiti tip u apstraktu bez poznavanja stvarnog tipa s kojim je povezan. Stoga je nemoguće implementirati Model-Updater-View uz njihovu pomoć.

    Ali C++ ima moćan sistem šabloniranja. Ovisnost tipa o vrijednosti modela može se sakriti u parametru šablona specijalizirane verzije izvršnog sistema.

    Strukturni procesor (virtuelni const procesor *next() const = 0; ); šablon struct ProcessorImpl: javni procesor ( const CurModel * model; ProcessorImpl (const CurModel* m) : model(m) ( ); const Procesor *next() const ( const Pogled * pogled = model->view(); const typename CurModel::Message * msg = view->run(); delete view; const Model * newModel = msg->process(model); delete msg; return newModel->processor(); ) );
    Opisujemo apstraktni izvršni sistem, sa jednom metodom da uradimo sve što je potrebno i vratimo novi sistem izvršenja prikladan za sledeću iteraciju. Specifična verzija ima parametar šablona i biće specijalizovana za svaku „klasu konstruktora“ modela. Ovdje je važno da će sva svojstva tipa CurModel biti provjerena tokom specijalizacije predloška sa određenim parametrom tipa, a u vrijeme kompilacije samog predloška ne moraju biti opisana (iako je moguće koristiti koncepti ili drugi načini implementacije klasa tipova). Scala takođe ima prilično moćan sistem parametrizovanih tipova, ali proverava svojstva tipova parametara tokom kompilacije parametrizovanog tipa. Tamo je implementacija takvog uzorka teška, ali moguća, zahvaljujući podršci klasa tipova.

    Hajde da opišemo model.

    Strukturni model ( virtual ~Model() (); virtual const Procesor *processor() const = 0; ); struct Prijavljen: javni model ( struct Poruka ( const virtuelni model * process(const Logined * m) const = 0; virtual ~Message() (); struct Odjava: javna poruka ( const Model * process(const Logined * m) const; struct Greet: javna poruka ( const Model * process(const Logined * m) const; std::string name); (...); const View * view() const (vrati novi LoginedView(name); ); const Procesor *processor() const (vrati novi ProcessorImpl (ovo); (...); const View ); ); struct Odjavljen: javni model ( struct Poruka ( const virtualni model * proces(const odjavljen * m) const = 0; virtual ~Message() (); struct Login: javna poruka ( const std::string name; Login(std :: string lname) : name(lname) ( const Model * process(const Logouted * m) const; * view() const (vrati novi LogoutedView(); ); const Procesor *processor() const (vrati novi ProcessorImpl
    (ovo);

    ); );

    Const Model * Odjavljen::Prijava::process(const Odjavljen * m) const ( izbriši m; vrati novo prijavljeno(ime); ); const Model * Logined::Logout::process(const Logined * m) const ( delete m; return new Logouted(); ); const Model * Logined::Greet::process(const Logined * m) const ( return m; );
    Sada sastavite sve što se odnosi na pogled, uključujući unutrašnje entitete modela

    Predložak struct View ( virtual const Poruka * run() const = 0; virtual ~View () (); ); struct Prijavljen: javni model ( struct LoginedView: javni pogled ( const std::string name; LoginedView(std::string lname) : name(lname) (); virtual const Poruka * run() const ( char buf; printf("Zdravo %s", name.c_str()) fgets(buf, 15, stdin return (*buf == 0 || *buf == "\n") ? (novi Odjava()) : static_cast (novi Greet); ); ); const View * view() const (vrati novi LoginedView(name); ); ); struct Odjavljen: javni model ( struct LogoutedView: javni pogled ( virtual const Poruka * run() const ( char buf; printf("Prijava: "); fgets(buf, 15, stdin); return new Login(buf); ); ); const View * view() const (vrati novi LogoutedView(); ); );
    I na kraju, hajde da napišemo main

    Int main(int argc, char ** argv) ( const Processor * p = new ProcessorImpl (novi Odjavljen()); while(true) (const Procesor * pnew = p->next(); delete p; p = pnew; ) return 0; )

    I opet Scala, ovaj put sa klasama tipova

    U strukturi, ova implementacija gotovo u potpunosti replicira C++ verziju.

    Sličan dio koda

    apstraktna klasa View ( def run(): Poruka) apstraktna klasa Procesor ( def next(): Procesor; ) zapečaćena apstraktna klasa Model ( def processor(): Procesor) zapečaćena apstraktna klasa LoginedMessage klasa slučaja Logout() proširuje klasu slučaja LoginedMessage Greet( ) proširuje klasu slučaja LoginedMessage Logined(val name: String) proširuje Model (nadjača def processor(): Procesor = novi ProcessorImpl(this) ) zapečaćena apstraktna klasa LogoutedMessage klasa slučaja Login(name: String) proširuje LogoutedMessage klasa slučaja Logouted() proširuje model ( override def processor(): Procesor = new ProcessorImpl(this) ) object Main ( import scala.annotation.tailrec @tailrec def process(p: Procesor) ( process(p.next()) ) def main(args: Array) = ( proces(novi ProcessorImpl(Odjavljen())) )


    Ali u implementaciji runtime okruženja pojavljuju se suptilnosti.

    Class ProcessorImpl(model: M)(implicitno ažuriranje: (M, Poruka) => Model, pogled: M => Pogled) proširuje Procesor ( def next(): Procesor = ( val v = view(model) val msg = v. run() val newModel = updater(model,msg) newModel.processor() )
    Ovdje vidimo nove misteriozne parametre (implicitno ažuriranje: (M, Poruka) => Model, pogled: M => Pogled). Implicitna ključna riječ znači da će pri pozivanju ove funkcije (tačnije, konstruktora klase), kompajler tražiti objekte odgovarajućeg tipa označene kao implicitne u kontekstu i proslijediti ih kao odgovarajuće parametre. Ovo je prilično složen koncept, čija je jedna od primjena implementacija klasa tipova. Ovdje obećavaju kompajleru da ćemo za specifične implementacije modela i poruke mi obezbijediti sve potrebne funkcije. Sada ispunimo ovo obećanje.

    Ažuriranja objekata ( implicitni def logoutedUpdater(model: Odjavljen, poruka: LogoutedMessage): Model = ( (model, poruka) podudaranje ( case (Odjavljen(), Login(name)) => Prijavljen(ime) ) ) implicit def viewLogouted(model : Odjavljen) = novi Pogled ( nadjača def run() : LogoutedMessage = ( println("Unesite ime") val name = scala.io.StdIn.readLine() Login(name) ) ) implicit def loginedUpdater(model: Logined, msg : LoginedMessage): Model = ( (model, poruka) podudaranje ( case (Prijavljen(ime), Odjava()) => Odjavljen() case (Prijavljen(ime), Greet()) => model ) ) implicit def viewLogined( model: Logined) = new View ( val name = model.name override def run() : LoginedMessage = ( println(s"Zdravo, $name") println("Prazan niz za odjavu, nonempy za pozdrav.") scala.io .StdIn.readLine() podudaranje ( case "" => Odjava() case _ => Greet() ) ) ) uvoz ažuriranja._

    Haskell

    U mainstream Haskell-u nema zavisnih tipova. Nedostaje mu i nasljeđivanje, što smo značajno koristili prilikom implementacije obrasca u Scali i C++. Ali nasljeđivanje na jednom nivou (sa elementima zavisnih tipova) može se modelirati korištenjem više ili manje standardnih jezičkih ekstenzija - TypeFamilies i ExistentialQuantification. Za zajedničko sučelje podređenih OOP klasa kreira se klasa tipa u kojoj postoji zavisni "porodični" tip, a same podređene klase su predstavljene posebnim tipom, a zatim umotane u "egzistencijalni" tip sa jednim konstruktorom .

    Model podataka = forall m. (Ažuriranje m, Vidljivo m) => Model m klasa Ažuriranje m gdje podaci Poruka m:: * ažuriranje:: m -> (Poruka m) -> Klasa modela (Ažuriranje m) => Vidljivo m gdje pogled:: m -> (Prikaz (Poruka m)) podaci Odjavljeni = Odjavljeni podaci Prijava = Prijavljeni niz
    Pokušao sam razdvojiti updater i view što je dalje moguće, pa sam napravio dvije različite klase tipova, ali do sada to nije dobro išlo.

    Implementacija ažuriranja je jednostavna

    Instanca koja se može ažurirati Odjavljena gdje su podaci Poruka odjavljena = Odjavljeno ažuriranje niza za prijavu (ime za prijavu) = Model (prijavljeno ime) instanca koja se može ažurirati Prijavljena gdje su podaci Prijava poruka = ​​Odjava | Ažuriranje pozdrava m Odjava = Ažuriranje odjavljenog modela m Greeting = Model m
    Morao sam popraviti IO kao View. Pokušaji da se učini apstraktnijim sve su uvelike zakomplikovali i povećali spregu koda - tip modela mora znati koji pogled ćemo koristiti.

    Import System.IO tip View a = IO a instanca Vidljiva Odjavljena gdje view Odjavljena = do putStr "Prijava: " hFlush stdout fmap Prijava getLine instanca Viewable Login gdje view (Prijavljeno ime) = do putStr $ "Zdravo " ++ name ++ " !\n" hFlush stdout l<- getLine pure $ if l == "" then Logout else Greeting
    Pa, izvršno okruženje se malo razlikuje od sličnog u Idrisu

    RunMUV::Model -> IO a runMUV (Model m) = uradi msg<- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)

  • mob_info