OOP:Einsteiger OOP in AHK

Hilfreiche Erklärungen und Tipps zum Lernen von Autohotkey

Moderator: jNizM

User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

OOP:Einsteiger OOP in AHK

28 Jan 2018, 09:48

Übersetzt mit dem deepL.com Translator
Hallo,
in diesem Tutorial möchte ich klarstellen, was genau OOP ist, seine Vor- und Nachteile und einige einfache Tricks, wie man einfache OOP in AHK erstellt.
Generelles:
OOP ist ein Weg, um an die Programmierung heranzugehen. Viele Leute denken, dass es "nur die Verwendung der Klassensyntax ist, um etwas zu programmieren", aber das stimmt nicht im Geringsten.
In AHK ist man sogar in der Lage, gültigen OOP code zu erzeugen, ohne die Klassensyntax überhaupt zu verwenden. Allerdings ist das eher ungewöhnlich und ich werde nur die einfachste Methode erklären, die die besten Ergebnisse liefert.
Der Begriff OOP ist die Kurzform für objektorientierte Programmierung. Der Programmierbegriff des Objekts wurde Mitte der 60er Jahre erfunden.
Die objektorientierte Programmierung dreht sich um diese Objekte. Es hat sich Mitte der 90er Jahre zur Hauptprogrammiermethode entwickelt, und heutzutage gibt es nur noch wenige Sprachen ohne OOP.
OOP scheint zwar ein sehr definitiver Begriff zu sein, aber es gibt tatsächlich mehrere Möglichkeiten, wie man sich an OOP angehen kann, z.B. in AutoHotkey haben wir eine Prototypen-basierte Programmierung, aber ihr muss nicht genau wissen, was das bedeutet.
-> Wikipedia Prototypen Basiertes Programmieren ( im Englischen Artikel werden wir sogar erwähnt :dance: )

Warum sollte ich OOP verwenden?:
In der objektorientierten Programmierung und in der Prozeduralen Programmierung (wo man nur mit Funktionen arbeitet) teilt man Code in kleinere Teile, um ihn wiederzuverwenden. Der Unterschied, zwischen beiden, besteht darin, dass man in OOP auf mehrere Ebenen aufteilt.
Man hat mehrere erweiterte Möglichkeiten, den Code auf verschiedene Arten zu teilen, von einer sehr, sehr groben Ebene bis zur gleichen Detaillierungsstufe, die die Prozedurale Programmierung bietet.
Dies bietet viel mehr Möglichkeiten bei der Gestaltung des Projekts, das man erstellen möchte. Im Gegenzug braucht man diese Planungsphase wirklich oder man verliert sich einfach in den Möglichkeiten.
OOP bietet eine Vielzahl von Standard-Aktionen und Konventionen, die die Kommunikation von Code erheblich vereinfachen. Da es eng von der Logik der realen Welt abgeleitet ist, wird es wirklich intuitiv, sobald man sich daran gewöhnt hat.
Es ist auch einfacher, komplexe Vorgänge hinter einer leicht zugänglichen, intuitiven Oberfläche zu verbergen - was den Austausch von Code mit anderen wesentlich erleichtert. Man kann auch Kollisionsprobleme vermeiden.
Es ist auch einfacher, modulare Programme zu erstellen, bei denen große Teile des Codes problemlos ersetzt werden können - eine Notwendigkeit, wenn man den Code nach der Veröffentlichung warten möchte.
-> Diese Vor- und Nachteile sind sehr abstrakt, ich fasse zusammen, wozu sie führen:
OOP is besser:
  • für größere Projekte
  • wenn man mit mehreren Leuten arbeitet
  • für Code den man wiederverwenden möchte
  • für Code den man teilen möchte
OOP ist gut für dich wenn:

Code: Select all

[*]du in der Zukunft Entwickler werden willst
[*]du gerne code mit anderen teilst
[*]du ein Entwickler werden willst - da es der verbreitetste Coding Standard in der Industrie ist
Allgemeine Objekte vs. AutoHotkeys Objekte:
Objekte als Konzept innerhalb der Programmierung müssen ein paar Regeln erfüllen, um Objekte genannt zu werden:
  1. Ein Objekt ist mehr als einfache Daten und mehr als Funktionalität - es ist immer beides. Ein Objekt sind die Daten und die Modifikationen, die Sie an diesen Daten vornehmen können.
  2. Ein Objekt ist immer die Instanz einer Klasse. Eine Klasse definiert die Datenstruktur und die Modifikationen, die Sie an dieser Datenstruktur vornehmen können.
  3. Ein Objekt schützt seine internen Abläufe vor dem Zugriff von außen und erlaubt nur bestimmte Aktionen und Modifikationen von außen.
AutoHotkeys-Objekte erfüllen die dritte Regel überhaupt nicht - aber wenn wir jemals nur so programmieren, als ob die Regel aktiv wäre, dann ist das eigentlich egal.
Nicht alles, was im Kontext von AutoHotkey als Objekt bezeichnet wird, erfüllt die 1. oder 2. In diesem Tutorial werde ich nur über Objekte sprechen, die diese Regeln erfüllen, z. B:

Code: Select all

class ObjectClass {
}
object := new ObjectClass() ;wobei object ein Objekt ist, das die Regeln 1 und 2 erfüllt und Regel 3 niemals gebrochen wird
;1 es hat leere Daten und die Möglichkeiten, diese Daten zu ändern, sind nicht vorhanden.
;2 ist es die Instanz der Klasse ObjectClass, die definiert, dass dieses Objekt weder über eine Datenstruktur noch über Modifikationsmöglichkeiten verfügt.
;3, da wir nie auf das Objekt in irgendeiner Weise zugreifen, die es nicht beabsichtigt war - daher ist Regel 3 nie gebrochen und somit erfüllt.
Praktische Arbeit:
Nach vielen theoretischen Fakten über Objekte möchte ich diese Fakten in die Tat umsetzen und anhand eines praktischen Beispiels erläutern.
Ich denke, dass auf diese Weise mehr Leute dieses Tutorial verstehen könnten und es interessanter wäre.
Also werden wir einfach ein Beispielobjekt oder besser gesagt eine Beispiel-Klasse in AutoHotkey erstellen, die im OOP-Stil gehalten ist.
Für dieses Beispiel habe ich an das Dateisystem gedacht - also an Dateien, Verzeichnisse und Laufwerke.

Planungsphase:
Bevor wir mit der Erstellung eines Objektes beginnen, benötigen wir zunächst eine Planungsphase. Für diese Planungsphase nehmen Sie Stift und Papier - es ist nicht notwendig, aber es hilft enorm. ( Es hilft mir und meinem schrecklichen Gedächtnis zumindest. )
In OOP orientiert sich der Programmierer an Objekten, um zu programmieren. Da Sie jetzt Programmierer sind, müssen Sie genau das tun.
Der erste Schritt besteht also darin, die verschiedenen Objekttypen zu definieren, die Sie programmieren möchten. Das Thema, das wir gewählt haben, ist das Dateisystem und die Objekttypen hier sind Dateien, Verzeichnisse und Laufwerke.
Dann nehmen Sie ein Objekt und legen fest, welche Art von Informationen dieser Objekttyp enthält:
files - haben einen Namen
...
was ich wie folgt aufschreiben werde:
files:
-path (... und die ganzen anderen Pfad arten wie z.B. Extension)
-content ( Inhalt der Datei )
--size ( Es ist zwar die size des contents aber man kann es trotzdem direkt ausgeben )
-attributes ( z. B. ReadOnly etc. )
-time ( letzte Zugriffszeit etc. )
Für den Einsteiger Kurs werden wir direkt zum Programmieren gehen.

Initialisierungsstufe:
Wenn Sie eine neue Klasse für einen bestimmten Objekttyp erstellen, sollte der erste Schritt die Erstellung einer neuen AHK-Datei sein, die so benannt ist, wie die Klasse, die Sie erstellen möchten:
Also erstellen wir eine File.ahk. Danach wird einfach der Code für eine neue Klasse in die Klasse eingefügt:

Code: Select all

class File {
}
Standardbegriffe und Aktionen von Objekten:
Unterbrechen wir das Programmieren kurz und kommen zurück zur Theorie. Es gibt mehrere Aktionen, die Sie auf jedes Objekt anwenden können.
Wenn diese Aktionen ausgeführt werden, wird eine bestimmte Methode innerhalb des Objekts aufgerufen.
Eine Methode ist im Grunde wie eine Funktion in einem Objekt, das aufgerufen wird.
Hier ist eine Liste von Dingen, die mit einem Objekt passieren können und welche Art von Methode sie aufrufen.
Es ist nicht wichtig zu verstehen, was sie alle im Moment tun - ich denke, dass man kaum in der Lage sein wird, aus dieser kurzen Liste heraus zu verstehen, was diese Methoden überhaupt tun.
  1. Erstellen eines Objekts __New( 0-x creation parameters )
  2. Löschen eines Objekts __Delete()
  3. Zugriff auf inexistenten Schlüssel __Get( 1-x keynames )
  4. Setzen eines inexistenten Schlüssels __Set( 1-x keynames, setValue )
  5. Aufruf einer Methode __Call( methodName, parameters )
  6. Benutzen des For-Loops _NewEnum()
  7. Vor __New gibt es __Init()
Abhängig von der Art der Methode, die Sie Ihrer Klasse hinzufügen, wird Ihr Objekt unterschiedlich auf diese Ereignisse reagieren. Einige dieser Methoden werden oft angewendet, andere fast nie. ( z. B. __init() sollte laut Dokumentation nicht verwendet werden. ).
Ich habe diese so bestellt, wie oft ich sie modifiziere. __New sollte ein Teil von fast jeder Klasse sein. __Delete ist nicht überall, aber es ist sehr verbreitet. __Get, __Set und __Call sind sehr mächtig, aber auch extrem anstrengend.
Anstatt ein neues _NewEnum() zu verwenden, werde ich oft eine Methode hinzufügen, um ein Array zu erhalten, das for-loopt so wie ich es will, da das einfacher ist.
Ich habe eigentlich noch nie zuvor __Init() benutzt, da ich stattdessen einfach __New verwenden kann und es nicht verwendet werden sollte.

Wenn Sie ein Objekt erstellen, sollten Sie sicherstellen, dass das Objekt, das Sie erstellt haben, über genügend Informationen verfügt, um gültig zu sein.
Das bedeutet, dass Sie, nachdem Sie ein neues File-Objekt aus unserer File-Klasse erstellt haben, in der Lage sein sollten, dessen Größe, Pfad... zu bestimmen.
Das bedeutet, dass wir für unser Beispiel die Informationen benötigen, die den Unterschied zwischen einer beliebigen Datei und einer bestimmten Datei ausmachen.
Für Dateien ist das ganz einfach - Sie haben einen Dateipfad, den nur diese Datei hat.
Für andere Objekte ist die Suche nach diesen Informationen etwas komplexer - Sie werden feststellen, dass es mehrere dieser Werte gibt oder dass Sie für einige dieser Werte Standardwerte verwenden können etc.
Für unser einfaches Beispiel sollte jedoch der Dateipfad ausreichen. Das Objekt benötigt diese Information beim Erstellen, daher benötigt es diese Information beim Aufruf von __New.
Um diesen Effekt zu erreichen, müssen wir der neuen Methode unseres Objekts und beim Erstellen mit dem new Operator einen Parameter hinzufügen:

Code: Select all

class Beispiel {
	__New( beispielInfo ) {
		Msgbox % beispielInfo
	}
}
test := new Besipiel( "Test" )
Bug Warnung
Zurück in unsere Klasse:
Zurzeit haben wir diesen Code:

Code: Select all

class File {
}
und wenn wir hinzufügen, was wir über unsere __New Methode wissen:

Code: Select all

class File {
	__New( fileName ) {
	}
}
Um eine gültiges File Objekt zu erstellen, müssen wir auch den fileName in dem Objekt speichern, das wir erstellen.
Dafür haben Methoden ein einfaches Feature. Das Objekt, mit dem sie aufgerufen werden, ist in ihnen vorhanden - es wird in der Variable this angeboten.

Code: Select all

class File {
	__New( fileName ) {
		this.name := fileName
	}
}
abc := new File( "D:\Test.ahk" ) ;zum Testen
Msgbox % abc.name ;auch zum Testen
Zu Testzwecken habe ich einige Zeilen hinzugefügt, die ein Test File-Objekt erzeugen. Und Sie sollten das Problem wahrscheinlich schon hier sehen.
Die Datei existiert wahrscheinlich nicht auf Ihrem PC, aber wir haben ein Objekt erstellt, das gültig sein sollte - aber das ist es nicht, da die Datei nicht existiert.
Wir sollten wahrscheinlich prüfen, ob die Datei existiert, innerhalb der __New-Methode, und wir sollten auch prüfen, ob es sich um eine Datei handelt.

Code: Select all

class File {
	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Msgbox Datei existiert nicht : "%fileName%"
		if ( inStr( fileExist( fileName ), "D" ) ) ;Wenn fileName auf einen Ordner oder ein Laufwerk verweist
			Msgbox Datei ist keine valide Datei
		this.name := fileName
	}
}
Dies sollte verhindern, dass ein ungültiges Objekt erzeugt wird.
Was tun, wenn die Objekterstellung fehlschlägt?
Der nächste Schritt ist das Hinzufügen von Funktionalität, die es erlaubt alle Informationen, die wir aus dieser Datei erhalten können, abrufen kann.
Es gibt 2 Möglichkeiten, dies zu tun. Eine davon ist die Verwendung von Properties. Die andere ist die Verwendung von Gettern.
Properties are special to AutoHotkey Objects where a piece of code is triggered when you try to set or get a specific key of an object.
Getters sind nur Methoden, bei denen der Name des Attributs, das Sie erhalten möchten, mit get vorangestellt wird, z. B. file.getFullPath().
Sie können beides benutzen, so oder so ist es gut. Allerdings bevorzuge ich Getter, da sie in vielen Sprachen gebräuchlich sind. Ich werde getters für dieses Tutorial verwenden.

Code: Select all

class File {
	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Msgbox Datei existiert nicht : "%fileName%"
		if ( inStr( fileExist( fileName ), "D" ) ) ;Wenn fileName auf einen Ordner oder ein Laufwerk verweist
			Msgbox Datei ist keine valide Datei
		this.name := fileName
	}
	getPath() {
		return this.name
	}
	getPathName() {
		SplitPath, % this.name, fileName
		return fileName
	}
	getPathDir() { ;das selbe wie getDirectory
		return This.getPathDirectory()
	}
	getPathDirectory() {
		SplitPath, % this.name, , fileDirectory
		return fileDirectory
	}
	getPathExtension() {
		SplitPath, % this.name , , , fileExtension
		return fileExtension
	}
	getPathNameNoExtension() {
		SplitPath, % this.name, , , , fileNameNoExtension
		return fileNameNoExtension
	}
	getPathDrive() {
		SplitPath, % this.name, , , , , fileDrive
		return fileDrive
	}
	getSize( unit := "" ) {
		FileGetSize, fileSize, % this.name, % unit
		return fileSize
	}
	getAttributes() { ;ein flag string siehe AutoHotkey Hilfe
		return FileExist( this.name )
	}
	getTimeAccessed() { ;in YYYYMMDDHH24MISS siehe AutoHotkey Hilfe für mehr Details
		FileGetTime, timeCreated, % this.name, A
		return timeCreated
	}
	getTimeModified() {
		FileGetTime, timeCreated, % this.name, M
		return timeCreated
	}
	getTimeCreated() {
		FileGetTime, timeCreated, % this.name, C
		return timeCreated
	}
}
Testen wir es noch einmal. Wir werden eine test.ahk im selben Ordner wie unsere File.ahk ablegen:

Code: Select all

#Include File.ahk
file1 := new File( A_ScriptFullPath ) ;ein File Objekt unserer test.ahk
file2 := new File( "File.ahk" ) ;ein File Objekt unserer File.ahk
Msgbox % file1.getPathDir() ;Den enthaltenden Ordner zurück geben
Msgbox % file2.getPathDir() ;Den enthaltenden Ordner zurück geben
Wenn Sie diesen Code ausführen, sollten Sie ein Problem sehen. Wenn wir versuchen, das enthaltende Verzeichnis unseres zweiten File-Objekts zu erhalten, bekommen wir kein Verzeichnis.
Dies ist auf eine Einschränkung innerhalb von SplitPath zurückzuführen. SplitPath gibt das Verzeichnis nur dann zurück, wenn es Teil des angegebenen Dateipfades war.
Der Dateipfad, den wir in unserem zweiten Objekt gespeichert haben, ist "File.ahk" - er enthält keinen Pfad zum Verzeichnis, was es SplitPath unmöglich macht, diesen zurückzugeben.
Die Lösung besteht darin, entweder relative Pfade zu verbieten oder jede Art von Pfad, der dem Objekt gegeben wird, in einen vollständigen Pfad aufzulösen.
Wenn Sie relative Pfade verbieten, werden die Anwendungsmöglichkeiten unserer Klasse reduziert, und Sie müssen herausfinden, ob der Pfad relativ ist oder nicht.
Das Auflösen des Pfades zu einem vollen Pfad ist ziemlich einfach und macht unsere Klasse mächtiger. Sie können die Datei-Schleife innerhalb des Konstruktors verwenden, um dies zu erreichen:

Code: Select all

...
	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Msgbox Datei existiert nicht : "%fileName%"
		if ( inStr( fileExist( fileName ), "D" ) ) ;Wenn fileName auf einen Ordner oder ein Laufwerk verweist
			Msgbox Datei ist keine valide Datei
		Loop, Files, % fileName, F ;Da sich der Dateiname auf eine einzelne Datei bezieht, wird diese Schleife nur einmal ausgeführt.
			this.name := A_LoopFileLongPath ;und hier wird er den Pfad auf den Wert setzen, den wir brauchen.
	}
...
Wenn wir unsere test.ahk ausführen, nachdem wir File.ahk geändert haben, sollten wir die richtigen Ergebnisse erhalten.

Getters, Setter und Delegation:
Nachdem wir Ihre erste Klasse erstellt und 2 Instanzen dieser Klasse erstellt haben, sollten wir einen Schritt zurücktreten und die Regeln überprüfen, die unser Objekt erfüllen muss, um OOP zu sein:
  1. Ein Objekt ist mehr als nur einfache Daten oder Funktionalität, denn es ist immer beides. Ein Objekt ist die Daten, die Zugriffsmöglichkeiten auf diese Daten und die Modifikationen, die Sie an diesen Daten vornehmen können.
    -> Die Daten sind der Dateipfad. Und bisher haben wir nur Möglichkeiten, auf die Daten der jeweiligen Datei zuzugreifen. OK
  2. Ein Objekt ist immer die Instanz einer Klasse. Eine Klasse definiert die Datenstruktur und die Modifikationen, die Sie an dieser Datenstruktur vornehmen können.
    -> Wir verwenden eine Klasse, um dieses Objekt zu definieren, seine Datenstruktur und die Zugriffsmöglichkeiten darauf. OK
  3. Ein Objekt schützt seine internen Abläufe vor dem Zugriff von außen und erlaubt nur bestimmte Aktionen und Modifikationen von außen.
    -> Wie wir bereits gesagt haben, ist diese Art von Regel in AutoHotkey schwer zu erfüllen. Solange Sie dieses Objekt so verwenden, wie es beabsichtigt war - also nur erstellen oder getters verwenden - ist es so, als ob es eine Grenze gäbe und wir erfüllen diese Regel OK.
Das bedeutet, dass unser erstes OOP File Object einsatzbereit ist - es ist jedoch unvollständig.
Der nächste Schritt, den wir machen müssen, ist, dem Benutzer dieser Klasse die Möglichkeit zu geben, die Attribute dieser Datei zu ändern.
Im einfachsten Fall fügen Sie einen Setter hinzu. Ein Setter ist wie ein Getter, nur dass er statt eines Attributs das Attribut eines Objekts setzt.
Setter können verwendet werden, solange es einen einzigen, definierten Weg gibt, um das Attribut zu setzen sowie das Setzen des Attributs auf seinen entsprechenden Get-Wert des selben Objektes keine Änderung im Objekt bewirkt.
Überprüfen wir die Attribute, die wir haben:
  • Path - der Pfad kann entweder durch Kopieren oder Verschieben der Datei geändert werden, da dies kein einziger Weg ist, den wir hier nicht benutzen können.
  • Attributes - wenn Sie versuchen, Attribute mit FileSetAttrib zu setzen, müssen Sie beschreiben, wie die Attribute geändert werden sollen, nicht wie sie am Ende aussehen sollen.
    Generell ist es möglich, hier einen Setter zu verwenden. Aber wir müssen die Befehle FileGetAttrib und FileSetAttrib so umschließen, dass der Getter-Wert in der Setter-Methode verwendet werden kann, ohne Änderungen am Objekt zu verursachen.
    Das wäre lästig. Oder besser gesagt, es ist Arbeit und generell wollen wir nicht viel unnötige Mehrarbeit.
  • Time - die Art und Weise, wie die Zeit eingestellt wird, ist einzigartig, definiert und die Eingabe des Get-Wertes in den Setter ändert die Datei nicht - wir können hier einen Setter verwenden.
  • Size - Die Größenänderung einer Datei ist unterdefiniert, die Größenänderung ändert den Inhalt und die Bearbeitung des Inhalts ist etwas, was wir noch nicht besprochen haben.
  • Content - Wir haben das alles aus einem bestimmten Grund nicht besprochen.
Für die Zeit werden wir Setter hinzufügen. Für Path werden wir die Methoden copy und move hinzufügen, die beide einen filePath akzeptieren, für Attribute denke ich an eine einfache Wrapper-Methode um SetAttrib namens changeAttributes.

Für unsere Inhalte setzen wir Delegation ein. Auf einer allgemeineren Ebene ist Delegation die Handlung, jemanden oder etwas anderes dazu zu bringen, Ihre Arbeit zu tun.
Wenn Sie ein Objekt programmieren und feststellen, dass es bereits ein Objekt gibt, das einen Teil Ihrer Arbeit erledigt, dann können Sie dieses Objekt verwenden und den Job an dieses Objekt zu delegieren.
Sie könnten auch Delegation verwenden, wenn Sie der Meinung sind, dass Sie viele Methoden haben, die spezifisch für einen einzelnen Attributtyp sind, der Ihr Objekt unübersichtlich macht. - z.B. unser Pfad mit allem getPath() getPathName()......
Wir werden uns jedoch darauf konzentrieren, die Aufgabe der Verwaltung des Dateiinhalts an AutoHotkeys zu delegieren, die inm File-Objekt integriert sind. Ich meine, dafür ist es doch gut, oder?
Das bedeutet, dass wir eine Methode benötigen, die ein neues AHK File-Objekt in unserer File-Klasse zurückgibt. Nennen wir es open()

Wir fügen auch eine weitere Methode für die letzte Aktion hinzu, die wir auf unsere Datei anwenden können (Löschen der Datei) und nennen sie delete().
Damit müssen wir die folgenden Methoden hinzufügen:
setTimeModified( time ), setTimeAccessed( time ), setTimeCreated( time )
copy( filePath ), move( filePath )
changeAttributes( changeAttributesString )
open( mode, encoding := A_FileEncoding )
Jetzt holen wir wieder Stift und Papier und schreiben und alle Methoden unseres Objektes neben dem dazugehörigen Attribut auf:
files: __new( fileName )
path: get*(), move( filePath, overwrite ), copy( filePath )
content: open( mode, encoding )
size: get*( unit )
attributes: get*(), change*( changes )
time: get*() set*()
delete()
Der Punkt dabei ist, dass Sie nun eine vollständige Liste aller Methoden Ihres Objekts haben, mit denen Sie es von außen modifizieren können.
Für mich ist das genug, um das Objekt komplett auswendig zu lernen. Ich habe ständig eine Liste aller Objekte und deren Methoden vor mir.
Das hat mir durch viele Situationen geholfen und mein Leben als Entwickler so viel einfacher gemacht.

Die letzte praktische Arbeit an der File Class:
Sie sollten wahrscheinlich in der Lage sein, alle Änderungen, die an dem FileObject vorgenommen werden müssen, selber zu implementieren.
Aber hier ist das Endresultat:

Code: Select all

class File {
	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Msgbox Datei existiert nicht : "%fileName%"
		if ( inStr( fileExist( fileName ), "D" ) ) ;Wenn fileName auf einen Ordner oder ein Laufwerk verweist
			Msgbox Datei ist keine valide Datei
		Loop, Files, % fileName, F ;Da sich der Dateiname auf eine einzelne Datei bezieht, wird diese Schleife nur einmal ausgeführt.
			this.name := A_LoopFileLongPath ;und hier wird er den Pfad auf den Wert setzen, den wir brauchen.
	}
	getPath() {
		return this.name
	}
	getPathName() {
		SplitPath, % this.name, fileName
		return fileName
	}
	getPathDir() { ;same as getDirectory
		return This.getPathDirectory()
	}
	getPathDirectory() {
		SplitPath, % this.name, , fileDirectory
		return fileDirectory
	}
	getPathExtension() {
		SplitPath, % this.name , , , fileExtension
		return fileExtension
	}
	getPathNameNoExtension() {
		SplitPath, % this.name, , , , fileNameNoExtension
		return fileNameNoExtension
	}
	getPathDrive() {
		SplitPath, % this.name, , , , , fileDrive
		return fileDrive
	}
	move( newFilePath, overwrite := 1 ) {
		FileMove, % this.name, % newFilePath, % overwrite
	}
	copy( newFilePath, overwrite := 1 ) {
		FileCopy, % this.name, % newFilePath, % overwrite
	}
	open( p* ) {
		return FileOpen( this.name, p* )
	}
	getSize( unit := "" ) {
		FileGetSize, fileSize, % this.name, % unit
		return fileSize
	}
	getAttributes() { ;ein flag string siehe AutoHotkey Hilfe
		return FileExist( this.name )
	}
	changeAttributes( changeAttributeString ) { ;siehe FileSetAttrib in der AutoHotkey hilfe für mehr infos
		FileSetAttrib, % changeAttributeString, % this.name
	}
	getTimeAccessed() { ;in YYYYMMDDHH24MISS siehe AutoHotkey Hilfe für mehr Details
		FileGetTime, timeCreated, % this.name, A
		return timeCreated
	}
	setTimeAccessed( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, A
	}
	getTimeModified() {
		FileGetTime, timeCreated, % this.name, M
		return timeCreated
	}
	setTimeModified( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, M
	}
	getTimeCreated() {
		FileGetTime, timeCreated, % this.name, C
		return timeCreated
	}
	setTimeCreated( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, C
	}
	delete() {
		FileDelete, % this.name
	}
}
Zusammenfassung:
In diesem Teil des Tutorials haben wir gelernt:
  • Was Objekte im Sinne von OOP sind
  • Wofür Klassen in OOP verwendet werden
  • Was ein Konstrukteur ist und wofür er gut ist
  • Was Methoden und Attribute sind und wie man sie verwendet
  • Was Getter und Setter sind
  • Was Delegation ist und wofür sie gut ist
In diesem Teil haben wir es geschafft:
  • unsere erste Klasse zu planen
  • und sie zu codieren
Recommends AHK Studio
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: OOP:Einsteiger OOP in AHK

28 Jan 2018, 09:48

Grundlegende Techniken:
Nach diesem ersten Schritt - dem Schritt der Erstellung Ihrer ersten Klasse und Ihres ersten Objekts - werden wir in diesem zweiten Teil das Tempo erhöhen.
Wenn du dich nach einer Weile verloren fühlst, empfehle ich dir, das, was du im ersten Teil gelernt hast, ein zweites Mal anzuwenden - diesmal auf eigene Faust.
Ich empfehle hierfür ein GUI-Control (z. B. ein Edit Control) zu verwenden, da GUI und OOP die perfekte Kombination sind.
Andernfalls, wenn Sie wirklich das Gefühl haben, dass Sie es nicht schaffen können - oder wenn Sie Schwierigkeiten haben, den ersten Teil zu verstehen - hinterlassen Sie einen Kommentar.
Ich bin nicht perfekt und ich mag es, meine Tutorials zu verbessern. Daher ist jede Art von Feedback willkommen.

In diesem Teil des Tutorials werde ich erklären, was wohl die wichtigste Grundtechnik von OOP ist. Nachdem Sie es gelernt haben, können Sie grundlegende OOP erstellen.
Und je nach Qualität wäre es bereits auf einem Niveau, das man als professionell bezeichnen könnte - zumindest, wenn man damit Erfahrungen sammelt.

Extension und Inheritance:
Nach dem, was wir bisher gemacht haben, fragt man sich wahrscheinlich: "Warum sollte jemand mehrere, verschiedene Klassen so schreiben wollen? Man muss so viel schreiben und sich so viel merken - es ist nicht besser als Funktionen zu benutzen, es ist schlimmer".
Nun, wenn man sich nur anschaut, was wir im ersten Teil gemacht haben, dann hat man sicherlich Recht damit. Wir haben eine einzelne Klasse entworfen, die für sich selbst steht und nicht besonders hilfreich ist.


In der Regel planen Sie eine ganze Gruppe von Objekten auf einmal. In unserem Fall schauen wir uns alle Objekte an, die wir im letzten Teil zu erstellen versucht haben.:
File, Folder und Drive
Dieses Mal werden wir sie alle auf einmal planen, anstatt doppelte Arbeit für jede einzelne Klasse, die wir erschaffen, zu leisten.
Bislang haben wir die Dateiklasse:
-path (...and all the other path names like name, extension etc.)
-content
--size ( it's the content's size but the attribute might be important even without accessing the content )
-attributes ( like ReadOnly etc. )
-time ( time last edited )
Wieder einmal werden wir planen, welche Attribute unsere Objekte enthalten.
Da wir bereits eine solche Liste mit Attributen haben, die unsere Datei hat, können wir diese bereits vorhandenen Attribute verwenden und unser Verzeichnis und Laufwerk überprüfen, ob sie ähnliche Attribute enthalten.
file:
-path (...and all the other path names like name, extension etc.)
-content
--size ( it's the content's size but the attribute might be important even without accessing the content )
-attributes ( like ReadOnly etc. )
-time ( time last edited )

folder:
-path (...and all the other path names like ...) unlike a single file it doesn't have any extension
-attributes ( should be the same as file I guess )
-time ( should be the same as file )
-content ( however unlike file content it's the files and folders contained )
--size ( a folder also has a size , however it's the size of all it's contained files and folders )

drive:
-path ( it's basically just %driveLetter%:/ and can't really be edited )
-attributes ( should be the same as file - although I'm not sure if you can edit it )
-content ( should be the same as folder )
--size ( should b the same as folder )
Und jetzt, nachdem wir das getan haben, vergleichen wir die Klassen und schreiben die Attribute auf, die für alle gleich sind.:
file, folder, drive:
-path ( only getting the drive name and getting the full path remains the same )
-attributes ( getting the reamains the same )
Und jetzt machen wir das Gleiche für jede einzelne Kombination aus Datei, Ordner und Laufwerk.
Wir werden jedoch alle Gemeinsamkeiten, die wir bereits für alle gefunden haben, auslassen.
file, folder:
-path ( getting and setting remains the same for directory, drive, name and full path )
-attribute setting
-time ( getting and setting )

folder, drive:
-path ( getting remains the same for full path and drive )
-content
--size
Jetzt fragen Sie sich vielleicht, warum wir das tun - und es gibt einen guten Grund dafür.:

Erweiterung erklärt:
Um Code wiederzuverwenden, haben sich die ursprünglichen Erfinder von OOP eine Technik ausgedacht, die Erweiterung oder Vererbung genannt wird.
In AutoHotkey können Sie es sich leicht als die Klasse Ihrer Klasse vorstellen.
Was passiert, ist, dass eine untergeordnete Klasse eine übergeordnete Klasse erweitert und die Untergeordnete erhält alle Methoden und Attribute, die von der Übergeordneten definiert wurden, und fügt dann ihre eigenen hinzu.
Jetzt sehen Sie, warum die Definition dieser Liste von Attributen so wichtig ist - sie hilft uns zu definieren, was in die übergeordnete Klasse gehört und was nicht. Lassen Sie uns jedoch zunächst über die Grenzen und Möglichkeiten der Extension sprechen.

Die Syntax der Extension ist eine der einfachsten:

Code: Select all

test := new child()
Msgbox % test.parentMethod()	;it can
Msgbox % test.childMethod()	;do both

class parent {
	parentMethod() {
		return 42
	}
}

class child extends parent {
	childMethod() {
		return 420
	}
}
In diesem Beispiel erweitert die Klasse child die Klasse parent.
  • eine Child-Klasse erweitert eine Parent-Klasse: Es ist in der Tat, dass ein Kind nur eine Elternklasse mit dieser Syntax haben kann, aber mit fortgeschrittenen Techniken können Sie im Grunde genommen Klassen modifizieren und was sich dynamisch ausdehnt - die ganze Zeile wird verschwommen. Was Sie wissen müssen, ist, dass dieser Satz für die grundlegende OOP die Wahrheit enthält.
    Zusätzlich kann eine Elternklasse noch einmal auf eine andere Großelternklasse ausgedehnt werden, die einen Großelternteil erweitern kann und so weiter und so fort.
  • Das Kind erhält alle Methoden und Attribute, die vom Elternteil definiert wurden, und fügt dann seine eigenen hinzu.: Das ist nicht ganz richtig, sondern vereinfacht nur das Geschehen. Wenn Sie jedoch nicht zu tief nachsehen, werden Sie keinen Code finden, der gegen diese Regel verstößt.
    Es ist erwähnenswert, dass das Kind Elternattribute und -methoden überschreiben kann, wie z.B.:

    Code: Select all

    test := new child()
    Msgbox % test.parentMethod()	;both will return
    Msgbox % test.childMethod()	;420
    
    class parent {
    	parentMethod() {
    		return 42
    	}
    }
    
    class child extends parent {
    	childMethod() {
    		return 420
    	}
    	parentMethod() {
    		return 420
    	}
    }
Die Wiederverwendung von Code lohnt sich immer und als eine Technik zur Wiederverwendung von Code-Erweiterungen gewinnt man die gleichen Vorteile.
Es reduziert den Arbeitsaufwand für die Erstellung der Klassen - um zu verstehen, wie man sie benutzt - um sie zu dokumentieren - um ihren Sourcecode zu lesen - um ihren Sourcecode zu verstehen und später zu modifizieren.
Es gibt nur Vorteile bei der Extension. Der einzige wirkliche Grund, eine Klasse nicht zu erweitern, ist, dass es keine ähnlichen Klassen gibt, die die Extension wert sind - eine Extension mit 2 Klassen zu machen, die kaum korrelieren, schafft eher Verwirrung als Hilfe.

Zurück zum Zeichenbrett:
Wir befinden uns jetzt in der Phase, in der wir entscheiden müssen, welche Elternklassen erstellt werden sollen. In dieser Phase benutze ich oft Zeichnungen, um die Möglichkeiten und Alternativen zu visualisieren.
Viele Leute dachten, dass es eine gute Idee ist, Beziehungen wie diese zu zeichnen. Es gibt eine ganze visuelle Diagrammfamilie namens UML, die alle Arten von Ereignissen und Beziehungen innerhalb eines Programms beschreibt.
Dies wäre unser Beispiel mit Untergeordnete Klasse Extends Übergeordnete von oben, in einem UML Diagramm.:
Image
Ich werde UML in diesem Tutorial manchmal verwenden, nicht weil es das beste Werkzeug für die Planung ist, sondern weil ich kein besseres Werkzeug zur Hand habe.
Es ist ein Werkzeug, das viele Profis benutzen - wie es aussieht, hängt davon ab, mit welchem Programm Sie es gezeichnet haben, und es ist nicht gerade für AHK geeignet.
Das Einzige, was für dieses Tutorial weniger geeignet ist, sind die Kritzeleien, die ich in etwa 30 Sekunden auf meinen Mini-Block zeichne, bevor ich sie entsorge, sobald eine Entscheidung getroffen wurde, oder ich habe mir ausreichend vergegenwärtigt habe, was sie beschreiben.

Jedenfalls zurück zu unseren Files, Directories und Disks. Wir müssen nun die Elternklassen planen, die wir erstellen wollen und welche Kindklassen diese erweitern sollen.
Es braucht viel Erfahrung und es gibt eine Menge, was man darüber sagen kann, wie man erweitern sollte. Ich arbeite meist intuitiv und stelle mir vor, wie der Code im Endergebnis aussehen würde.
Jedoch durch logisches Denken können wir mit 2 Schemata aufwarten, die die meiste Code-Wiederverwendung für diesen Fall ermöglichen würden.:
Image
und
Image
Im ersten Bild erhalten Directory und Drive eine spezifische Elternklasse, die es ihnen erlaubt, Methoden gemeinsam zu nutzen. Ich habe diese übergeordnete Klasse FileContainer genannt, weil - im Gegensatz zu Files - sowohl Drives als auch Directories Files und Directories enthalten.
Im zweiten Diagramm erhalten File und Directory eine bestimmte übergeordnete Klasse, mit der sie Methoden gemeinsam nutzen können. Ich habe diese übergeordnete Klasse FileContained genannt, weil - im Gegensatz zu Drives - beide von Drives und Directories enthalten sein können.
Der Unterschied ist, dass wir im ersten Fall vermeiden können, die get und setContent Methoden von Drives and Directories neu einzugeben, und dass wir im zweiten Fall einen Vorteil haben, wenn es um die getPath Methoden geht.
Ich werde immer versuchen, Methoden 2 mal zu schreiben die: komplex sind, sich oft ändern oder noch nicht vollständig definiert sind.
Unsere Pfadmethoden sind nicht komplex, werden sich wahrscheinlich nicht ändern und sind nicht nur bereits definiert, sondern auch komplett in Code geschrieben.
Wir haben noch keine Ahnung, wie unsere FileContainer-Zugriffsmethoden aussehen werden, aber ich kann Ihnen sagen, dass sie komplexer sein werden als die Pfade.
Die erste Alternative ist daher die bessere Wahl nach meinen Maßstäben - umso mehr, als ich Ihnen einen kleinen Trick zeigen werde, wie Sie vermeiden können, dass Sie jede path Methode immer wieder neu eingeben müssen.

Das Skelettwerk:
Der nächste Schritt ist etwas, was ich Skelettarbeit nenne. In diesem Schritt erstellen wir zunächst alle Klassen und ihre Beziehungen zueinander, bevor wir sie mit irgendwelchem Code befüllen.
Danach werden wir den Code, den wir bereits haben, sofort in die entsprechenden Klassen kopieren. Ich genieße diese Etappe wirklich, weil wir einige gute Ergebnisse erzielen, ohne viel Arbeit leisten zu müssen, während der Plan, den wir uns ausgedacht haben, langsam Gestalt annimmt.
1. werden wir das Skelett nach unserem Plan erschaffen.:

Code: Select all

class FileSystemElement {
}
class File extends FileSystemElement {
}
class FileContainer extends FileSystemElement {
}
class Directory extends FileContainer {
}
class Drive extends FileContainer {
}
Dann nehmen wir jede Methode, die wir bereits für unsere allererste Klasse erstellt haben, und prüfen, wie sie in die Klasse eingefügt werden kann und ob sie gleich bleibt.
Da wir bereits im Voraus geplant haben, können wir die einfachsten zuerst aussuchen und dann mit:

Code: Select all

	open( p* ) {
		return FileOpen( this.name, p* )
	}
	getSize( unit := "" ) {
		FileGetSize, fileSize, % this.name, % unit
		return fileSize
	}
	move( newFilePath, overwrite := 0 ) {
		FileMove, % this.name, % newFilePath, % overwrite
	}
	copy( newFilePath, overwrite := 0 ) { ;I changed overwrite to 0 since thats the default of the AHK command
		FileCopy, % this.name, % newFilePath, % overwrite
	}
	delete() {
		FileDelete, % this.name
	}
Diese gehören rein zur Klasse File, da die darin verwendeten Befehle rein zur Klasse File gehören.
Putting them there results in

Code: Select all

	getAttributes() { ;flag string see AutoHotkey Help: FileExist for more infos
		return FileExist( this.name )
	}
	changeAttributes( changeAttributeString ) { ;see FileSetAttrib for more infos
		FileSetAttrib, % changeAttributeString, % this.name
	}
getAttributes arbeitet für Dateien, Laufwerke und Verzeichnisse. changeAttributes funktioniert nicht mit Laufwerken. Sie auseinander zu spalten ist keine Option und die gleiche Methode an mehreren Stellen mit dem gleichen Namen zu haben, ist auch schlecht.
Da wir getAttributes für alle Klassen benötigen, werden wir die Grand Parent-Klasse einfügen, die für alle verfügbar ist, und - da wir das Paar zusammenhalten müssen - auch changeAttributes.:
Putting them there results in

Code: Select all

	getPath() {
		return this.name
	}
	getPathName() {
		SplitPath, % this.name, fileName
		return fileName
	}
	getPathDir() { ;same as getDirectory
		return This.getPathDirectory()
	}
	getPathDirectory() {
		SplitPath, % this.name, , fileDirectory
		return fileDirectory
	}
	getPathExtension() {
		SplitPath, % this.name , , , fileExtension
		return fileExtension
	}
	getPathNameNoExtension() {
		SplitPath, % this.name, , , , fileNameNoExtension
		return fileNameNoExtension
	}
	getPathDrive() {
		SplitPath, % this.name, , , , , fileDrive
		return fileDrive
	}
Wir stehen hier eigentlich vor dem gleichen Dilemma. Files hat all diese Methoden, Directory verliert bereits alles, was Name betrifft - wie Extension und NameNoExtension. Laufwerk hat nur Pfad und Laufwerk.
Dennoch können wir sie nicht trennen, da sie zusammengehören. Also gehen sie in die Klasse der Großeltern.
The resulting code

Code: Select all

	getTimeAccessed() { ;in YYYYMMDDHH24MISS see AutoHotkey help for more infos
		FileGetTime, timeCreated, % this.name, A
		return timeCreated
	}
	setTimeAccessed( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, A
	}
	getTimeModified() {
		FileGetTime, timeCreated, % this.name, M
		return timeCreated
	}
	setTimeModified( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, M
	}
	getTimeCreated() {
		FileGetTime, timeCreated, % this.name, C
		return timeCreated
	}
	setTimeCreated( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, C
	}
Hier ist es wieder fast das Gleiche - FileGetTime und FileSetTime funktionieren für Files and Directories, aber nicht für Drives. Wieder einmal haben wir es in unsere Großelternklasse aufgenommen.
The resulting code
Die letzte Methode, die wir integrieren müssen, ist der Konstruktor.:

Code: Select all

	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Msgbox File doesn't exist
		if ( inStr( fileExist( fileName ), "D" ) ) ;if file is a folder or a drive
			Msgbox File is not a valid File
		Loop, Files, % fileName, F ;since the fileName refers to a single file this loop will only execute once
			this.name := A_LoopFileLongPath ;and there it will set the path to the value we need
	}
In AutoHotkey kann ein Konstruktor vererbt werden, deshalb müssen wir uns fragen, ob dieser Konstruktor für andere Klassen funktioniert.
Die einfache Antwort lautet nein. Dieser Code prüft spezifisch und funktioniert nur, wenn es sich bei der Eingabe um einen Dateipfad handelt.
The complex answer

Code: Select all

class FileSystemElement {
	getAttributes() { ;flag string see AutoHotkey Help: FileExist for more infos
		return FileExist( this.name )
	}
	changeAttributes( changeAttributeString ) { ;see FileSetAttrib for more infos
		FileSetAttrib, % changeAttributeString, % this.name
	}
	getPath() {
		return this.name
	}
	getPathName() {
		SplitPath, % this.name, fileName
		return fileName
	}
	getPathDir() { ;same as getDirectory
		return This.getPathDirectory()
	}
	getPathDirectory() {
		SplitPath, % this.name, , fileDirectory
		return fileDirectory
	}
	getPathExtension() {
		SplitPath, % this.name , , , fileExtension
		return fileExtension
	}
	getPathNameNoExtension() {
		SplitPath, % this.name, , , , fileNameNoExtension
		return fileNameNoExtension
	}
	getPathDrive() {
		SplitPath, % this.name, , , , , fileDrive
		return fileDrive
	}
	getTimeAccessed() { ;in YYYYMMDDHH24MISS see AutoHotkey help for more infos
		FileGetTime, timeCreated, % this.name, A
		return timeCreated
	}
	setTimeAccessed( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, A
	}
	getTimeModified() {
		FileGetTime, timeCreated, % this.name, M
		return timeCreated
	}
	setTimeModified( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, M
	}
	getTimeCreated() {
		FileGetTime, timeCreated, % this.name, C
		return timeCreated
	}
	setTimeCreated( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, C
	}
}
class File extends FileSystemElement {
	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Throw exception( "File """ . path . """ doesn't exist", "__New", "Exist test returned false" )
		if ( inStr( fileExist( fileName ), "D" ) ) ;if file is a folder or a drive
			Throw exception( "Error creating File", "__New", "Path points to Folder" )
		Loop, Files, % strReplace(fileName,"/","\"), F ;since the fileName refers to a single file this loop will only execute once
			this.name := A_LoopFileLongPath ;and there it will set the path to the value we need
	}
	open( p* ) {
		return FileOpen( this.name, p* )
	}
	getSize( unit := "" ) {
		FileGetSize, fileSize, % this.name, % unit
		return fileSize
	}
	move( newFilePath, overwrite := 0 ) {
		FileMove, % this.name, % newFilePath, % overwrite
	}
	copy( newFilePath, overwrite := 0 ) {
		FileCopy, % this.name, % newFilePath, % overwrite
	}
	delete() {
		FileDelete, % this.name
	}
}
class FileContainer extends FileSystemElement {
}
class Directory extends FileContainer {
}
class Drive extends FileContainer {
}
Damit haben wir tatsächlich unsere gesamte File-Klasse in das neue Skelett integriert.
Das bedeutet, dass unsere gesamte File-Klasse bereits nutzbar ist - Sie können sie sogar ausprobieren, wenn Sie wollen - sie hat sich nicht verändert. Drive und Directory benötigen jedoch noch etwas Arbeit.

Jetzt ist es Zeit für den Trick, den ich bereits erwähnt habe. Lassen Sie uns noch einmal wiederholen, worum es ging.:
Einige der Methoden, die wir der Klasse File entnommen haben, sind für alle Klassen verfügbar, obwohl sie nur für einige von ihnen funktionieren - und sie werden stillschweigend scheitern.
In der Programmierung sollte man immer vermeiden, Dinge stillschweigend scheitern zu lassen. Da die Methode dort vorhanden ist, zeigt AHK keine Art von "missing method error" an - ( obwohl das in v1 sowieso nie funktioniert ).
Also müssen wir selbst einen Fehler ausgeben.:

Code: Select all

	%methodName%(p*) { ;replace %methodName% and (p*) with the name of the function and the actual parameters thats you want to throw this kind of error
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
Wir können einfach Methoden, die in Elternklassen definiert wurden, in Kindklassen überschreiben und wir werden Methoden, die in dieser speziellen Kindklasse nicht verfügbar sind, mit dieser Methode überschreiben.
Dadurch machen wir den Benutzer unserer Klasse darauf aufmerksam, dass mit seinem Code etwas nicht in Ordnung ist. (... z. B. erwartete er ein File, bekam aber stattdessen ein Directory Objekt.)
Nach der Anwendung dieser Änderungen ist unsere Klasse bereit zum Testen.:

Code: Select all

class FileSystemElement {
	getAttributes() { ;flag string see AutoHotkey Help: FileExist for more infos
		return FileExist( this.name )
	}
	changeAttributes( changeAttributeString ) { ;see FileSetAttrib for more infos
		FileSetAttrib, % changeAttributeString, % this.name
	}
	getPath() {
		return this.name
	}
	getPathName() {
		SplitPath, % this.name, fileName
		return fileName
	}
	getPathDir() { ;same as getDirectory
		return This.getPathDirectory()
	}
	getPathDirectory() {
		SplitPath, % this.name, , fileDirectory
		return fileDirectory
	}
	getPathExtension() {
		SplitPath, % this.name , , , fileExtension
		return fileExtension
	}
	getPathNameNoExtension() {
		SplitPath, % this.name, , , , fileNameNoExtension
		return fileNameNoExtension
	}
	getPathDrive() {
		SplitPath, % this.name, , , , , fileDrive
		return fileDrive
	}
	getTimeAccessed() { ;in YYYYMMDDHH24MISS see AutoHotkey help for more infos
		FileGetTime, timeCreated, % this.name, A
		return timeCreated
	}
	setTimeAccessed( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, A
	}
	getTimeModified() {
		FileGetTime, timeCreated, % this.name, M
		return timeCreated
	}
	setTimeModified( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, M
	}
	getTimeCreated() {
		FileGetTime, timeCreated, % this.name, C
		return timeCreated
	}
	setTimeCreated( timeStamp ) {
		FileSetTime, % timeStamp, % this.name, C
	}
}
class File extends FileSystemElement {
	__New( fileName ) {
		if ( !fileExist( fileName ) )
			Throw exception( "File """ . path . """ doesn't exist", "__New", "Exist test returned false" )
		if ( inStr( fileExist( fileName ), "D" ) ) ;if file is a folder or a drive
			Throw exception( "Error creating File", "__New", "Path points to Folder" )
		Loop, Files, % strReplace(fileName,"/","\"), F ;since the fileName refers to a single file this loop will only execute once
			this.name := A_LoopFileLongPath ;and there it will set the path to the value we need
	}
	open( p* ) {
		return FileOpen( this.name, p* )
	}
	getSize( unit := "" ) {
		FileGetSize, fileSize, % this.name, % unit
		return fileSize
	}
	move( newFilePath, overwrite := 0 ) {
		FileMove, % this.name, % newFilePath, % overwrite
	}
	copy( newFilePath, overwrite := 0 ) {
		FileCopy, % this.name, % newFilePath, % overwrite
	}
	delete() {
		FileDelete, % this.name
	}
}
class FileContainer extends FileSystemElement {
	getPathName() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getPathExtension() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getPathNameNoExtension() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
}
class Directory extends FileContainer {
}
class Drive extends FileContainer {
	changeAttributes( changeAttributeString ) {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getPathDir() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getPathDirectory() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getTimeAccessed() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	setTimeAccessed( timeStamp ) {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getTimeModified() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	setTimeModified( timeStamp ) {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	getTimeCreated() {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
	setTimeCreated( timeStamp ) {
		Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
	}
}
Testen unserer Klasse:
Wir werden nun noch einmal die Sache testen, die wir ein paar Mal erstellt haben, um zu prüfen, ob sie brauchbar und korrekt ist.
Dazu werden wir nur ein paar Testfälle erstellen.
Es gibt mehrere Möglichkeiten, Klassen zu testen - für bestimmte Klassen kann es sinnvoll sein, nur ein paar Methoden zu testen - für andere werden alle Fälle und jeder Zentimeter davon getestet.
Für unsere Klasse und diesen Test - wo wir eine Klasse um bereits existierende Befehle herum erstellen - könnte es sinnvoll sein, ein paar Use Cases mit der alten Syntax zu erstellen und sie dann so zu ändern, dass sie mit unserer Klasse funktionieren.

Code: Select all

File := "test.txt"
LongFilePath := A_ScriptDir . "\" . File
SplitPath, LongFilePath, Name, Directory, Extension, NameNoExtension, Drive
FileOpen( File, "w" ).write( Name "`n" Directory "`n" Extension "`n" NameNoExtension "`n" Drive )
Dieser Code erzeugt eine Datei namens test.txt und fügt seinen Namen, das Verzeichnis, in dem er sich befindet, seine Erweiterung, seinen Namen ohne Erweiterung und das Laufwerk, auf dem er sich befindet, hinzu.
Wenn wir den gleichen Code grob übersetzen, damit er unsere Klasse benutzt, dann wäre es so etwas wie:

Code: Select all

#Include FileSystem.ahk ;I named the file containing our classes FileSystem.ahk
File      := "test.txt"
fileObj := new File( File )
fileObj.Open( "w" ).write( fileObj.getPath() "`n" fileObj.getPathName() "`n" fileObj.getPathDirectory() "`n" fileObj.getPathExtension() "`n" fileObj.getPathNameNoExtension() "`n" fileObj.getPathDrive() )
Wenn Sie dies versuchen, werden Sie sehen, dass nichts passiert. Wenn Sie einer Variablen mit dem gleichen Namen einer Klasse Daten zuweisen (wie in diesem Fall File), überschreiben Sie die Klasse mit den Daten.
Dies liegt daran, dass Klassen im Wesentlichen Objekte sind, die in superglobalen Variablen gespeichert sind. Unser Code würde nicht einmal funktionieren, wenn wir ihn in eine Funktion einbauen würden. Wir müssen entweder den Namen dieser Variable oder den Namen der Klasse ändern.
Wir fügen bereits Arbeit hinzu, indem wir unser Include verwenden - selbst in einem so einfachen Skript.

Modularität:
Dinge, die im superglobalen Bereich gespeichert sind, sind immer eine massive Ursache von Problemen. Stellen Sie sich vor, Sie hätten eine Bibliothek, die die Klasse File definiert, und eine andere Bibliothek wie Gdip, die die Variable File in fast jeder Funktion verwendet.
Sie müssten entweder die Klasse oder jede Variable innerhalb von Gdip umbenennen. Aber stellen Sie sich vor, dass Sie diese Klasse bereits in drei oder vier anderen Projekten verwendet haben - würden Sie sie dort auch umbenennen?
Erstellen Sie eine bestimmte Version, nur um mit GDIp zu arbeiten, oder erstellen Sie eine bestimmte Version von GDIp. Und nachdem Sie Ihrer File-Klasse gepräfixed haben (z.B. mit class zu classFile), stellen Sie fest, dass eine andere Bibliothek, die Sie bisher benutzt haben, nicht mehr mit Ihrer File-Klasse funktioniert. Also suchen Sie nach Alternativen - vielleicht verschiedene Bibliotheken oder rufen Sie die Dlls selbst auf? Oder erstellen Sie einfach Ihre eigene Bibliothek?
Sie werden feststellen, dass Sie mit der Zeit Ihren eigenen, spezifischen Namenssinn entwickeln werden, der automatisch Kollisionen verhindert (z.B. verwende ich oft kurze Namen innerhalb von Variablen und vollständige Namen in Klassen).: indirectReference vs. indRef ).
Die Arbeit mit anderen Menschen schafft meist nur mehr Probleme, als sie löst. So werden Sie oft feststellen, dass Sie nur Code von bestimmten Personen verwenden, von denen Sie wissen, dass sie mit Ihnen kompatibel sind.
Für AutoHotkey-Anwender ist es ein Muss, sich an die Standards zu halten, die von einem Teil der Community vorgegeben werden. Ich teile die Standards, die ich kenne, die von Bedeutung sind, für OOP-Material, das dazu gedacht ist included zu werden:
  • Benutzen Sie keine Superglobals oder Globals.:
    Wenn Sie OOP schreiben, haben Sie immer mindestens eine Klasse zur Hand und Klassen sind immer super global. Sie können diese Klasse fast wie jedes normale Objekt verwenden, nachdem es initialisiert wurde.
    Zusätzlich können Sie statische Daten direkt in der Klasse initialisieren, wenn sie geladen wird.:

    Code: Select all

    Msgbox % OpenGL.dllFile
    OpenGL.init()
    Msgbox % OpenGL.GL_FRONT
    
    class OpenGL {
    	static dllFile := "opengl32" ;it's even called openGl32.dll on Windows 64 bit
    	
    	init() {
    		DllCall( "LoadLibrary", "Str", OpenGL.dllFile )
    		OpenGL.GL_FRONT := 0x0404 ;it's a constant but to showcase what I mean	
    	}
    }
  • Vermeiden Sie Variablennamen, die so klingen, als ob sie gute Klassennamen ergeben würden.:
    Wenn etwas nach einem guten Klassennamen klingt, benennen Sie die Variable um.
    Klassennamen sind in der Regel ein oder mehrere Wörter in voller Länge, was uns zum nächsten Punkt bringt.
  • Versuchen Sie, den Sinn der Klassenbenennung konsistent mit allen anderen zu halten.:
    Klassennamen sind in der Regel Wörter in voller Länge - oft kombiniert. Manchmal benutzen die Leute ein Präfix - ich nicht.
    Außerdem sollten Sie vermeiden, ein einzelnes Wort zu verwenden, das häufig als Variablenname verwendet wird.
  • Vermeiden Sie byref zur Ausgabe von Daten:
    AutoHotkey mit OOP benötigt byRef nicht - byRef erfordert nur, dass der Benutzer eine neue Variable erstellt - diese Variable wird dann oft in einem Objekt gespeichert.
    Sie verschwenden eine Zeile und zwingen den Benutzer Ihrer Klasse, seinen Scope mit einer anderen Variable zu vertauschen - verwenden Sie stattdessen Arrays oder Objekte.
  • Fügen Sie Ihre Klassen in eine Containerklasse ein und benennen Sie Ihre Datei nach dieser Containerklasse.:
    Dies ist eine der wichtigsten Konventionen, die ich in letzter Zeit entdeckt habe. Im Wesentlichen legen Sie alle Ihre streunenden Klassen in eine Containerklasse.
    Zusätzlich benennen Sie den Dateinamen, den er enthält, genauso wie den Namen der Klasse, die die Klasse enthält. Auf die Klassen im Inneren kann dann durch Schreiben zugegriffen werden. Container.Class, kann eine neue Instanz wie folgt erzeugt werden new Container.Class()

    Code: Select all

    ;From:
    class FirstClass {
    }
    class SecondClass {
    }
    
    ;To ContainerClass.ahk:
    ;instance := new ContainerClass.FirstClass()
    class ContainerClass {
    	class FirstClass {
    	}
    	class SecondClass {
    	}
    }
    Auf diese Weise reduzieren Sie die Anzahl der Superglobals auf eins und teilen sogar den Namen des Superglobals im Dateinamen mit.
    Zusätzlich erhalten Sie auch eine neue Klasse, mit der Sie Methoden oder andere Funktionen speichern können, die Sie benötigen, um Ihre Klassen zum Laufen zu bringen, die aber nicht direkt zu einer der Klassen gehören.
Wir werden diese Regeln Schritt für Schritt auf unsere Klasse anwenden.:
  • Die Variablen sind größtenteils in Ordnung - aber ich habe TimeStamp mehrmals umbenannt, da das nicht nach einem unmöglichen Klassennamen klingt - ich glaube nicht, dass man hier überhaupt so vorsichtig sein muss.
  • Wir benutzen keine Superglobals.
  • Wir verwenden byRef nirgendwo
  • Ich denke, wir sollten unsere Klassen in eine Klasse namens FileSystem einpacken.
Wenn man diese Änderungen anwendet, erhält man folgendes Endergebnis:

Code: Select all

class FileSystem {
	class FileSystemElement {
		getAttributes() { ;flag string see AutoHotkey Help: FileExist for more infos
			return FileExist( this.name )
		}
		changeAttributes( changeAttributeString ) { ;see FileSetAttrib for more infos
			FileSetAttrib, % changeAttributeString, % this.name
		}
		getPath() {
			return this.name
		}
		getPathName() {
			SplitPath, % this.name, fileName
			return fileName
		}
		getPathDir() { ;same as getDirectory
			return This.getPathDirectory()
		}
		getPathDirectory() {
			SplitPath, % this.name, , fileDirectory
			return fileDirectory
		}
		getPathExtension() {
			SplitPath, % this.name , , , fileExtension
			return fileExtension
		}
		getPathNameNoExtension() {
			SplitPath, % this.name, , , , fileNameNoExtension
			return fileNameNoExtension
		}
		getPathDrive() {
			SplitPath, % this.name, , , , , fileDrive
			return fileDrive
		}
		getTimeAccessed() { ;in YYYYMMDDHH24MISS see AutoHotkey help for more infos
			FileGetTime, timeCreated, % this.name, A
			return timeCreated
		}
		setTimeAccessed( timeAccessed ) {
			FileSetTime, % timeAccessed, % this.name, A
		}
		getTimeModified() {
			FileGetTime, timeModified, % this.name, M
			return timeModified
		}
		setTimeModified( timeModified ) {
			FileSetTime, % timeModified, % this.name, M
		}
		getTimeCreated() {
			FileGetTime, timeCreated, % this.name, C
			return timeCreated
		}
		setTimeCreated( timeCreated ) {
			FileSetTime, % timeCreated, % this.name, C
		}
	}
	class File extends FileSystem.FileSystemElement {
		__New( fileName ) {
			if ( !fileExist( fileName ) )
				Throw exception( "File """ . path . """ doesn't exist", "__New", "Exist test returned false" )
			if ( inStr( fileExist( fileName ), "D" ) ) ;if file is a folder or a drive
				Throw exception( "Error creating File", "__New", "Path points to Folder" )
			Loop, Files, % strReplace(fileName,"/","\"), F ;since the fileName refers to a single file this loop will only execute once
				this.name := A_LoopFileLongPath ;and there it will set the path to the value we need
		}
		open( p* ) {
			return FileOpen( this.name, p* )
		}
		getSize( unit := "" ) {
			FileGetSize, fileSize, % this.name, % unit
			return fileSize
		}
		move( newFilePath, overwrite := 0 ) {
			FileMove, % this.name, % newFilePath, % overwrite
		}
		copy( newFilePath, overwrite := 0 ) {
			FileCopy, % this.name, % newFilePath, % overwrite
		}
		delete() {
			FileDelete, % this.name
		}
	}
	class FileContainer extends FileSystem.FileSystemElement {
		getPathName() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getPathExtension() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getPathNameNoExtension() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
	}
	class Directory extends FileSystem.FileContainer {
	}
	class Drive extends FileSystem.FileContainer {
		changeAttributes( changeAttributeString ) {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getPathDir() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getPathDirectory() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getTimeAccessed() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		setTimeAccessed( timeStamp ) {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getTimeModified() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		setTimeModified( timeStamp ) {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		getTimeCreated() {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
		setTimeCreated( timeStamp ) {
			Throw exception( "Couldn't find method" , A_ThisFunc, "Method: " . A_ThisFunc . " is not available for objects of Class: " . this.__class )
		}
	}
}
Beachten Sie, wie sich die "extends" verändert haben, nachdem ich die Klassen in den Container gewickelt habe.


Zusammenfassung:
In diesem Kapitel haben wir gelernt:
Über Extension:
  • was extension ist
  • wie man es benutzt
  • wo man es benutzt und wie man es handhabt
  • wie mit Kollisionen und Methodenverteilung umgeht
Über Modularität:
  • was es ist
  • warum es notwendig ist
  • viele Techniken und Konventionen, um es zu erreichen
Clean coding:
Obwohl ich das Thema in meinem Tutorial nie explizit erwähne, spreche ich über viele Konventionen, die Clean coding ermöglichen.

Zusätzliche Hinweise:
Sogar ich klebe nicht immer Methoden so perfekt zusammen - es ist nur, um Ihnen zu zeigen, wie perfekt sauberer Code aussieht.

Natürlich ist ByRef für Ausgangsvariablen nie notwendig - aber man könnte es immer verwenden, um die Geschwindigkeit von Eingangsvariablen zu erhöhen.
Allerdings bietet das nicht viel Geschwindigkeitsgewinn - wenn Sie mit schnellen Strings in OOP arbeiten wollen, müssen Sie eine String-Klasse schreiben.

Ich bin mir nicht sicher, ob ich das jemals erwähnt habe, aber.__Class enthält den Namen der Klasse, aus der das Objekt stammt.:

Code: Select all

testInstance := new TestClass()
Msgbox % testInstance.__Class
class TestClass {
}
Recommends AHK Studio
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: OOP:Einsteiger OOP in AHK

28 Jan 2018, 09:48

Reserved
Recommends AHK Studio
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: OOP:Einsteiger OOP in AHK

03 Feb 2018, 07:27

Ich habe den 2. Teil hinzugefügt. Der 3. sollte nun schneller gehen, da ich mir einen kleinen Helfer gebaut habe.
Recommends AHK Studio

Return to “Tutorials”

Who is online

Users browsing this forum: No registered users and 10 guests