Prisijunkite ir atsijunkite rodinius. Modelis-Update-View šablonas ir priklausomi tipai Atsijungti html rodinio rinkinys

„Django“ turi daug integruotų išteklių, skirtų dažniausiai naudojamiems žiniatinklio programos atvejams. Registracijos programa yra labai geras pavyzdys, o geras dalykas yra tai, kad funkcijomis galima naudotis iš karto.

Naudodami Django registracijos programą galite pasinaudoti šiomis funkcijomis:

  • Prisijungti
  • Atsijungti
  • Registruotis
  • Slaptažodžio nustatymas iš naujo

Šioje pamokoje daugiausia dėmesio skirsime prisijungimo ir atsijungimo funkcijoms. Norėdami prisiregistruoti ir iš naujo nustatyti slaptažodį, žr. toliau pateiktus vadovėlius:

Darbo pradžia

Prieš pradėdami įsitikinkite, kad INSTALLED_APPS yra django.contrib.auth ir tinkamai sukonfigūruota autentifikavimo tarpinė programinė įranga MIDDLEWARE_CLASSES nustatymuose.

Abu jie jau sukonfigūruoti, kai pradedate naują Django projektą naudodami komandą startproject . Taigi, jei nepašalinote pradinių konfigūracijų, turėtumėte viską nustatyti.

Jei pradedate naują projektą tik norėdami sekti šią pamoką, sukurkite vartotoją naudodami komandinę eilutę, kad galėtume išbandyti prisijungimo ir atsijungimo puslapius.

$ python manage.py createsuperuser

Šio straipsnio pabaigoje pateiksiu pavyzdžio šaltinio kodą su minimalia konfigūracija.

Konfigūruokite URL maršrutus

Pirmiausia importuokite django.contrib.auth.views modulį ir pridėkite prisijungimo ir atsijungimo rodinių URL maršrutą:

iš django.conf.urls importuoti URL iš django.contrib importuoti admin iš django.contrib.auth importuoti rodinius kaip auth_views urlpatterns = [ url (r"^login/$" , auth_views . login , name = "prisijungti" ), url ( r"^logout/$" , auth_views , vardas = "atsijungti" ), url (r"^admin/" , admin . site . urls ), ].

Sukurkite prisijungimo šabloną

Pagal numatytuosius nustatymus rodinys django.contrib.auth.views.login bandys pateikti registravimo/login.html šabloną. Taigi pagrindinė konfigūracija būtų sukurti aplanką pavadinimu registracija ir įdėti login.html šabloną.

Vadovaudamiesi minimaliu prisijungimo šablonu:

(% išplečia "base.html" %) (% bloko pavadinimas %)Prisijungimas (% endblock %) (% bloko turinys %)

Prisijungti

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

Šis paprastas pavyzdys jau patvirtina vartotojo vardą ir slaptažodį bei teisingai autentifikuoja vartotoją.

Prisijungimo rodinio tinkinimas

Yra keli parametrai, kuriuos galite perduoti prisijungimo rodiniui, kad jis atitiktų jūsų projektą. Pavyzdžiui, jei norite išsaugoti prisijungimo šabloną kitur nei registracija/login.html, galite perduoti šablono pavadinimą kaip parametrą:

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

Taip pat galite perduoti pasirinktinę autentifikavimo formą naudodami parametrą autentifikavimo_forma , jei įdiegėte pasirinktinį vartotojo modelį.

Dabar labai svarbi konfigūracija atliekama faile settings.py, tai yra URL, Django nukreips vartotoją po sėkmingo autentifikavimo.

Failo settings.py viduje pridėkite:

LOGIN_REDIRECT_URL = "namai"

Reikšmė gali būti užkoduotas URL arba URL pavadinimas. Numatytoji LOGIN_REDIRECT_URL reikšmė yra /accounts/profile/ .

Taip pat svarbu pažymėti, kad Django bandys nukreipti vartotoją į kitą GET parametrą.

Atsijungimo rodinio nustatymas

Pasiekęs django.contrib.auth.views.logout rodinį, Django pateiks registravimo/logged_out.html šabloną. Panašiai, kaip tai padarėme prisijungimo rodinyje, galite perduoti kitą šabloną, pavyzdžiui:

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

Paprastai norėčiau naudoti parametrą next_page ir nukreipti arba į savo projekto pagrindinį puslapį, arba į prisijungimo puslapį, kai tai prasminga.

Šiame pavyzdyje parodyta, kaip automatiškai atsijungti naudojant numatytąją Spring saugos konfigūraciją.

Norėdami atsijungti, mums tereikia pasiekti URL „/logout“ su POST užklausa.

Formoje POST /logout taip pat turime įtraukti CSRF prieigos raktą, kuris yra apsauga nuo CSRF atakos.

Pažiūrėkime pavyzdį, kaip tai padaryti.

Java konfigūracijos klasė

@Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan viešoji klasė AppConfig išplečia WebSecurityConfigurerAdapter ( Protected void configure(HttpSecurity http) meta išimtį ( http.authorizeRequests() .anyRequest().authenticated() @Lover public() .and()ri); void configure(AuthenticationManagerBuilder builder) pateikia išimtį ( builder.inMemoryAuthentication() .withUser("joe") .password("123") .roles("ADMIN"); ) @Bean public ViewResolver viewResolver() ( InternalResourceViewResolver view (); viewResolver.setPrefix("/WEB-INF/views/");

Atminkite, kad aukščiau pateiktoje konfigūracijoje mes taip pat nepaisome konfigūracijos (HttpSecurity http), kad praleistume numatytąjį pagrindinį autentifikavimą (žr. pradinį metodą „WebSecurityConfigurerAdapter“ šaltinio kode) ir naudotume formomis pagrįstą autentifikavimą. Tai darome, nes naršyklės agresyviai saugo pagrindinio autentifikavimo informaciją (po pirmojo sėkmingo prisijungimo) ir nėra galimybės atjungti vartotojo dabartinėje sesijoje. Daugumoje pavyzdžių nenaudosime pagrindinio autentifikavimo mechanizmo.

Valdiklis

@Controller viešoji klasė PavyzdžiuiController ( @RequestMapping("/") public String handleRequest2(ModelMap map) ( map.addAttribute("time", LocalDateTime.now().toString()); grąžina "mano puslapis"; ) )

JSP puslapis

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

Pavasario apsaugos pavyzdys

Laikas: $(laikas)

Norėdami išbandyti pavyzdžius, paleiskite embedded tomcat (sukonfigūruotą toliau pateikto pavyzdinio projekto pom.xml):

Mvn tomcat7:run-war

Išvestis

Pradinė prieiga prie URI „/“ bus nukreipta į „/login“:

Pateikę vartotojo vardą ir slaptažodį, kaip nustatėme „AppConfig“ klasėje:

Spustelėjus mygtuką „Atsijungti“:


Projekto pavyzdys

Priklausomybės ir naudojamos technologijos:

  • spring-security-web 4.2.3.IŠLEIDIMAS: spring-security-web.
  • spring-security-config 4.2.3.RELEASE: spring-security-config.
  • spring-webmvc 4.3.9.IŠLEIDIMAS: Spring Web MVC.
  • javax.servlet-api 3.1.0 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

Redaguoti failą urls.py programos sąskaitą:

iš django.conf.urls importuoti URL iš . importuoti rodinius urlpatterns = [ # ankstesnis prisijungimo vaizdas # url(r"^login/$", views.user_login, name="prisijungimas"),# prisijungimo / atsijungimo URL url(r"^login/$" , "django.contrib.auth.views.login", name="login" ), url(r"^logout/$" , „django.contrib.auth.views.logout“, name="atsijungti" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login", name="logout_then_login" ), ]

Mes pakomentavome rodinio URL šabloną vartotojo prisijungimas, sukurtas anksčiau, kad galėtumėte naudoti rodinį Prisijungti Django.

Sukurkite naują katalogą programų šablonų kataloge sąskaitą ir įvardink Registracija. Sukurkite naują failą naujame kataloge, pavadinkite jį login.html

(% išplečia "base.html" %) (% bloko pavadinimas %)Prisijunkite (% endblock %) (% bloko turinys %)

Prisijungti

(% jei forma.klaidos %)

Jūsų vartotojo vardas ir slaptažodis nesutapo. Bandykite dar kartą.

(%Kitas%)

Norėdami prisijungti, naudokite šią formą:

(%endif%) (%endblock%)

Šis prisijungimo šablonas yra labai panašus į anksčiau sukurtą. Django naudoja Autentifikavimo forma, randasi django.contrib.auth.forms. Ši forma bando autentifikuoti vartotoją ir generuoja patvirtinimo klaidą, jei vartotojo vardas buvo neteisingas. Tokiu atveju klaidų galime ieškoti naudodami komandą (% if form.errors %) . Atminkite, kad pridėjome paslėptą elementą siųsti kintamojo, pavadinto, reikšmę Kitas.

Parametras Kitas turi būti URL. Jei šis parametras nurodytas, vartotojui prisijungus, jis nukreipiamas į nurodytą URL.

Dabar sukurkite šabloną logged_out.htmlšablonų katalogo viduje Registracija ir įklijuokite į jį šį kodą:

(% išplečia "base.html" %) (% bloko pavadinimas %) Atsijungtas (% endblock %) (% bloko turinys %)

Atsijungęs

Jūs sėkmingai atsijungėte. Galite prisijungti dar kartą.

(%endblock%)

Tai šablonas, kuris bus rodomas vartotojui prisijungus.

Pridėjus URL šablonus ir šablonus įvesties ir išvesties rodiniams, svetainė yra paruošta prisijungti naudojant Django autentifikavimo rodinius.

Atkreipkite dėmesį, kad pristatymas atsijungti_tada_prisijungti, įtraukta į mūsų urlconf, šablono nereikia, nes jis nukreipia į prisijungimo vaizdas.

Dabar sukurkime naują rodinį, kad būtų rodomas vartotojui skirtas prietaisų skydelis, kad žinotume, kada vartotojas prisijungia prie savo paskyros. Atidarykite failą views.py programos sąskaitą ir pridėkite prie jo šį kodą:

iš django.contrib.auth.decorators importuoti login_required @login_required def prietaisų skydelis (užklausa): grąžinti pateikimą(užklausa, "account/dashboard.html" , ("skyrius" : "dashboard" ))

Į savo vaizdą pridedame dekoratorių login_required autentifikavimo sistema. Dekoratorė login_required patikrina, ar dabartinis vartotojas yra autentifikuotas. Jei vartotojas autentifikuotas, pateikimas bus vykdomas; Jei vartotojas nėra autentifikuotas, jis bus nukreiptas į prisijungimo puslapį.

Taip pat apibrėžėme kintamąjį skyrius. Naudosime šį kintamąjį norėdami stebėti, kurią svetainės dalį žiūri vartotojas.

Dabar turite sukurti prietaisų skydelio rodinio šabloną. Sukurkite naują failą šablonuose / paskyroje šablonai/sąskaita/ ir įvardink dashboard.html :

(% išplečia "base.html" %) (% bloko pavadinimas %)Informacijos suvestinė (% endblock %) (% bloko turinys %)

Prietaisų skydelis

Sveiki atvykę į jūsų prietaisų skydelį.

(%endblock%)

Tada pridėkite šį šio pakeitimo failo URL šabloną urls.py programos sąskaitą:

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

Dabar redaguokite failą settings.py:

iš django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy("prietaisų skydelis" ) LOGIN_URL = reverse_lazy("prisijungti" ) LOGOUT_URL = reverse_lazy("atsijungti" )
  • LOGIN_REDIRECT_URL: nurodo, į kurį URL nukreipti vartotoją prisijungus.
  • LOGIN_URL: URL, nukreipiantis vartotoją į prisijungimą (pavyzdžiui, naudojant dekoratorių login_required)
  • LOGOUT_URL: URL, peradresuojantis vartotoją išeiti

Dabar prie pagrindinio šablono pridėsime prisijungimo ir atsijungimo nuorodas.

Norėdami tai padaryti, būtina nustatyti, ar dabartinis vartotojas yra prisijungęs, ar ne, kad būtų rodoma nuoroda, atitinkanti esamą vartotojo būseną. Dabartinis vartotojas nurodytas HttpRequest autentifikavimo tarpinės klasės objektas. Jį galima pasiekti naudojant prašymas.vartotojas. Užklausa suras vartotojo objektą, net jei vartotojas nėra autentifikuotas. Neautentifikuotas vartotojas, nurodytas užklausoje kaip pavyzdys Anoniminis naudotojas. Geriausias būdas patikrinti esamo vartotojo autentifikavimo būseną yra paskambinti request.user.is_authenticated()

Redaguokite šablone base.html

su ID antrašte:

Kaip matote, svetainės meniu rodomas tik autentifikuotiems vartotojams. Taip pat patikriname dabartinę sekciją, kad pasirinktą klasės atributą pridėtume prie atitinkamo elemento

  • norėdami paryškinti esamą meniu skyrių naudodami CSS. Taip pat rodomas vartotojo vardas ir nuoroda, skirta atsijungti, jei vartotojas yra autentifikuotas, arba nuoroda prisijungti.

    Naršyklėje atidarykite http://127.0.0.1:8000/account/login/. Turėtumėte pamatyti prisijungimo puslapį. Įveskite galiojantį vartotojo vardą ir slaptažodį. Pamatysite:

    Matote, kad skyrius Mano prietaisų skydelis paryškintas CSS, nes joje yra klasė pasirinktas. Kadangi vartotojas buvo autentifikuotas, vartotojo vardas rodomas dešinėje antraštės pusėje. Spustelėkite nuorodą Atsijungti. Pamatysite šį puslapį:

    Šiame puslapyje matote, kad vartotojas yra atsijungęs, todėl svetainės meniu neberodomas. Dabar rodoma nuoroda dešinėje antraštės pusėje Prisijungti.

    Jei matote atsijungimo puslapį iš Django administratoriaus svetainės, o ne savo atsijungimo puslapį, patikrinkite INSTALLED_APPS nustatymus ir įsitikinkite, kad django.contrib.admin yra po sąskaitą. Abu šablonai yra tame pačiame santykiniame kelyje, o „Django“ šablonų įkroviklis naudos pirmąjį rastą.

    Daugiausia skirta vartotojo sąsajoms kurti. Norėdami jį naudoti, turite sukurti modelio tipą, kuris atspindi visą programos būseną, pranešimo tipą, apibūdinantį išorinius aplinkos įvykius, į kuriuos programa turi reaguoti pakeisdama savo būseną, atnaujinimo funkciją, kuri sukuria naują programos būseną. iš senos būsenos ir pranešimo bei peržiūros funkcija, kuri Remdamasi programos būsena apskaičiuoja reikiamus poveikius išorinei aplinkai, generuojančius Pranešimo tipo įvykius. Šablonas labai patogus, tačiau turi nedidelį trūkumą – neleidžia apibūdinti, kurie įvykiai yra prasmingi konkrečioms programos būsenoms.

    Panaši problema iškyla (ir išsprendžiama) naudojant būsenos OO šabloną.

    Elm kalba yra paprasta, bet labai griežta – ji patikrina, ar atnaujinimo funkcija kažkaip tvarko visus įmanomus modelio būsenos ir pranešimo įvykių derinius. Todėl jūs turite parašyti papildomą, nors ir nereikšmingą kodą, kuris paprastai palieka modelį nepakitęs. Noriu parodyti, kaip to galima išvengti sudėtingesnėmis kalbomis – Idris, Scala, C++ ir Haskell.

    Visas čia rodomas kodas yra prieinamas GitHub eksperimentams. Pažvelkime į įdomiausias vietas.


    Pranešimo funkcija neįprasta – ji grąžina tipą, o ne reikšmę. Vykdymo metu nieko nežinoma apie reikšmių tipus – kompiliatorius ištrina visą nereikalingą informaciją. Tai yra, tokią funkciją galima iškviesti tik kompiliavimo etape.

    MUV yra konstruktorius. Jis priima parametrus: modelis - pradinė programos būsena, atnaujintojas - funkcija, skirta atnaujinti būseną įvykus išoriniam įvykiui, ir vaizdas - išorinio vaizdo kūrimo funkcija. Atkreipkite dėmesį, kad atnaujinimo ir peržiūros funkcijų tipas priklauso nuo modelio reikšmės (naudojant funkciją msg iš tipo parametrų).

    Dabar pažiūrėkime, kaip paleisti šią programą

    MuvRun: (Application modelType msgType IO) -> IO a muvRun (MUV modelio atnaujinimo rodinys) = do msg<- view model muvRun (MUV (updater model msg) updater view)
    Kaip išorinį atvaizdavimą (vaizdą) pasirinkome įvesties/išvesties operaciją (Idris, kaip ir Haskell, įvesties/išvesties operacijos yra pirmos klasės reikšmės; kad jos būtų įvykdytos, reikia atlikti papildomus veiksmus, dažniausiai grąžinant tokią operaciją iš pagrindinės funkcijos).

    Trumpai apie IO

    Atliekant (IO a) tipo operaciją, atsiranda tam tikras poveikis išoriniam pasauliui, galbūt tuščias, ir programai grąžinama a tipo reikšmė, tačiau standartinės bibliotekos funkcijos suprojektuotos taip, kad ji galėtų apdorojami tik generuojant naują IO tipo reikšmę b. Taip grynos funkcijos atskiriamos nuo funkcijų, turinčių šalutinį poveikį. Tai neįprasta daugeliui programuotojų, tačiau padeda parašyti patikimesnį kodą.


    Kadangi funkcija muvRun generuoja I/O, ji turėtų grąžinti IO, bet kadangi ji niekada nebus baigta, operacijos tipas gali būti bet koks – IO a.

    Dabar apibūdinkime subjektų tipus, su kuriais ketiname dirbti

    Duomenų modelis = Atsijungta | Prisijungimo eilutės duomenys MsgOuted = Prisijungimo eilutės duomenys MsgIned = Atsijungti | Sveikiname visą msgType: Modelis -> Tipas msgType Logouted = MsgOuted msgType (Prisijungta _) = MsgIned
    Čia aprašome modelio tipą, atspindintį dviejų sąsajos būsenų buvimą – vartotojas nėra prisijungęs, o vartotojas, kurio vardas yra String, yra prisijungęs.

    Toliau aprašome du skirtingižinučių tipai, aktualūs skirtingiems modelio variantams - jei esame atsijungę, tuomet galime prisijungti tik tam tikru vardu, o jei jau esame prisijungę, galime arba atsijungti, arba pasisveikinti. Idris yra stipriai spausdinama kalba, kuri neleis sumaišyti skirtingų tipų.

    Ir galiausiai, funkcija, kuri nustato modelio reikšmės atitiktį pranešimo tipui.

    Funkcija deklaruojama kaip bendra - tai yra, ji neturėtų užstrigti ar užstrigti, kompiliatorius bandys tai stebėti. msgType iškviečiamas kompiliavimo metu, o jo visuma reiškia, kad kompiliacija neužstos dėl mūsų klaidos, nors negali garantuoti, kad šios funkcijos vykdymas išeikvos sistemos išteklius.
    Taip pat garantuojama, kad jis nevykdys „rm -rf /“, nes jo paraše nėra IO.

    Apibūdinkime atnaujintuvą:

    Visas atnaujintojas: (m:Modelis) -> (msgType m) -> Modelių atnaujintojas Atsijungtas (Prisijungimo vardas) = ​​Prisijungęs vardo atnaujinimas (Prisijungęs vardas) Atsijungti = Atsijungtas atnaujintojas (Prisijungęs vardas) Sveiki = Prisijungęs vardas
    Manau, kad šios funkcijos logika aiški. Norėčiau dar kartą pažymėti visuma - tai reiškia, kad Idris kompiliatorius patikrins, ar mes įvertinome visas tipo sistemos leidžiamas alternatyvas. Elm taip pat atlieka šį patikrinimą, bet negali žinoti, kad negalime atsijungti, jei dar nesame prisijungę, ir reikės aiškiai apdoroti sąlygą

    Atnaujintojo atsijungta Atsijungti = ???
    Idris suras tipo neatitikimus per nereikalingą patikrinimą.

    Dabar pereikime prie rodinio – kaip įprasta vartotojo sąsajoje, tai bus sunkiausia kodo dalis.

    Visas prisijungimo puslapis: IO MsgOuted loginPage = do putStr "Prisijungimas: " žemėlapis Prisijungti getLine total genMsg: String -> MsgIned genMsg "" = Atsijungti genMsg _ = Sveikiname visą darbąPuslapis: String -> IO MsgIned workPage name = do putStr ("Sveiki, " ++ name ++ "\n") putStr "Įveskite tuščią eilutę atsijungimui arba netuščią pasisveikinimo eilutę\n" žemėlapis genMsg getLine bendras vaizdas: (m: Modelis) -> IO (msgType m) vaizdas Atsijungta = prisijungimas Puslapio rodinys (Prisijungęs vardas ) = darbo puslapio pavadinimas
    rodinys turi sukurti I/O operaciją, kuri grąžina pranešimus, kurių tipas vėlgi priklauso nuo modelio reikšmės. Turime dvi parinktis: loginPage, kuris išspausdina pranešimą „Prisijungti:“, nuskaito klaviatūros eilutę ir įtraukia ją į prisijungimo pranešimą, ir „workPage“ su vartotojo vardo parametru, kuris atspausdina sveikinimą ir grąžina skirtingus pranešimus (bet to paties tipo). - MsgIned) priklausomai nuo to, ar vartotojas įveda tuščią ar ne tuščią eilutę. view grąžina vieną iš šių operacijų, priklausomai nuo modelio reikšmės, o kompiliatorius patikrina jų tipą, nors jis skiriasi.

    Dabar galime sukurti ir paleisti savo programą

    Programa: programos modelis Main.msgType IO programėlė = MUV Atsijungtas naujinimo programos vaizdas pagrindinis: IO () pagrindinis = muvRun programa
    Čia reikėtų atkreipti dėmesį į subtilų dalyką – grįžta funkcija muvRun IO a, kur a nebuvo nurodyta, o reikšmė main yra tipo IO(), Kur () yra paprastai vadinamo tipo pavadinimas Vienetas, kuris turi vieną reikšmę, taip pat parašytas kaip tuščia eilutė () . Tačiau kompiliatorius gali tai lengvai susitvarkyti. vietoj to pakeiskite a().

    Scala ir nuo kelio priklausomi tipai

    „Scala“ ne visiškai palaiko priklausomus tipus, tačiau yra tipų, kurie priklauso nuo objekto, per kurį jis nurodomas, egzemplioriaus (nuo kelio priklausomi tipai). Priklausomų tipų teorijoje juos galima apibūdinti kaip sigmos tipo variantą. Nuo kelio priklausomi tipai leidžia uždrausti pridėti vektorių iš skirtingų vektorių erdvių arba apibūdinti, kas gali ką pabučiuoti. Bet mes juos panaudosime paprastesnėms užduotims.

    Sealed abstract class MsgLogouted bylų klasė Prisijungimas(vardas: Eilutė) pratęsia MsgLogouted antspauduotą abstrakčią klasę MsgLogined bylų klasė Atsijungimas() išplečia MsgLogined bylų klasę Greet() išplečia MsgLogined abstrakčią klasę Rodinys ( def run() : Msg ) klasė antspauduotas ab types def view() : View ) case class Atsijungtas() extens Model ( type Message = MsgLogined override def view() : View .... ) case class Prisijungęs(vardas: String) extens Model ( type Message = MsgLogined override def view() ): Žiūrėti .... )
    Scala algebriniai tipai modeliuojami paveldėjimo būdu. Tipas atitinka kai kuriuos užantspauduota abstrakčioji klasė, ir kiekvienas konstruktorius iš jo paveldėjo atvejo klasė. Bandysime juos naudoti tiksliai kaip algebrinius tipus, apibūdindami visus kintamuosius kaip priklausančius pirminiam užantspauduota abstrakčioji klasė.

    Mūsų programos MsgLogined ir MsgLogouted klasės neturi bendro protėvio. Kad būtų galima pasiekti konkretaus tipo pranešimą, peržiūros funkcija turėjo būti paskirstyta skirtingose ​​modelio klasėse. Tai turi savų privalumų, kuriuos įvertins OO rėmėjai – kodas sugrupuotas pagal verslo logiką, šalia yra viskas, kas susiję su vienu naudojimo atveju. Bet aš mieliau išskirčiau požiūrį į atskirą funkciją, kurios plėtojimą būtų galima perleisti kitam žmogui.

    Dabar įdiegkime atnaujinimą

    Objekto naujinimo priemonė ( def update(modelis: modelis)(msg: model.Message) : Model = ( modelio atitiktis ( case Logouted() => msg match ( atvejis Prisijungimas(vardas) => Prisijungtas(vardas) ) atvejis Prisijungtas(vardas)) => msg match ( Case Logout() => Logouted() Case Greet() => model ) ) ) )
    Čia mes naudojame nuo kelio priklausomus tipus, kad apibūdintume antrojo argumento tipą iš pirmojo. Kad „Scala“ priimtų tokias priklausomybes, funkcijos turi būti aprašytos suformuluota forma, ty kaip funkcija iš pirmojo argumento, kuri grąžina funkciją iš antrojo argumento. Deja, šiuo metu „Scala“ neatlieka daug tipo patikrinimų, kuriems kompiliatorius turi pakankamai informacijos.

    Dabar pateikime pilną modelio įgyvendinimą ir vaizdą

    Atvejo klasė Logouted() praplečia modelį ( type Message = MsgLogouted override def view() : View = new View ( override def run() = ( println("Įveskite pavadinimą ") val name = scala.io.StdIn.readLine() Prisijungimas (vardas) ) ) atvejo klasė Prisijungęs(vardas: String) pratęsia Modelį ( type Message = MsgLogined override def view() : View = new View ( override def run() = ( println(s"Hello, $name") println ( "Tuščia eilute atsijungimui, nonempy sveikinimui.") scala.io.StdIn.readLine() atitiktis ( case "" => Logout() case _ => Greet() ) ) ) abstract class View ( def run( ) ): Msg ) object Viewer ( def view(modelis: Model): View = ( model.view() ) )
    Rodinio funkcijos grąžinimo tipas priklauso nuo jos argumento egzemplioriaus. Tačiau įgyvendinimui reikia modelio.

    Tokiu būdu sukurta programa paleidžiama taip:

    Pagrindinis objektas ( importuoti scala.annotation.tailrec @tailrec def process(m: modelis) ( val msg = Viewer.view(m).run() process(Updater.update(m)(msg)) ) def main(args: Masyvas) = ​​( procesas (atsijungta()) ) )
    Taigi vykdymo sistemos kodas nieko nežino apie vidinę modelių struktūrą ir pranešimų tipus, tačiau kompiliatorius gali patikrinti, ar pranešimas atitinka dabartinį modelį.

    Čia mums nereikėjo visų nuo kelio priklausomų tipų teikiamų galimybių. Įdomios savybės atsiras, jei dirbsime lygiagrečiai su keliais Model-Updater-View sistemų egzemplioriais, pavyzdžiui, kai imituosime kelių agentų pasaulį (vaizdas tada atspindėtų agento įtaką pasauliui ir grįžtamojo ryšio gavimą). Šiuo atveju kompiliatorius patikrino, ar pranešimą apdorojo būtent tas agentas, kuriam jis buvo skirtas, nepaisant to, kad visi agentai turi tą patį tipą.

    C++

    C++ vis dar yra jautrus apibrėžimų tvarkai, net jei jie visi sukurti tame pačiame faile. Tai sukelia tam tikrų nepatogumų. Kodą pateiksiu tokia seka, kuri patogi idėjoms demonstruoti. Kompiliuojamą versiją galite rasti „GitHub“.

    Algebrinius tipus galima įgyvendinti taip pat, kaip ir Scala - abstrakčioji klasė atitinka tipą, o konkretūs palikuonys atitinka algebros konstruktorius (vadinkime juos „konstruktorių klasėmis“, kad nebūtų painiojami su įprastais C++ konstruktoriais). tipo.

    C++ palaiko nuo kelio priklausomus tipus, tačiau kompiliatorius negali naudoti tipo abstrakčiai, nežinodamas tikrojo tipo, su kuriuo jis susietas. Todėl su jų pagalba neįmanoma įdiegti Model-Updater-View.

    Tačiau C++ turi galingą šablonų sistemą. Tipo priklausomybė nuo modelio reikšmės gali būti paslėpta specializuotos vykdomosios sistemos versijos šablono parametre.

    Struct Processor ( virtual const Processor *next() const = 0; ); šabloną struct ProcessorImpl: viešasis procesorius ( const CurModel * modelis; ProcessorImpl (const CurModel* m) : modelis(m) ( ); const Procesorius *next() const ( const View * vaizdas = modelis->vaizdas(); const tipo pavadinimas CurModel::Message * msg = view->run(); ištrinti vaizdą; const Model * newModel = msg->process(modelis); ištrinti žinutę; return newModel->processor(); ) );
    Aprašome abstrakčią vykdymo sistemą su vienu metodu atlikti viską, ko reikia, ir grąžinti naują vykdymo sistemą, tinkamą kitai iteracijai. Konkreti versija turi šablono parametrą ir bus specializuota kiekvienai modelio „konstruktorių klasei“. Čia svarbu, kad visos CurModel tipo savybės būtų tikrinamos šablono specializavimo metu su konkrečiu tipo parametru, o sudarant patį šabloną jų aprašyti nebūtina (nors galima naudojant sąvokas ar kitus tipų klasių įgyvendinimo būdus). „Scala“ taip pat turi gana galingą parametrizuotų tipų sistemą, tačiau ji tikrina parametrų tipų savybes, kai sudaromas parametrizuotas tipas. Ten tokio modelio įgyvendinimas yra sunkus, bet įmanomas dėl tipo klasių palaikymo.

    Apibūdinkime modelį.

    Struktūros modelis ( virtualus ~Model() (); virtualus const Procesorius *procesorius() const = 0; ); struct Prisijungęs: viešas Modelis ( struct Pranešimas ( const virtual Model * process(const Logined * m) const = 0; virtual ~Message() (); struct Atsijungti: viešas pranešimas ( const Model * process(const Prisijungęs * m) const; struct Sveiki: viešas pranešimas ( const Model * process(const Prisijungęs * m) const; std::string name; (...); const Žiūrėti * view() const ( grąžinti naują LoginedView(vardas); ); const Procesorius *procesorius() const ( grąžinti naują ProcessorImpl (tai); ); ); struct Atsijungta: viešas Modelis ( struct Pranešimas ( const virtual Model * process(const Atsijungtas * m) const = 0; virtualus ~ Pranešimas() (); struct Prisijungimas: viešas pranešimas ( const std::string name; Prisijungimas (std :: string lname) : name(lname) ( ); (...); const Žiūrėti * view() const ( grąžinti naują LogoutedView(); ); const Procesorius *procesorius() const ( grąžinti naują ProcessorImpl (tai); ); );
    Modelio „konstruktorių klasės“ „nešasi viską su savimi“ - tai yra, jose yra joms skirtos žinutės ir peržiūros klasės, taip pat gali patys susikurti vykdomąją sistemą. Native View tipai turi bendrą visų modelių pirmtaką, kuris gali būti naudingas kuriant sudėtingesnes vykdymo sistemas. Svarbu, kad pranešimų tipai būtų visiškai izoliuoti ir neturėtų bendro protėvio.

    Atnaujinimo programos įgyvendinimas yra atskiras nuo modelio, nes reikalauja, kad modelio tipas jau būtų visiškai aprašytas.

    Const Model * Atsijungta::Prisijungimas::process(const Atsijungta * m) const ( ištrinti m; grąžinti naują Prisijungęs(vardas); ); const Modelis * Prisijungęs::Atsijungtas::process(const Prisijungęs * m) const ( ištrinti m; grąžinti naują Atsijungtas(); ); const Modelis * Prisijungęs::Greet::process(const Prisijungęs * m) const ( return m; );
    Dabar sudėkime viską, kas susiję su vaizdu, įskaitant vidinius modelių objektus

    Šablonas struct View ( virtual const Pranešimas * run() const = 0; virtualus ~View () (); ); struct Prisijungta: public Model ( struct LoginedView: public View ( const std::string name; LoginedView(std::string lname) : name(lname) (); virtual const Pranešimas * run() const ( char buf; printf("Sveiki, %s", name.c_str()) fgets(buf, 15, stdin) return (*buf == 0 || *buf == "\n" || *buf == "\r") ? (naujas Atsijungti()) : static_cast (naujas Greet); ); ); const Žiūrėti * view() const ( grąžinti naują LoginedView(vardas); ); ); struct LogoutedView: viešas vaizdas ( virtual const Pranešimas * run() const ( char buf; printf ("Prisijungti: "); fgets(buf, 15, stdin); return new Login(buf); ); ); const Žiūrėti * view() const ( grąžinti naują LogoutedView(); ); );
    Ir galiausiai, parašykime pagrindinį

    Int main(int argc, char ** argv) ( const Processor * p = new ProcessorImpl (naujas Atsijungtas()); while(true) (const Procesorius * pnew = p->next(); ištrinti p; p = pnew; ) grąžinti 0; )

    Ir vėl Scala, šį kartą su tipo klasėmis

    Struktūra šis įgyvendinimas beveik visiškai atkartoja C++ versiją.

    Panaši kodo dalis

    abstract class Rodymas ( def run(): Pranešimas ) abstract class Procesorius ( def next(): Procesorius; ) antspauduota abstrakti klasė Modelis ( def processor(): Procesorius ) antspauduota abstrakčioji klasė LoginedMessage case class Atsijungti() išplečia LoginedMessage atvejo klasę Sveikin ) pratęsia PrisijungęsŽinutės atvejo klasė Prisijungtas(val pavadinimas: String) pratęsia Modelį (nepaisyti def procesorius(): Procesorius = new ProcessorImpl(this) ) užplombuota abstrakti klasė LogoutedMessage case class Prisijungimas(vardas: String) pratęsia AtsijungtaŽinutės atvejo klasė Atsijungta() išplečia Modelį ( override def processor(): Processor = new ProcessorImpl(this) ) object Main ( import scala.annotation.tailrec @tailrec def process(p: Processor) ( process(p.next()) ) def main(args: Array) = ( process(new ProcessorImpl(Atjungta())) ) )


    Tačiau įgyvendinant vykdymo aplinką atsiranda subtilybių.

    Klasė ProcesoriusImpl(modelis: M)(numanomas atnaujinimas: (M, pranešimas) => Modelis, vaizdas: M => Rodinys) išplečia procesorių ( def next(): Procesorius = ( val v = vaizdas(modelis) val msg = v. paleisti() val newModel = atnaujintojas(modelis,msg) newModel.processor() ) )
    Čia matome naujus paslaptingus parametrus (numanomas atnaujinimas: (M, pranešimas) => Modelis, vaizdas: M => Rodinys). Implicit raktažodis reiškia, kad iškviesdamas šią funkciją (tiksliau, klasės konstruktorių), kompiliatorius ieškos tinkamo tipo objektų, kontekste pažymėtų kaip implicit, ir perduos juos kaip tinkamus parametrus. Tai gana sudėtinga koncepcija, kurios viena iš programų yra tipo klasių įgyvendinimas. Čia jie kompiliatoriui pažada, kad konkretiems modelio ir pranešimo diegimams visas reikalingas funkcijas suteiksime mes. Dabar ištesėkime šį pažadą.

    Objekto atnaujinimai ( implicit def logoutedUpdater(modelis: Atsijungta, msg: Atsijungta Pranešimas): Modelis = ( (modelis, msg) atitiktis ( Case (Logouted(), Prisijungimas(vardas)) => Prisijungtas(vardas) ) ) implicit def viewLogouted(modelis) : Atsijungta) = naujas Rodinys ( nepaisyti def run() : LogoutedMessage = ( println("Įveskite pavadinimą ") val name = scala.io.StdIn.readLine() Prisijungimas(vardas) ) ) implicit def loginedUpdater(modelis: Prisijungta, msg : Prisijungęs pranešimas): Modelis = ( (modelis, žinutė) atitiktis ( case (Prisijungęs(vardas), Atsijungęs()) => Atsijungtas() atvejis (Prisijungęs(vardas), Sveiki()) => modelis ) ) implicit def viewPrisijungęs( modelis: Prisijungęs) = naujas Rodinys ( val pavadinimas = model.name override def run() : LoginedMessage = ( println(s"Sveiki, $name") println("Tuščia eilute atsijungti, nonempy pasisveikinti.") scala.io .StdIn.readLine() atitikmuo ( atvejis "" => Atsijungti() atvejis _ => Sveikinimas() ) ) ) importuoti atnaujinimus._

    Haskell

    Pagrindiniame Haskell priklausomų tipų nėra. Jam taip pat trūksta paveldėjimo, kurį mes labai panaudojome įgyvendindami modelį Scala ir C++. Tačiau vieno lygio paveldėjimą (su priklausomų tipų elementais) galima modeliuoti naudojant daugiau ar mažiau standartinius kalbos plėtinius - TypeFamilies ir ExistentialQuantification. Bendrai vaikų OOP klasių sąsajai sukuriama tipo klasė, kurioje yra priklausomas „šeimos“ tipas, pačios antrinės klasės vaizduojamos atskiru tipu, o po to apvyniojamos „egzistenciniu“ tipu su vienu konstruktoriumi. .

    Duomenų modelis = visiems m. (Atnaujinama m, Matoma m) => Modelio m klasė Atnaujinama m kur duomenys Pranešimas m:: * atnaujinimas:: m -> (Pranešimas m) -> Modelio klasė (Atnaujinamas m) => Matomas m kur vaizdas:: m -> (Žiūrėti (Pranešimas m)) duomenys Atsijungti = Atsijungti duomenys Prisijungti = Prisijungta eilutė
    Bandžiau kiek įmanoma atskirti atnaujinimą ir peržiūrą, todėl sukūriau dvi skirtingų tipų klases, bet iki šiol tai nepasiteisino.

    Atnaujinimo diegimas yra paprastas

    Egzempliorius Atnaujinamas Atsijungta, kur duomenys Žinutė Atsijungta = Prisijungimo eilutės atnaujinimas Atsijungta (Prisijungimo vardas) = ​​Modelis (Prisijungęs vardas) Atnaujinamas pavyzdys Prisijungta, kur duomenys Pranešimas Prisijungtas = Atsijungti | Pasisveikinimo atnaujinimas m Atsijungti = Modelis Atsijungtas atnaujinimas m Pasisveikinimas = Modelis m
    Turėjau pataisyti IO kaip View. Bandymai jį padaryti abstrakčiau labai viską apsunkino ir padidino kodo susiejimą – Modelio tipas turi žinoti, kurį View'ą naudosime.

    Importuoti System.IO tipą Žiūrėti a = IO egzempliorių Žiūrėti Atsijungta, kur vaizdas Atsijungta = do putStr "Prisijungti: " hFlush stdout fmap Prisijungti getLine egzempliorius Matomas Prisijungta, kur vaizdas (Prisijungęs vardas) = ​​do putStr $ "Sveiki " ++ vardas ++ " !\n" hFlush stdout l<- getLine pure $ if l == "" then Logout else Greeting
    Na, vykdomoji aplinka mažai skiriasi nuo panašios Idris

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

  • mob_info