Registrieren Einloggen
Passwort vergessen
Index Letzte Beiträge (0) Gruppen Suchen Wer ist online?
RSS-Feed Formel-Generator Grafiker-Galerie PB-Lexikon
FAQ Support-Unterforum
Die Netiquette
PureBasic-Lounge (PureBasic - Forum) Foren-Übersicht Win-API PureBasic-Library
Benutzer
Forum (0)
Diverses
Hilfe
PB-Lexikon
 Quick - Stats 
:: Beliebteste Artikel :: Beste Artikel :: Letzte Artikel ::

Autor Hroudtwolf Datum Mon Sep 14, 2009 1:23 am Ansichten 698
Beschreibung Model view controller Entwurfsmuster.
Kategorie Fortgeschrittenes Typ Allgemeine Programmierung

MVC mit PB (Supi, es reimt sich.)
MVC mit PB (Supi, es reimt sich.)

Wer MVC (Model view controller) aus der Web-Programmierung kennt, weiss die Strukturierung welche
dieses Model mit sich bringt zu schätzen.
Vor einiger Zeit fragte ich mich wie man das wohl per PureBasic umsetzen könnte.

Anders als z.B. mit PHP hat man mit PB keinen Laufzeit-Interpreter.
Dadurch fallen natürlich einige Vorteile weg die man von PHP MVC-Patterns her kennt.
Z.B. wäre da das Autoload von Controllers bei Request und Views bei Instanzierung, wie es einige Pattern machen.
In PureBasic müssen wir alle Controllers, alle Views, und alle Models natürlich fest einbinden.
Die bekannten Vorteile der sauberen Strukturierung bleiben uns aber selbstverständlich trotzdem erhalten.

Das von mir für PB entwickelte MVC-Pattern ist auf die Ausgabe der GUI auf einem Webgadget ausgelegt.
Benutzereingaben werden vom Webgadget über ein Callback geholt und ein Dispatcher entscheidet welcher
Controller für den jeweiligen Benutzer-Request instanziert werden soll.

Neben den Vorteilen des MVC-Entwurfsmusters birgt die Ausgabe von GUIs auf einem WebGadget den Vorteil dass man die GUI viel einfacher layouten kann. Auch bei Grössenänderungen des Fensters passt sich das Layout, wenn mans per HTML so vorgesehen hat, auch automatisch der Fenstergrösse an (WebGadget muss mit resized werden.).
Zudem kann JavaScript eingesetzt werden. Ein mächtiger Partner beim gestallten einer Interaktiven HTML-GUI.

Zunächst aber erst einmal zur verdeutlichung ein paar Details zum Thema.
Ein klassisches MVC-Pattern ist, wie der Name schon sagt in 3 Aspekte unterteilt.

[Controller] Module unterschiedlicher Funktions-Aspekte.
[Model] Module für Zugriffe auf Datenquellen.
[View] Alles rund um das grafische Benutzerinterface. Komponenten des Renderings.

Es gibt soviele Controller wie es Funktions-Aspekte im Programm gibt.
Im PB MVC-Pattern hätte ein Controller z.B. folgende Aufgaben.

Beispiel-Aspekte:

Aspekt: Dokument

  • Speichern
  • Laden
  • Neues Dokument
  • Kopieren
  • Einfügen
  • Ausschneiden


Aspekt: Konfiguration

  • GUI Einstellungen
  • Pfade definieren


Models wird es in der Applikation soviele geben wie es unterschiedliche Aspekte der Datenverwaltung gibt.
Z.B. könnte es da ein Model für Dateimanagement und ein Model für Datenbank-Zugriffe geben.
Geschickter wäre es aber, die Modele feiner in Unteraspekte zu gliedern.
Z.B. (rein fiktiv) ein Model für Zugriffe auf die Benutzertabelle, und einem Model für Zugriffe auf
die Artikel-Tabelle einer Datenbank.
Jeweils mit für ihren eigenen Zweck angepassten Schnittstellen.

Views können Module sein mit denen ganz bestimmte Render-Operationen durchgeführt werden sollen.
Es können aber auch Templates (HTML) für unterschiedlichste Zwecke sein.

Die 3 Aspekte des Patterns arbeiten wie in folgendem, fiktiven Szenario zusammen.
Der Benutzer macht eine Eingabe um z.B. ein neues Dokument auf unserer, per MVC entwickelten Anwendung
zu erstellen.
Der interne Dispatcher erkennt die Eingabe und erstellt eine Instanz des Dokument-Controllers.
Ein weiterer Teil der Benutzereingabe beschrieb die daraus aufzurufende Funktion.
Es wird also der Dokument-Controller instanziert und dessen New-Funktion aufgerufen.
In der New-Funktion wird das Dokument-Model instanziert. Also jenes Model das für die Datenhandhabung
von Dokumenten zuständig ist.
Aus dieser Model-Instanz wird ebenfalls die New-Funktion aufgerufen.
Es wird der Rückgabe-Wert dieser Funktion ausgewertet und z.b. ein "OK" oder ein "Not OK" in eine
Message-Variable geschrieben.
Es wird nun das GUI-View geladen, ebenfalls das Message-View. Beides sind HTML-Templates.
Das Message-View wird mit dem Wert aus unserer Message-Variablen ausgefüllt.
Das GUI-View wird mit dem fertigen, ausgefüllten HTML-Code des Messsage-View ausgefüllt.
Nun wird das GUI-View an das WebGadget gesandt. Und fertig war der Funktions-Ablauf.

Die Funktionen des Controllers nenne ich im Tutorial fortan richtigerweise Methods.
Denn die Controllers werden im Pattern ähnlich Klassen implementiert.

Illustration eines MVC-Szenarios in einer betrieblichen Verwaltungs-Software.
Image

Wie oben bereits geschrieben, kommt beim folgend erklärten MVC-Pattern das Webgadget zum Einsatz.
Dort wird die GUI-Dargestellt und die Benutzereingaben werden von dort aus zum Programm gesendet.
Dazu bedienen wir uns der Möglichkeit einem Webgadget ein Ereigniss-Callback zuweisen zu können.
Alle Benutzereingaben werden dann zu diesem Callback gesendet und von dort aus ausgewertet.
Doch zunächst einmal ein Beispiel zum Webgadget+Callback:
#WEBGADGET = 0

Declare.i _WebGadgetCB (idGadget.i , sURL.s)

OpenWindow (#PB_Any, #PB_Ignore, #PB_Ignore, 640, 480, "MVC-Example" )
WebGadget (#WEBGADGET, 0, 0 , 640, 480, "" )

SetGadgetItemText (#WEBGADGET, #PB_Web_HtmlCode, PeekS (?RES_HTML_EXAMPLE))
SetGadgetAttribute (#WEBGADGET, #PB_Web_NavigationCallback, @_WebGadgetCB ())

Repeat : Until WaitWindowEvent () = #PB_Event_CloseWindow
End

; Das Callback an welches alle angefragten URLs gesendet wird.
Procedure.i _WebGadgetCB (idGadget.i , sURL.s)
   
   Debug sUrl
 
   ; Wichtig! Legt die Anfrage tot.
   ProcedureReturn #False
EndProcedure

; Nur für dieses Beispiel notwendig.
DataSection
   RES_HTML_EXAMPLE:
   Data.s "<html><body><a href=" + #DOUBLEQUOTE$ + "http://controllerA/methodA" + #DOUBLEQUOTE$ + ">Controller A::Method A</a><br /><a href=" + #DOUBLEQUOTE$ + "http://controllerA/methodB" + #DOUBLEQUOTE$ + ">Controller A::Method B</a><br /><a href=" + #DOUBLEQUOTE$ + "http://controllerB/methodA" + #DOUBLEQUOTE$ + ">Controller B::Method A</a><br /></body></html>"
EndDataSection


Im Code sind 3 Links zu sehen. Jene sind wie folgt formatiert:
{Protokol}://{Name des Controllers}/{Name der aufzurufenden Method des Controllers}[/{Verschiedene Parameter[/...]}]


Dieses Format ist für den Dispatcher wichtig, über den ich nun ein paar Worte schreiben werde.
Der Dispatcher ist eine Funktion welche die URLs die das WebGadget-Callback empfängt auseinandernimmt und aufgrund
der darin enthaltenen Informationen den adäquaten Controller zu instanzieren und ferner die davon angeforderte Method
aufzurufen.

Controllers

Ohne Controllers ist der beste Dispatcher nur eine arbeitslose Funktion.
Daher beschreibe ich zunächst die Implementierung eines Controllers.

Die Controllers benötigen eine standartisierte Schnittstelle.
Das heisst, alle Controllers müssen exakt gleich angesprochen werden können.
Sowas ist zwar keine Anforderung im klassischen MVC, jedoch notendig für speziell mein PB MVC-Pattern.
MVC kommt am häufigsten in der Programmiersprache PHP zum Einsatz.
Dort kann der Dispatcher prüfen ob eine Controller-Klasse eine bestimmte Method besitzt bevor er versucht sie
auszuführen.
Grund dafür ist, dass PHP eine interpretierte Sprache ist. In PureBasic geht das nativ daher nicht.

Unsere Controllers werden künfigt aussehen wir unser erster Beispiel-Controller:
Structure cControllerA
   irgendwelchedaten.i
EndStructure

Procedure.i ControllerA_MethodA (*this.cControllerA, Array sArguments.s (1), nArguments.i)
   
   Debug "Method A of controller A was called."
   
   ProcedureReturn #true
EndProcedure

Procedure.i ControllerA_MethodB (*this.cControllerA, Array sArguments.s (1), nArguments.i)
   
   Debug "Method A of controller A was called."
   
   ProcedureReturn #True
EndProcedure

Procedure.i CallController_ControllerA (sCallMethod.s, Array sArguments.s (1), nArguments.i)
   Static this.cControllerA
   
   Select sCallMethod
      Case "methodA"
      ProcedureReturn ControllerA_MethodA (this, sArguments (), nArguments)
     
      Case "methodB"
      ProcedureReturn ControllerA_MethodB (this, sArguments (), nArguments)
     
   EndSelect
   
   ProcedureReturn #False
EndProcedure


Die Methodes unseres Beispiel-Controllers bekommen als erstes Argument den Zeiger auf die privaten Daten
der zugehörigen Controller-Klasse mitgeteilt.
Als Klasse wird hier ein Controller aufgrund seiner Kapselung der privaten Daten bezeichnet.
Die nächsten Parameter sind jeweils das Argument-Array und die Argument-Anzahl welche durch den Dispatcher über
die CallController-Funktion beim Aufruf an die Methods übermittelt werden.

Wie im Code zu sehen ist, bekommt die CallController-Funktion des jeweiligen Controllers Method-Name, Aufruf-Argumente und Anzahl
der Aufruf-Argumente mitgeteilt.
Diese Argumente stammen von der Dispatcher-Funktion.

Um verstehen zu können wie die Controllers funktionieren werden, benötigen wir zunächst den Dispatcher.
Denn dieser ist der zentrale Kern aller unserer künftigen Controllers.
Enumeration
   #REQUESTPART_CONTROLLER
   #REQUESTPART_METHOD
EndEnumeration

Procedure.i RequestDispatcher (sQuery.s)
   Protected         nArguments .i
   Protected         nArgument  .i
   Protected Dim     sArguments .s (255)
   Protected         sRequestKey.s
   
   ; Die Folgenden Zeilen zerhauen die URL in Einzelteile
   ; und legen die Einzelteile in ein Array. Das Argumente-Array.
   sQuery = RemoveString (sQuery , "http://" , #PB_String_NoCase)
       
   If Right (Trim (sQuery) , 1) <> "/"
      sQuery = Trim (sQuery) + "/"
   EndIf
   
   sRequestKey = StringField (sQuery, 1, "/")
   nArguments  = CountString (sQuery, "/")

   For nArgument = 1 To nArguments
      sArguments (nArgument - 1) = StringField (sQuery, nArgument, "/")
   Next nArgument

   ; Auswertung des angefragten Controllers.
   Select LCase (sArguments (#REQUESTPART_CONTROLLER))
      Case "controllera"
         ; Weiterleitung.
         blControllerResult = CallController_ControllerA (sArguments (#REQUESTPART_METHOD), sArguments (), nArguments)
     
      ; Weitere Controllers.....
     
      Default
      ; Mögliche Fehlerverarbeitung.
      Debug "Controller unbekannt."
     
   EndSelect

   If Not blControllerResult
      ; Mögliche Fehlerverarbeitung.
   EndIf
   
   ProcedureReturn #False
EndProcedure

; WebGadget-Callback.
; Hier werden die Anfragen aus dem WebGadget abgefangen,
; an den Dispatcher übergeben und in Richtung Internet totlegen.
Procedure.i _WebGadgetCB (idGadget.i , sURL.s)

   ; Übergabe der URL an den Dispatcher.
   RequestDispatcher (sURL)

   ; Anfrage in Richtung Internet totlegen.
   ProcedureReturn #False
EndProcedure


Selbst im Verbund entsprechend die bereits gezeigten Code-Abschnitte jedoch noch nicht MVC.
Bisher sind wir soweit durch eine HTML-Maske verschiedene Controllers anzusprechen.
Noch aussen vor blieben die Views und die Models.

Models

Es steht fest, dass Models bestenfalls Klassen oder auch Module sind welche eine Schnittstelle zur Handhabung von
Datenverwaltungs-Operationen zur Verfügung stellen.
Zum Beispiel könnte es in einer betrieblichen Verwaltungs-Software Models für Benutzer-Verwaltung, Artikel-Verwaltung,
Rechnungs-Verwaltung, Lager-Buchungen und so weiter, geben.
Eben Zugriffe auf die Datenbank. Und zwar aufgeteillt in separate Aspekte.

In den Methods der Controllers werden Models für verschiedene Zwecke genutzt.
Folgendes Beispiel zeigt eine Controller-Method welche unterschiedliche Models nutzt.
Die Models sind gepräfixt mit *model_, der bessern Übersicht halber.
Procedure.i Controller_Articles_viewarticle (*this.cController_Articles, Array sArguments.s (1), nArguments.i)
   Protected *model_UserHandler     .IcModel_UserHandler  = getInstance_Model_UserHandler     ()
   Protected *model_ErrorHandler    .IcModel_ErrorHandler = getInstance_Model_ErrorHandler    ()
   Protected *model_Articles        .IcModel_Articles     = getInstance_Model_Articles        ()
   Protected article                .tModelData_Article 
   
   If (nArguments<3)
      *model_ErrorHandler \throwException ("Argument missed for method viewarticle.")
   EndIf
   
   If (*model_UserHandler\permissionLevel () < #MODEL_USERHANDLER_PERMLEVEL_MANUFACTURER)
      *model_ErrorHandler \throwError ("No permissions to use this function.")
      ProcedureReturn #False
   EndIf
   
   *model_Articles\getArticle (article, sArguments (2))   
   
   ; Rendering.....   
   
   ProcedureReturn #True
EndProcedure


Es ist hier schon gut zu sehen wofür Models stehen.
Man sieht deutlich dass sie nur der Datenbeschaffung und Manipulation von/zu etwaige(n) Datenquellen dienen.

Views

Alles was die Anwendung an GUI-Elementen ausspuckt entstammt sogenannten Views.
Im Code werden View-Methods Daten übergeben die z.B. durch Models beschafft wurden und die jeweilge View-Methods
rendert daraus eine GUI.

Szenario-Beispiel:

Eine View-Method mit dem fiktiven Namen *view\article (article) erhält den Datensatz eines Artikels,
so wird diese diesen Datensatz gemass ihren Darstellenungs-Konventionen auf z.B. eine Ausgabe-Maske rendern.
Zum Beispiel würde diese Method eine HTML-Tabelle generieren welche diesen Datensatz auflistet und den
Tabellen-Code zurückgeben.
Der Controller seinerseits, der die View nutzt, sendet diesen Output am Ende seiner Arbeit mit allen andern
Outputs aller andern genutzten View-Methods an das WebGadget.
So entsteht die Ausgabe auf dem WebGadget.

Beispiel-Code:
Procedure.i Controller_Articles_viewarticle (*this.cController_Articles, Array sArguments.s (1), nArguments.i)
   Protected *model_UserHandler     .IcModel_UserHandler  = getInstance_Model_UserHandler     ()
   Protected *model_ErrorHandler    .IcModel_ErrorHandler = getInstance_Model_ErrorHandler    ()
   Protected *model_Articles        .IcModel_Articles     = getInstance_Model_Articles        ()
   Protected article                .tModelData_Article 
   Protected *view_Articles         .IcView_Articles      = getInstance_View_Articles         ()
   Protected *view_Default          .IcView_Default       = getInstance_View_Default          ()
   Protected sOutput                .s
   
   If (nArguments<3)
      *model_ErrorHandler \throwException ("Argument missed for method viewarticle.")
   EndIf
   
   If (*model_UserHandler\permissionLevel () < #MODEL_USERHANDLER_PERMLEVEL_MANUFACTURER)
      *model_ErrorHandler \throwError ("No permissions to use this function.")
      ProcedureReturn #False
   EndIf
   
   sOutput = *view_Default\header ()
   
   If *model_Articles\getArticle (article, sArguments (2))     
      sOutput + *view_Articles\article (article)
   Else
      *model_ErrorHandler \throwException ("Couldn't render article.")
   EndIf
   
   sOutput + *view_Default\footer ()
   
   SetGadgetItemText (#WEBGADGET, #PB_Web_HtmlCode, sOutput)
   
   ProcedureReturn #True
EndProcedure


Zur Anregung...
Nutzt man statt View-Klassen eine Template-Engine können Views auch HTML-Templates sein.
So ein Template könnte dann z.B. so aussehen.

<table>
    <tr>
        <th>Articles list</th>
    </tr>
    <!--FOREACH-ARTICLE-
    <tr>
        <td width="25%">{ARTICLE.price}</td>
        <td width="25%">{ARTICLE.stored}</td>
        <td width="50%">{ARTICLE.text}</td>
    </tr>
    -->
</table>


Die Template-Engine welche so eine Syntax nutzt würde mit <!--FOREACH-ARTICLE-/--> funktionieren
wie PureBasic mit Foreach/Next.
Die Inhalte in den geschweiften Klammern würden durch Inhalte des jeweils interierten
Elements des an die Template-Engine übergebenen Datensatze-Arrays ARTICLE ersetzt.
Wie man eine solchen Template-Engine konzipiert, darauf gehe ich hier nicht näher ein.
Zum Beispiel könntest du dich an der Smarty-Templateengine orientieren die es für PHP gibt.

Noch zum Schluss zum Pattern....
Das Pattern basiert, wie beschrieben auf GUIs welche im WebGadget gerendert werden und von denen aus die
Benutzer-Eingaben an die Applikation gehen.
Ein Problem besteht in der Eingabe von grösseren Datenmengen in Formularen.
Diese müssen per POST Methode an die Applikation gesendet werden.
Das WebGadget-Callback kann diese Daten aber nicht nativ zugänglich machen.
Hierzu kannst du dir aber selbst helfen.
Lies dazu folgenden Artikel um dir die notwenigen Informationen dazu zu beschaffen.
http://purebasic-lounge.com/viewtopic.php?p=59169#59169

Das wars auch schon.
Gerne helfe ich Fragen auszuräumen wenn noch welche offen sind.
  

 Benutzer-Kommentare 
hajol Verfasst am: Mon Sep 14, 2009 8:59 pm    Titel

Bravo!

Danke Wolf
bembulak Verfasst am: Tue Sep 15, 2009 1:58 pm    Titel

Danke!

Abgesehen von MVC (ich finde die "Erweiterung" MVP [Module-View-Presenter] für mein Denken noch passender), würde ich mich noch interessieren, wie sehr man sein GUIs in HTM+JavaScript basteln kann.

Tolles Tutorial.
Hroudtwolf Verfasst am: Tue Sep 15, 2009 4:15 pm    Titel

Hi,

HTML besitzt von Haus aus schon einige Controll-Elemente die man für das Basteln von GUIs nutzen kann.
Spezielleres muss man sich selbst basteln.
In folgendem Beispiel habe ich ein einfaches Listview-Gadget mit Javascript und HTML/CSS nachgebaut.

(Einfach die im ZIP liegende Datei im Browser ausführen.)
Download

Anders wie im Beispiel kann man die eigenen Controll-Elemente natürlich auch schön in JS-Funktionen gekapselt implementieren. So dass man sie leicht mehrfach im Projekt recyclen kann.

Ich hab derzeit nicht viel Zeit. Daher muss das eine Beispiel leider vorerst ausreichen.

MfG
Wolf

PS: Das Beispiel funktioniert hier unter FF 3.x, Safari und IE 7. Für andere Browser gebe ich keine Garantie.
Unter Opera funktionert es natürlich nicht.
Quick KB Navigation 
 ACHTUNG 
Die hier gespeicherten Inhalte sind Urheberrechts geschützte Werke nach europäischem Recht.
Eine Veröffentlichung dieser Inhalte auf andern Webseiten oder Printmedien ist ohne explizite
Erlaubnis des Forenbetreibers, in Stellvertretung der Autoren nicht gestattet.

Jeder hier, durch die Benutzer des Forums eingereichten Inhalte bleibt geistiges Eigentum des
jeweiligen Autoren.

You cannot post new articles in this category
You cannot edit your articles in this category
You cannot delete your articles in this category
You cannot comment articles in this category
You cannot rate articles in this category
Articles need no approval in this category
Article edits need no approval in this category


Alle Zeiten sind GMT
Powered by phpBB2 Plus © 2001, 2002 phpBB Group :: Designed by DerWebWolf.de

Brieffreunde finden im Internet mit Net-Channel.de