OOP design patterns in AHK

Put simple Tips and Tricks that are not entire Tutorials in this forum
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

OOP design patterns in AHK

11 Oct 2017, 01:49

Today I want to bring you closer to one of my most favourite topics in the world of programming: design patterns.
Design patterns are like larger bricks of code that you will encounter and repeat a lot once you start seriously programming in OOP. They have one thing in common - they tackle certain problems in a adjusteable way.
It's fine if you imagine them like large lego bricks of code that you can connect with additional code. It mainly helps you planning and then developing what you planned leading to a higher success rate and better software.

In the following topic we will collect design pattern that have been used in AHK and describe them:
Name:If there is an official name for it use the official name if there isn't just think one up
AHK only:(yes/source) tells us whether this is a thing you just found useful for ahk or if it has an external source - if yes I would like to link an external source if possible
Description:Here you should explain shortly how this design pattern works and what it does.
Use-cases:When is this design pattern useful and what kind of problem does it tackle.
Code:Provide an example code for certain design patterns and use comments to show wht you described.

This topic is just for collecting the patterns.
You can discuss them here: https://autohotkey.com/boards/viewtopic ... 17&t=38448
Recommends AHK Studio
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: OOP design patterns in AHK

11 Oct 2017, 01:49

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

Re: OOP design patterns in AHK

11 Oct 2017, 02:32

Since the explination I gave doesn't exactly show what I mean I guess I will go ahead and show the first 2 patterns:


Name: Singleton
AHK only: a common pattern first introduced by the gang of four - they also wrote a famous book about design patterns you should check that out
Description: A Singleton pattern is a pattern where a class only has one instance - here it is combined with automatic initialization. It works by writing to a static variable in the constructor once and then returning it's content ins
Use-cases: This is especially useful for libraries
Code:

Code: Select all

class singleton
{
	
	__New()
	{
		static init
		;This is where the instance will be stored
		
		if init ;This will return true if the class has already been created
			return init ;And it will return this instance rather than creating a new one
		
		init := This ; this will overwrite the init var with this instance
		
		;Whatever you want to do with your class constructor
		;for example:
		This.foo  := "bar"
		This.time := A_TickCount ;measure the current time ( we will use that later )
	}
	
	;whatever else you want inside your class
	
}

;To show you what I mean:
startTime := A_TickCount ;This is just to reduce the amount of numbers in A_TickCount 
sleep 400 ;This is to make current time pass
Loop 2 ;this is to show that the time since test was constructed remains the same
{
	test := new singleton() ;This will return the instance that was created in the beginning rather than creating a new one
	Msgbox % "Time when test was constructed:" . test.time - startTime . " ms`ncurrent sime: " . A_TickCount - startTime " ms" ;show that test is indeed a single instance
}
Name: automatically initialized singleton
AHK only: That's one thing that probably could be used outside of AHK
Description: A singleton pattern is a pattern where a class only has one instance - here it is combined with automatic initialization. It works by setting a certain key on startup and checking that key in it's constructor.
Use-cases: This is especially useful for libraries. Imagine you have a class Version of GDIp and it automatically calls gdipStartup. Of course this is a simple example - there are way more complex libraries out there.
Code:

Code: Select all

class staticSingleton
{
	
	__New()
	{
		static init := new staticSingleton()
		;The static keyword mkes sure that this get's called when AutoHotkey is just done parsing everything
		;It will happen before the auto execute section
		;So this will make sure that it initializes ASAP
		
		if init ;This will return true if the class has already been created
			return init ;And it will return this instance rather than creating a new one
		
		;Whatever you want to do with your class constructor
		;for example:
		This.foo  := "bar"
		This.time := A_TickCount ;measure the current time ( we will use that later )
	}
	
	;whatever else you want inside your class
	
}

;To show you what I mean:
startTime := A_TickCount ;This is just to reduce the amount of numbers in A_TickCount 
sleep 400 ;This is to make current time pass
Loop 2 ;this is to show that the time since test was constructed remains the same
{
	test := new staticSingleton() ;This will return the instance that was created in the beginning rather than creating a new one
	Msgbox % "Time when test was constructed:" . test.time - startTime . " ms`ncurrent sime: " . A_TickCount - startTime " ms" ;show that test is indeed a single instance
}
Recommends AHK Studio
User avatar
runie
Posts: 304
Joined: 03 May 2014, 14:50
Contact:

Re: OOP design patterns in AHK

11 Oct 2017, 02:40

Name: Subclassed method
AHK-only: kinda specific to AHK syntax
Description: Creates a new temporary instance of a subclass when the subclass is called as a method via the main class.
Use-cases: Making a subclass that can be called as a method
Code:

Code: Select all

MyClass.SubClass("Test", "Second", 3)

Class MyClass {
	Class SubClass extends MyClass.Functor {
		Call(Params*) {
			msgbox % this.ParseObject(Params)
		}
		
		ParseObject(Params) {
			for Index, Param in Params
				ParamText .= "`n" Param
			return SubStr(ParamText, 2)
		}
	}
	
	Class Functor {
		__Call(NewEnum, Param*) {
			(new this).Call(Param*)
		}
	}
}
See here for a bit more detail: https://autohotkey.com/boards/viewtopic.php?f=7&t=38149
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: OOP design patterns in AHK

12 Oct 2017, 18:28

Nice topic nnnik, and interesting ideas from all contributors. :thumbup:

I give a small example, which I find useful sometimes,

Name: Proxy / router / implicit reference object
AHK only: Wikipedia has an article Proxy pattern which seems to be relevant.
Description: In short, operate on an object through another object.
Use-cases: In ahk it is especially useful when dealing with self-refrences and meta functions. (Comment limited by my imagination)
Code: Two simple examples, first, a proxy class which only forwards method calls to a referenced object,

Code: Select all

; Pattern:
class proxy {	; Simple Method call proxy
	__new(p){
		objrawset(this,proxy,p)	; Using the class as key for the reference is convenient if you want to implement __set and __get in the proxy class, any other "unlikely" key will do ofc.
	}
	__call(f,p*){
		return this[proxy][f](p*)	; forward the method call to the referenced object
	}
}
; Example usage:
myTestProxy:= new proxy(new test)
myTestProxy.g(1)

class test{
	g(a){
		msgbox % a
	}
}
Second, a more useful example, dealing with an object which has self-references,

Code: Select all

; Pattern:
class proxy {	 	
	__new(p){
		objrawset(this,proxy,p)
	}
	__delete(){					
		this[proxy].clear()		; when a derived proxy object is released, it calls its referenced object's clear() method,
								; which clears self-references, then the (referenced) object can be properly released
								; Note: if __delete is called after all onExit routines has been called, the variable proxy might be blank,
								; you could do this[ objgetbase(this) ].clear() to work around. However, in general it is recommended to ensure 
								; __delete is not called after all onExit routines has been called
	}
}
; Example usage:
new o("A")	; not released
new op("B")	; released. Note, currently, if you assign the result of this line to a variable called z (but not a), the object will not be released. See comments in proxy.__delete for more details.

class o {	; Object with self-references, you need to "manually" call clear() to break self-references
	__new(name){
		this.name:=name		; For visual purposes
		this.that:=this		; self-reference
	}
	clear(){
		this.that:=""
	}
	__delete(){
		msgbox % "released " this.name
	}
}

class op extends o {	; The proxy will call the clear() method for you
	__new(name){
		base.__new(name)
		return new proxy(this)	; Returns the proxy object instead of "this".
	}
}
Real use example: I use a proxy for the hash table data structure, which implements __set and __get to enable the familiar array ((val:=) obj[key]:=val) syntax while avoiding the possiblity to overwrite methods, eg, obj["methodName"] := 37 stores 37 under the key methodName in the hash table, without overwriting the method.

Cheers.
Last edited by Helgef on 08 Feb 2019, 03:48, edited 2 times in total.
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: OOP design patterns in AHK

17 Oct 2017, 09:05

Name: super global automatically initialized Singleton
AHK-Only: Probably
Description: A Singleton that is stored in the super global variable of it's base class and initializes itself automatically - it works like the other singletons I have shown so far exept that it also overwrites its base classes super global variable.
Use-Cases: Libraries - it has the advantage over static super global Singletons that __Get, __Set, __Call and __Delete work properly.
Code:

Code: Select all

class superGlobalAutoSingleton
{
	
	__New()
	{
		;These 5 lines are the important part
		static init := new superGlobalAutoSingleton()
		if init
			return init
		classPath := StrSplit( This.base.__Class, "." )
		className := classPath.removeAt(1)
		if ( classPath.Length() > 0 )
			%className%[classPath*] := This
		else
			%className% := This
		
		;...add the rest of the constructor here
		This.text := "Abc"
		This.text2 := "def"
		This.name := className . " Instance"
	}
	
	getText()
	{
		return This.text . This.text2
	}
	
	__Delete()
	{
		Msgbox % "deleting " . This.name ;To show that __delete still works
	}

}
Msgbox % superGlobalAutoSingleton.getText()
Recommends AHK Studio
User avatar
runie
Posts: 304
Joined: 03 May 2014, 14:50
Contact:

Re: OOP design patterns in AHK

25 Dec 2017, 21:59

Name: Run multiple __New methods automatically in an extended class
AHK only: Yes.
Description: If you extend a class, the __New method in the base class will not run if it's overwritten in the class that extends it.
Use-cases: If you want to replicate how for example python handles inheritance.

Code: Select all

new Top

Class Top extends Bottom {
	__New() {
		msgbox % A_ThisFunc
	}
}

Class Bottom {
	; this will be put in Bottom.__Init(), which runs when a new Top is instantiated
	; so by calling Bottom.__New with the instance from Top, we can run both __New methods.
	__NewInit := Bottom.__New.Call(this)
	
	__New() {
		msgbox % A_ThisFunc
		return ""
	}
}
Only downside is that it'll store a variable in the instance called __NewInit.
User avatar
runie
Posts: 304
Joined: 03 May 2014, 14:50
Contact:

Re: OOP design patterns in AHK

25 Dec 2017, 22:19

This is a continuation of the above, which also supports __Delete.

Code: Select all

new Top

Class Top extends Bottom {
	__New() {
		msgbox % A_ThisFunc
	}
	
	__Delete() {
		msgbox % A_ThisFunc
	}
}

Class Bottom {
	__NewInit := Bottom.__New.Call(this)
	
	__New() {
		msgbox % A_ThisFunc
		
		if this.base.HasKey("__Delete")
			this.base.__Delete := this.base.base.__Delete
		return ""
	}
	
	__Delete() {
		msgbox % A_ThisFunc
		
		if this.base.HasKey("__Delete")
			Func(this.base.__Class ".__Delete").Call(this)
	}
}
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: OOP design patterns in AHK

26 Dec 2017, 04:23

I think the __New is a good one.
However the __Delete one probably needs work
Recommends AHK Studio
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: OOP design patterns in AHK

14 May 2018, 12:10

Not sure I agree with the Singleton implementations in here.
For me, a Singleton pattern in AHK would look like this (If you used a property, of course it could be done using a GetInstance() method too):

Code: Select all

MySingleton.Instance.Test()
MySingleton.Instance.Test()

class MySingleton {
    Instance[]
    {
        get {
            if (!this.HasKey("_Instance")){
                this._Instance := new MySingleton()
            }
            return this._Instance
        }

    }
    
    Test(){
        ToolTip % A_TickCount
    }
}
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: OOP design patterns in AHK

14 May 2018, 12:36

Here is a version that you can derive from to create a singleton.
It also stops the user from trying to new one up directly.

Uses Runie's "Run multiple __New methods" technique

Code: Select all

MySingleton.Instance.Test()
MySingleton.Instance.Test()
a := new MySingleton()

class MySingleton extends SingletonBase {
	__New(){
		a := 1
	}
	
	Test(){
		ToolTip % A_TickCount
	}
}

class SingletonBase {
	__NewInit := SingletonBase.__New.Call(this)
	
	__New(){
		if (this._Instance != -1){
			Msgbox % "Error: Tried to instantiate Singleton"
			ExitApp
		}
	}
	
	Instance[]
	{
		get {
			if (!this.HasKey("_Instance")){
				this._Instance := -1
				c := this.__Class
				this._Instance := new %c%()
			}
			return this._Instance
		}
	}
}
User avatar
cyruz
Posts: 346
Joined: 30 Sep 2013, 13:31

Re: OOP design patterns in AHK

27 Dec 2018, 21:35

Sharing this, maybe can be useful for somebody:

Name: Superglobal instance of class.
AHK only: Yes.
Description: Replace class superglobal variable with its object instance.
Use-cases: Take advantage of meta functions when using static class method calling (e.g. to check if an object has been instantiated).

Code: Select all

MsgBox, % Test.Hello()
obj := New Test(1)
MsgBox, % obj.Hello()

Class Test
{
	Static _ := (Test := new Test) && 0

	__New(bFlag)
	{
		this.FLAG := bFlag
	}

	__Call()
	{
		If ( !this.FLAG )
			Return "Object not instantiated"
	}
	
	Hello()
	{
		Return "Hello!"
	}
}
Last edited by cyruz on 08 Feb 2019, 06:04, edited 1 time in total.
ABCza on the old forum.
My GitHub.
User avatar
vvhitevvizard
Posts: 454
Joined: 25 Nov 2018, 10:15
Location: Russia

Re: OOP design patterns in AHK

04 Jan 2019, 17:23

AHK: v2 (tested with alpha100)
Name: The shortest
Description: I'm an apologist of super-compact writing style. Here is my version of self-initializing prototype class with __Get, __Call, etc meta-functions functioning.
Code: Test example:

Code: Select all

Class1:={base:Class1}
class Class1{
	static __Call:=(_, _method, _args*)=>_args[1]
}
MsgBox(Class1.a2("1st arg"))
and 2 variations w/o instantiation:

Code: Select all

class Class4 extends Class4.Class5{
	class Class5{
		static __Call:=(_, _s, _a*) =>_a[1]
	}
}

class Class3{
	static __Call:=(_, _s, _a*) =>_a[1]
}
class Class2 extends Class3{
}

;tests
msgbox(Class4.a2("1st arg"))

msgbox(Class2.a2("1st arg"))
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: OOP design patterns in AHK

07 Feb 2019, 10:12

Helgef wrote:
12 Oct 2017, 18:28
circular reference proxy

Code: Select all

class proxy {	 	
	__new(p){
		; no idea whats that supposed to do
		; a 'proxy' object is instantiated
		; the 'proxy' super-global class is the used as a key in the instantiated 'proxy' object, to assign to this key, the object that the 'proxy' wraps
		; why
		objrawset(this,proxy,p)
	}
	__delete(){		
		; before the script starts, store a reference to the super-global variable 'proxy' (which is what contains the implementation of the proxy class), 
		; inside 'proxy.__delete()' method's 'prx' static variable
		; why is this done?
		static prx := proxy
		
		; use the stored reference to the super-global var containing the class 'proxy' as a key, 
		; to access the instantiated 'proxy' object's member variable, which contains a reference to the object wrapped by the instantiated 'proxy'
		; call this wrapped reference's 'clear()' method
		this[prx].clear()			; when a derived proxy object is released, it calls its referenced object's clear() method,
									; which clears self-references, then the (referenced) object can be properly released
	}
}
can u explain why this works the way it does

the same can be achieved with:

Code: Select all

class proxy {	 	
	__new(p){
		objrawset(this, "reference_to_wrapped_object",p)
	}
	__delete(){					
		this.reference_to_wrapped_object.clear()
	}
}
so, what about ExitApp()'s behavior necessitates storing the class in a static var
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: OOP design patterns in AHK

08 Feb 2019, 01:46

You can use whichever key you like, the point with using the class object was that it would not be used by the wrapped object.

When the program starts to terminate, eg after exitapp is called, variables are released in unknown order, so when the delete method is called, the proxy variable might be blank, hence I used have static variables since they seem to live longer, it is a bad idea and I will change it. We could use objgetbase I guess. Further, using onexit to release stuff is recommend over relying on things existing near the termination of the script.

Cheers.

Return to “Tips and Tricks (v1)”

Who is online

Users browsing this forum: No registered users and 27 guests