Programmaufbau

Konzeptionelles, Technisches, Termine, Fragen zum Hersteller usw.

Moderator: Moderatoren

Antworten
Robert
Cut&Paste-Entwickler
Cut&Paste-Entwickler
Beiträge: 36
Registriert: Mo, 13. Feb 2006 12:47

Programmaufbau

Beitrag von Robert »

Hallo,

mich interessiert, wie ihr im allgemeinen eure Anwendungen aufbaut. Nutzt ihr das MVC-Pattern ( http://de.wikipedia.org/wiki/MVC ) und trennt strikt Oberfläche, Daten und Logik, oder doch nicht ganz so strickt und vermischt die ein oder andere Schicht?

Vlleicht habt ihr auch andere Ansätze, eure Anwendung zu strukturieren?

Über die ein oder anderen Codeschnipsel und Meinungen würde ich mich freuen.
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15697
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 66 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Beitrag von brandelh »

Hi,

also wenn man einmal eine fremde Anwendung zu inspizieren / pflegen / weiter zu entwickeln hatte, die ausgiebig von den Validate und When Regeln in Clipper gebraucht gemacht hat, weiß man schon, dass es sinnvoll ist die Programmlogik von der Optik und den Daten zu trennen.

Den goldenen Mittelweg habe ich aber noch nicht gefunden.
Der erwähnte Artikel ist mir zu theoretisch.

Ich habe gute Erfahrungen damit gemacht zuerst die Bilder zu malen, die die Anwender erwarten, denn die denken in Bildern und nicht in Datensätzen. Danach generiere ich die DBs mit den Feldern, plus denen, die ich noch zusätzlich brauche und in einer Aufteilung, die in etwa der 3. Normalform entspricht (keine doppelten Daten, 1:n Beziehungen aufgelöst, 1:1 nur wenn sinnvoll ausgegliedert). Diese dann über sinnvolle Indexdateien verknüpft.

Den Quellcode trenne ich nach allgemeinen Funktionen / Prozeduren, speziellen Klassen, Fenstern mit eigenen Klassen und spezial funktionen und einem für MAIN bzw. dem Programmfluss falls das überhaupt noch möglich ist (eventdriven ...).
Gruß
Hubert
Daniel

Beitrag von Daniel »

Hallo Robert

wir machen im Prinzip für jeden Dialog eine Klasse = 1 PRG,
dann noch ein MAIN, eines für allg. FUNCTIONs, das AppSys (kann leer sein), und das DBESys.
Speziell ist noch ein DialogManager, der sorgt für an- und abmelden, schliessen der Child-Dialoge und DBFs.

Natürlich gibt es noch Klassen, die etwas verarbeiten ohne Dialog, da ist auch jede Klasse ein PRG. Grundsätzlich ist bei uns jede Verarbeitungseinheit, also was sich inhaltlich zusammenfassen lässt, eine Klasse. Und das PRG kann einfach gleich heissen wie die Klasse.

Eine Trennung nach User-Interface, Daten und Logik kommt mir zu theoretisch vor, kann ich mir schwer vorstellen.

Gruss Daniel
Gerd König
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 193
Registriert: Fr, 09. Jun 2006 7:52
Wohnort: Nähe Sömmerda

Beitrag von Gerd König »

Hallo Robert,

wir handhaben diese Problematik prinzipiell genauso wie Daniel beschrieben hat.

Im Wesentlichen besteht jede Applikation aus einem Dialogfenster (eine Klasse) mit je nach Aufgabe (Menüpunkt) auf einem Static in der Drawingarea angeordneten Elementen. Damit gibt es für jede Aktivitätät eine Klasse, die die gesamte Funktionalität enthält. Damit wird die Grafik im wesentlichen über :init() und :create() festgelegt. Logische Verknüpfungen und Tabellenzugriffe erfolgen über entsprechende Methoden.

Abgeleitet sind alle Klassen von einer Klasse mit immer wieder benötigten Methoden (meistens CLASS METHOD) und allgemein gültigen Variablen (meistens CLASS VAR, in der Regel SHARED). Dabei handelt es sich u.a. um Methoden zum Tabellenhandling (Öffnen, Lesen, Schreiben, Append, Suchen, Schließen).

Auch bei uns ist jede Klasse in einer eigenen prg-Datei hinterlegt. Dabei gibt es Quellcode-Dateien mit nur wenigen Zeilen (unter 100), andere haben wieder mehr als 10000 Zeilen. Trotzdem bleibt auch bei letzterem auf Grund der Klassen mit "sprechenden" Methodennamen und Variablen das Ganze relativ übersichtlich. Auf eine Kommentierung innerhalb einer Methode kann man dann weitgehend verzichten. Parameter, Rückgabewerte und Funktionalität sind vor (fast) jeder Methode kommentiert.

Viele Grüße
Gerd
Benutzeravatar
Rolf Ramacher
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1930
Registriert: Do, 09. Nov 2006 10:33
Wohnort: Bergheim
Danksagung erhalten: 3 Mal
Kontaktdaten:

Beitrag von Rolf Ramacher »

Hallo Robert,

ich mache das auch ähnlich wie die anderen. Jede Function und Proc-Main
haben eine eigene PRG-Datei. Die PRG-Datei heißt genauso wie die Function. Das jeweilige Fenster hat eine einzelne PRG.

Das macht die Suche bei Fehlern oder ähnliches wesentlich einfacher als alles in eine PRG zu schreiben. Aus dem Hauptfenster starte ich die entsprechenden Untermenüs mit runshell. d.h. für die Untermenüs mache ich eigene exe-Files.

Außerdem verwende ich für den Aufbau von den Fenstern (GUI) sei es SLE
Felder oder Static-Felder Arrays, in der ich die entsprechenden Infos wie
Datenbank, DB-Feld, Recno. usw eintrage.

Vorgehensweise hierbei: Das Array wird aus den Datenbanken gefüllt,
Datenbanken geschlossen, Maskenaufbau, beim Speichern wird das Array durchlaufen, Datenbanken öffen , schreiben , schliessen. beenden
Gruß Rolf

Mitglied der Gruppe XUG-Cologne
www.xug-cologne.de
Benutzeravatar
Wolfgang Ciriack
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 2936
Registriert: Sa, 24. Sep 2005 9:37
Wohnort: Berlin
Hat sich bedankt: 13 Mal
Danksagung erhalten: 34 Mal
Kontaktdaten:

Beitrag von Wolfgang Ciriack »

Hallo Rolf,
Vorgehensweise hierbei: Das Array wird aus den Datenbanken gefüllt,
Datenbanken geschlossen, Maskenaufbau, beim Speichern wird das Array durchlaufen, Datenbanken öffen , schreiben , schliessen. beenden
Ist das eine Netzwerkanwendung ?
Wenn ja wirds mit diesem Vorgehen aber problematisch.
Viele Grüße
Wolfgang
Benutzeravatar
Rolf Ramacher
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1930
Registriert: Do, 09. Nov 2006 10:33
Wohnort: Bergheim
Danksagung erhalten: 3 Mal
Kontaktdaten:

Beitrag von Rolf Ramacher »

Hallo Wolfgang,

es gibt ein paar Kunden von uns, die ein Netzwerk haben. Aber wenn die Maske in Benutzung ist, so wird diese Maske dann gesperrt. Dieses regel ich über eine seperate Datenbank
Gruß Rolf

Mitglied der Gruppe XUG-Cologne
www.xug-cologne.de
Benutzeravatar
Wolfgang Ciriack
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 2936
Registriert: Sa, 24. Sep 2005 9:37
Wohnort: Berlin
Hat sich bedankt: 13 Mal
Danksagung erhalten: 34 Mal
Kontaktdaten:

Beitrag von Wolfgang Ciriack »

OK
Viele Grüße
Wolfgang
Sören
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 205
Registriert: Mo, 07. Aug 2006 10:18
Wohnort: Leipzig
Danksagung erhalten: 11 Mal

Beitrag von Sören »

Hallo,

Huberts Beschreibung kommt dem Programmaufbau, so wie ich ihn mache, sehr nahe.
Wobei ich zuerst die DB's, dann die Optik erstelle (jeder hat so seinen eigenen Ablauf).

Um auf PRIVATE- und PUBLIC-Variablen vollständig verzichten zu können, gibt es eine Main-Class mit allgemein (programmweit) verwendeten Variablen, deren Objekt als Parameter an Funktionen oder an andere Klassen übergeben wird.

Bei der Trennung von Programm-Steuerung und -Optik gehe ich einen sehr strikten Weg: Meine Dialoge sind komplett losgelöst vom Programmcode (und somit auch von der EXE).

Die Dialoge, Views und Xbp's werden aus Maskendateien (in banalem INI-Stil) generiert.

Beispiel einer Maskendatei:

Code: Alles auswählen

[Template]
Type=Dialog
Width=447
Height=321
FontName=Arial
FontSize=10
Title=Nutzereinstellungen
Modal=1

[Frame:Frame_Anmeldevorgaben]
Row=10
Col=14
Width=420
Height=131
Caption=Vorgaben Nutzeranmeldung
CaptionFontColor=DarkBlue

[Label:Label_Nutzername]
Row=42
Col=29
Width=149
Height=22
Caption=Vorgabe Nutzername:

[TextBox:TextBox_Nutzername]
Row=42
Col=183
Width=162
Height=22

[Label:Label_Kennwort]
Row=70
Col=28
Width=152
Height=22
Caption=Vorgabe Kennwort:

[TextBox:TextBox_Kennwort]
Row=70
Col=183
Width=162
Height=22
Unreadable=1

[CheckBox:CheckBox_AutoAnmeldung]
Row=105
Col=27
Width=156
Height=22
Tooltip=aus Sicherheitsgründen;nicht empfohlen
Caption=automatisch anmelden

[Frame:Frame_Ansicht]
Row=155
Col=14
Width=420
Height=97
Caption=Ansicht
CaptionFontColor=DarkBlue

[CheckBox:CheckBox_MinimiertStarten]
Row=187
Col=25
Width=122
Height=22
Caption=minimiert starten

[CheckBox:CheckBox_MinimierenBeiAufruf]
Row=215
Col=25
Width=221
Height=22
Caption=bei Programmaufruf minimieren

[Button:Button_Speichern]
Row=284
Col=219
Width=100
Height=22
ReposH=1
ReposV=1
Shortcut=Alt-S
Caption=&Speichern

[Button:Button_Abbrechen]
Row=284
Col=334
Width=100
Height=22
ReposH=1
ReposV=1
Shortcut=Alt-A
Caption=&Abbrechen
Um diese Masken nicht von Hand tippen zu müssen, habe ich mir eine Art Maskendesigner
programmiert, ähnlich dem Xbase-Formdesigner, nur umfangreicher, dennoch kompakter, einfacher
zu bedienen und mit weniger Macken behaftet. Das oben gezeigte Maskenscript wurde
damit generiert.


Im PRG wird ein Maskenscript z.B. wie folgt geladen und eine Anzeige erzeugt:

Code: Alles auswählen

// cMaskFile kann der Dateiname oder der Inhalt (String) des Maskenscripts sein
oMask := xMask():New( cMaskFile, , .T. )
oMask:create()
oMask:CheckBox_AutoAnmeldung:disable()
Die Vorteile liegen auf der Hand:

- Trennung von Programmsteuerung und -Optik (wie diskutiert)
- sehr einfache Erstellung von Dialogen und visuellen Anzeigen (Views)
- Änderung der Optik ohne Neukompilierung (außerhalb der EXE) (auch v. versierten Usern)

Nachteile sind:

- bei großen Dialogen mit vielen Elementen: der langsamere Aufbau (bisher bei mir irrelevant)
- ein Maskenscript kann keine Xbp's in Z-Richtung definieren

Zu Nachteil Nr. 2:
Man muss doch Xbp's auf anderen Xbp's positionieren können!? Kann man auch, aber man muss eine
weitere Maskendatei erstellen!
Zur inhaltlichen Gruppierungen von Xbp's (Groupbox) verwende ich einen transparenten Xbp (xFrame),
der lediglich die Darstellg. verschiedener Rahmentypen zulässt; dieser wird schlicht über die
inhaltlich zusammengehörigen Elemente positioniert.

Die Maskenscripte können verschlüsselt oder unverschlüsselt auf Festplatte gespeichert, sie können in Text-Feldern von DB's abgelegt sein; sie können auch im Programmcode zw. TEXT ... ENDTEXT stehen oder als Resource zur EXE gelinkt werden. In den letzten beiden Fällen ist das Maskenscript dann allerdings Teil der EXE.

Tschüs,
Sören
hschmidt
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 164
Registriert: Mo, 09. Jan 2006 17:06
Wohnort: Paderborn
Hat sich bedankt: 2 Mal
Kontaktdaten:

Beitrag von hschmidt »

Hallo Sören,

Dein Konzept gefällt mir sehr gut.
Kannst Du damit alle GUI-Komponenten (auch Browses) abbilden.
Wie löst Du das technisch in der Parserklasse xMask, mit Makros oder Codeblocks?

Schöne Grüße

Hans
Sören
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 205
Registriert: Mo, 07. Aug 2006 10:18
Wohnort: Leipzig
Danksagung erhalten: 11 Mal

Beitrag von Sören »

Hallo Hans,

wie Du schon richtig bemerkt hast, hat die xMask-Klasse u.a. eine Art Parser-Funktion, die das Maskenscript durchläuft und jedem Element-Typ (=Sektion) den entsprechenden Xbp zuordnet (z.B. Label = xStatic), diesen Xbp dann erzeugt und die Eigenschaften zuweist.

Die Zuordnung sieht so aus (mit x... beginnende Klassen sind eigene, von bestehenden Xbp- abgeleitete Klassen):

Code: Alles auswählen

// Zuordnung der Xbp's zu den Partnamen (.F./.T. gibt an, ob fuer den Part ein Placeholder
// angezeigt werden soll oder nicht - nur wenn ::EditMode == .T.)
#define XMASK_PARTTYPES  { { "Label", "xStatic", .F. }, ;
                           { "Line", "xLine", .F. }, ;
                           { "Frame", "xFrame", .F. }, ;
                           { "TextBox", "xSLE", .F. }, ;
                           { "NumericBox", "xNumericSLE", .F. }, ;
                           { "ActionBox", "xActionSLE", .F. }, ;
                           { "Text", "xMLE", .F. }, ;
                           { "Button", "xPushButton", .F. }, ;
                           { "ToolButton", "xToolButton", .F. }, ;
                           { "CheckBox", "xCheckbox", .F. }, ;
                           { "ThreeStateBox", "x3State", .F. }, ;
                           { "RadioButton", "xRadioButton", .F. }, ;
                           { "ListBox", "xListBox", .F. }, ;
                           { "ComboBox", "xComboBox", .F. }, ;
                           { "SpinButton", "xSpinButton", .F. }, ;
                           { "Browse", "xBrowse", .T. }, ;
                           { "QuickBrowse", "xQuickBrowse", .T. }, ;
                           { "Tree", "xTreeView", .F. }, ;
                           { "ScrollBar", "xScrollBar", .T. }, ;
                           { "TabControl", "xTabControl", .T. }, ;
                           { "PageControl", "xPageControl", .F. }, ;
                           { "Calendar", "xPopUpCalendar", .T. }, ;
                           { "Image", "xImageView", .T. }, ;
                           { "PickList", "xPickList", .T. }, ;
                           { "DirSelect", "xDirSelect", .T. }, ;
                           { "FileSelect", "xFileSelect", .T. }, ;
                           { "QueryView", "xQueryView", .T. }, ;
                           { "PlaceHolder", "xStatic", .T. } }
irgendwann wird dann, wie oben beschrieben, der Xbp erzeugt:

Code: Alles auswählen

cXbp := "xQuickBrowse"
cXbp := cXbpType + "()"
oXbp := &(cXbp)
oXbp := oXbp:New( ::Parent, , aPos, aSize, aPresParam )
und die Eigenschaften werden zugewiesen:

- allgemeine Eigenschaften, die jeder Xbp hat:

Code: Alles auswählen

// Tabstop zuweisen, wenn angegeben und kein EditMode
if ! Empty(  cValue := ::Ini:GetValue( cSectionName, "TabStop" )  ) .and. ! ::EditMode
  oXbp:tabStop := StrToValue( cValue, "L" )
elseif ::EditMode  // im EditMode kein TabStop zulassen
  oXbp:tabStop := .F.
endif

...
- und spezielle Xbp-typische Eigenschaften:

Code: Alles auswählen

if cPartType == "QUICKBROWSE"  // QuickBrowse (xQuickBrowse)

  // Style zuweisen
  if ! Empty(  cValue := Upper( ::Ini:GetValue( cSectionName, "Style" ) )  )
    oXbp:style := iiif( cValue == "FANCY", XBP_STYLE_FANCY,  cValue == "3D", XBP_STYLE_3D,  cValue == "Plain", XBP_STYLE_PLAIN,  XBP_STYLE_SYSTEMDEFAULT )
  endif

  ...

endif
Die Methode NEW() ist eine CLASS METHOD, die dynamisch eine neue, von xMask abgeleitete Klasse erzeugt.

Damit wird z.B.:

oMask:CheckBox_AutoAnmeldung --> oCheckbox

übersetzt in die Access Method:

oMask:GetPart( "CheckBox_AutoAnmeldung" ) --> oCheckbox

Das gewährleistet den direkten Zugriff auf die Xbp-Objekte, ohne Umwege über Methoden.

Eine gute Erklärung, wie man dynamische Klassen erzeugt, ist übrigens in der Alaska-Hilfe bei der Funktion ClassCreate() zu finden.


Tschüs,
Sören
hschmidt
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 164
Registriert: Mo, 09. Jan 2006 17:06
Wohnort: Paderborn
Hat sich bedankt: 2 Mal
Kontaktdaten:

Beitrag von hschmidt »

Hallo Sören,
Eine gute Erklärung, wie man dynamische Klassen erzeugt, ist übrigens in der Alaska-Hilfe bei der Funktion ClassCreate() zu finden.
die habe ich gelesen, kurz nachdem ich angefangen habe, mich mit Xbase++ zu beschäftigen. Da war mir das bedeutend zu hoch.
Vielleicht sollte ich es noch mal versuchen.

Schöne Grüße

Hans
Sören
Rekursionen-Architekt
Rekursionen-Architekt
Beiträge: 205
Registriert: Mo, 07. Aug 2006 10:18
Wohnort: Leipzig
Danksagung erhalten: 11 Mal

Beitrag von Sören »

Hallo Hans,
die habe ich gelesen, kurz nachdem ich angefangen habe...
Wenn ich das als Xbase-Anfänger gelesen hätte (was ich zum Glück nicht habe) hätte ich gleich das Handtuch geschmissen! :oops:

Sören
Antworten