Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Class library (OOP) - Help Thread


  • Please log in to reply
92 replies to this topic
bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Maybe this is beyond the scope of what's available now, but what if you want to have a "table" of data stored as an Array or Vector?

For example, I want a Vector of Element attributes for an XML document, but I don't want to create an Attribute object just for use in that one place. Each attribute has a number of properties (qName, uri, localName, etc...), so I can't store them as strings efficiently.

Is there a way to specify a generic object with an arbitrary number of "fields" I can access? I guess it would be considered a generic 'struct'? Or would I be better off just creating a new class for an attribute?

In the Java code I'm referencing, to access the "uri" field of an attribute in the array, you'd use a call like:
data[index*5]

localName is:
data[index*5+1]
etc.

If nothing else, I'll just create an Attribute class and fill the Vector with Attribute objects.

Thanks!

animeaime
  • Members
  • 1045 posts
  • Last active: Jun 18 2011 04:44 AM
  • Joined: 04 Nov 2008

Is there a way to specify a generic object with an arbitrary number of "fields" I can access? I guess it would be considered a generic 'struct'? Or would I be better off just creating a new class for an attribute?

That sounds like a class... or an Array (for a known number of fields). For example, if you had 5 fields, you could make a Class that contained those 5 fields. Or, you could create an Array of length 5 and store the values in it (via the wrapper classes) and this would be your "struct".

In the Java code I'm referencing, to access the "uri" field of an attribute in the array, you'd use a call like:

data[index*5]

localName is:
data[index*5+1]

etc.

So data is an array of Strings, I take? The multiplier of 5 implies there are 5 such values in this "attribute". Then, each "field" in the "attribute" would be at a specified offset? It seems "uri" is at offset 0 and "localName" is at offset 1.

Also, since it is an array, the length is predetermined, right? Or is it unknown at creation?


Why not use an Array or Vector of Strings? Use the String class to wrap a String and create an Array or Vector of Strings. Then, do the same thing you would in java.

;"uri field"

;in Java (0-based index)
data[index*5]

;in AHK (1-based index)
Array_get(data, index*5+1)

;"localName field"

;in Java (0-based index)
data[index*5+1]

;in AHK (1-based index)
Array_get(data, index*5+2)


I'm not sure what needs you have, and a Class object might simplify them, but the above is the equivalent to the java code you presented. Just remember that when setting a value, you will need to wrap the string first.

;"localName field"

;in Java (0-based index)
data[index*5+1] = "Some name"

;in AHK (1-based index)
Array_set(data, index*5+2, String_new("Some name"))


The string will automatically be destroyed when the Array, data, is destroyed, so no worries there. You can just add 1 to the index (to convert from 0-based to 1-based index), so that's easy. The Array_get will automatically unwrap the String value, so this is done for you. You will, however, need to remember to wrap the String when setting the value.


I'm not sure if I answered your question. If not, could you explain what it is you are trying to do, and why the above suggestions don't work. This would help me answer your question better. Also, remember that a "struct" is nothing but a Class with no functions. In AHK, a "struct" would still be called a "class", but you can have it behave like a struct by defining no class functions (apart from the getters / setters, and the "required" functions (e.g. new, initClass, destroy)).

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Thanks very much for the clarification.

I never even considered using a flat array and simply assigning 5 values to each field. It seems less Object-oriented than creating an Attribute object containing the 5 fields and storing each attribute as one object in the array, however the goal of this operation is speed, and that's how it's done in the Java implementation of the class I'm writing, so that's cool.

In answer, yes, this is for writing a SAX driver, and when I'm creating this Attributes object, the number of attributes will always be known when the object is created, and the number of strings assigned to each attribute will always be the same, so I think an array will work fine as opposed to a Vector. (Update: I take that back, the class supports a blank factory method, meaning no attributes, so I'm going to have to support adding attributes later--Vector it is!)

Since java is 0-based, the first field (index*5) is still at index 0. With AHK, 1-based, the first field (index*5) would start at index 5. So I suppose it would be better for me to calculate indexes as 0-based and then add one to the end, instead of using AHK-style indexes, leaving me with 4 unused array indexes.

Thanks a lot for walking me through that.

animeaime
  • Members
  • 1045 posts
  • Last active: Jun 18 2011 04:44 AM
  • Joined: 04 Nov 2008

Since java is 0-based, the first field (index*5) is still at index 0. With AHK, 1-based, the first field (index*5) would start at index 5. So I suppose it would be better for me to calculate indexes as 0-based and then add one to the end, instead of using AHK-style indexes, leaving me with 4 unused array indexes.

If you have index be 0-based, then you're fine. If not, for example in a Loop, then just store A_Index - 1 in a value (the 0-based index). This way you don't have to rewrite the code - since you probably store the index in some variable in Java anyway - just use the same name and store A_Index - 1 in that variable.


The first attribute is at index=0, the second at index=1, etc. For each field, have them 0-based. Finally, on the last step (e.g. a get or set) add 1 to convert from the 0-based to 1-based indexes (for AHK).

For example, the "uri" field for the first attribute would be retrieved like so.

;0-based index
index = 0

;0-based field ("uri" is the first field)
field = 0

;+1 converts from 0-based to 1-based index
value := Vector_get(data, index*5+field+1)

The "localName" field for the first attribute would be retrieved like so.

;0-based index
index = 0

;0-based field ("uri" is the second field)
field = 1

;+1 converts from 0-based to 1-based index
value := Vector_get(data, index*5+field+1)

This way, you can keep the same references that you use in Java, and convert it as the end from 0-based to 1-based using the "+1". This is what I typically do when converting code from Java (or another 0-based index language) to AHK (or another 1-based index). This way, I don't have to worry about changing the code, and just convert on the last step (e.g. when retrieving or setting values).

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
In my current work with classes, I have three Factory classes going which are designed to persistently register different implementations of some of my interfaces (since sometimes you can't necessarily know what the class name of those implementation is in the script), and then create an instance of one upon request.

Since I wanted to keep things consistent, I figured, why not create an ImplementationFactory interface that anyone who wants this functionality can implement in their own factory?

Since I'm pretty new at OOP with AHK, I wanted to get your input on it, to see if it appears I've done things properly, and logically. Care to take a peek for me and give me any insight before I finalize the first version of it for use in my classes and for anyone who wants to utilize it?

/*
ImplementationFactory Interface

This interface provides methods and fields for any class acting as a factory class, allowing object registration and retrieval, either temporarily or consistently between sessions.

The "new" method of the ImplementationFactory should never be called by the end user. It is called only within the "singleton" method as needed. All static methods should use the "singleton" method to retrieve the current factoryObject.

A class must be registered with the factory (which should then persist permanently unless unregistered).
Registration can happen one of the following ways:
 - Calling the "register" method (which should persist permanently between sessions [unless unregistered or not found])
 - Calling the "add" method (which only persists until the factory object is destroyed)
 - Modifying the defaultImplementations list, though how that is implemented is not defined here

 Factories should generally store registered implementations in an implementationsFile. How this is implemented is not defined in this interface, however.

 Note:
 Many times, factories will need to locate an implementation based on what "features" the it offers. How this is handled is out of scope for this interface, but a recommentation is to expand the "create" method into a "create[ImplementationName] method which includes one or more parameters for defining the features to check for.
*/

/*
	Static Methods
*/

/*	Method: singleton
	Description: returns the current factoryObject, and optionally re-creates it

	This function is called within every other static function to always get the current factoryObject.

	This function handles the one and only factory object that should exist during any session. The factoryObject should be a static variable within this function. It is created upon the first call to this method, and destroyed and re-created if the reset parameter is true.

	The function ALWAYS returns a valid factory object. If one already exists, and reset is not true, it simply returns the existing factory.
*/
ImplementationFactoryI_singleton(reset) {
	return Class_sCall(A_ThisFunc, implementation)
}

/*	Method: create
	Description: Locates and creates a new instance of an object from a registered implementation

	This allows a single point of object creation no matter which class implements the object.

	Some ImplementationFactories may create several types of objects, and may have different createImplementation-style static functions to accomodate creating different types of things. In those cases, this function may return either one of the other creation functions, or nothing.
*/
ImplementationFactoryI_create() {
	return Class_sCall(A_ThisFunc)
}

/*	Method: register
	Description: Register an implementation with the factory (permanently until unregistered)

	The factory should save the implementation permanently. No matter how this is implemented (usually by saving the list of implementations to file), it should not allow duplicates in the list.

	This method should also add the new implementation to the current factoryObject, unless it has already been added manually with the "add" method (duplicates should not be allowed in that list either).

	An ImplementationFactory is required to verify that a registered implementation actually exists before it is used. If it doesn't exist, or it's new function doesn't return a valid object, the next registered implementation is tried.

	An implementation may be unregistered automatically by the factory if it is not found, but it is not required by this interface.
*/
ImplementationFactoryI_register(implementation) {
	return Class_sCall(A_ThisFunc, implementation)
}

/*	Method: unregister
	Description: Unregister an implementation with the factory

	The factory should remove the implementation permanently (unless it is again registered). It should also remove it from the current factoryObject, though it may also be removed manually before it is unregistered, so this method should not require the removal to succeed.
*/
ImplementationFactoryI_unregister(implementation) {
	return Class_sCall(A_ThisFunc, implementation)
}

/*	Method: add
	Description: adds an implementation to the current implementationObject

	This does not persist between sessions, or register the implementation permanently. It should be used during this session as if it were registered, however.

*/
ImplementationFactoryI_add(implementation) {
	return Class_sCall(A_ThisFunc, implementation)
}

/*	Method: remove
	Description: removes an implementation from the current implementationObject

	This does not persist if the implementation is registered, but only removes it from the current session's factoryObject. It does not unregister the implementation.
*/
ImplementationFactoryI_remove(implementation) {
	return Class_sCall(A_ThisFunc, implementation)
}

/*
	Fields
*/
; Returns a list or object of registered (or known) implementations (can be called to get a list of all known implementations, though its format or return type is undefined here)
ImplementationFactoryI_getImplementations(ImplementationFactoryObject) {
	return Class_Call(ImplementationFactoryObject, A_ThisFunc)
}
; Sets the current list of implementations (usually not called by end-users)
ImplementationFactoryI_setImplementations(ImplementationFactoryObject, implementations) {
	return Class_Call(ImplementationFactoryObject, A_ThisFunc, implementations)
}


animeaime
  • Members
  • 1045 posts
  • Last active: Jun 18 2011 04:44 AM
  • Joined: 04 Nov 2008
Sorry for the slow reply. For some reason, I didn't get an email saying a new reply was posted.

Since I'm pretty new at OOP with AHK, I wanted to get your input on it, to see if it appears I've done things properly, and logically. Care to take a peek for me and give me any insight before I finalize the first version of it for use in my classes and for anyone who wants to utilize it?

Aren't we all pretty new at OOP with AHK?? I mean, it's a relatively new thing...


However, I'll be glad, like always, to share my input.

ImplementationFactoryI_singleton(reset) { 
   return Class_sCall(A_ThisFunc, implementation) 
}
1a) Why is there a parameter, "reset", which is never used?
1b) Where is "implementation" declared?
1c) For Class_sCall, You have to pass an object / class name for the first parameter.

ImplementationFactoryI_create() { 
   return Class_sCall(A_ThisFunc) 
}
2) Class_sCall needs at least two parameters. The first is a class object or the class name of the class to use for the call, and the second is the A_ThisFunc value.

ImplementationFactoryI_register(implementation) { 
   return Class_sCall(A_ThisFunc, implementation) 
}

ImplementationFactoryI_unregister(implementation) { 
   return Class_sCall(A_ThisFunc, implementation) 
}

ImplementationFactoryI_add(implementation) { 
   return Class_sCall(A_ThisFunc, implementation) 
}

ImplementationFactoryI_remove(implementation) { 
   return Class_sCall(A_ThisFunc, implementation) 
}
3) You have to pass an object / class name for the first parameter (see 2 for details).


Apart from these generic comments, I really can't say much, as I don't have the slightest idea how to use the interface (and thus, not sure how it would be implemented). As such, I can't add anything on that end, sorry. :D

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
It's pretty simple as interfaces go... I haven't documented it as well as I should... but it's an AHK implementation of a factory class as used for many Java projects, only AHK doesn't have most of the means that Java uses to find the classes.

I haven't defined what an 'implementation' is because for this interface it doesn't matter... I'll try explaining it with an example, as you've requested for my past babblings :)

Let's say several people write an Email class implementing the EmailI interface, and you're writing a script to distribute which sends email without knowing which Email implementation the person utilizing your script will be using...

An EmailFactory class can be created from the ImplementationFactoryI interface, which allows the available Email classes to register themselves (permanently). Then the script which implements email can call EmailFactory_create(), and the factory will find an email implementation, create an object from it, and return the object for use in the script.

And that's it.

---

I'm implementing this for the DOM in the DOMImplementationRegistry class. I'm using it for my SAX driver in the SAXXMLReaderFactory class. And I'm starting to implement it in several other larger projects I'm starting to get underway.

The issue is, I don't want the object to be specified as a parameter to most of the functions; I want the functions to look up the object from the 'singleton' method. Is this possible to portray in an interface?

---

Oh, and the 'reset' parameter of the 'singleton' method does get used; it is supposed to re-create the singleton object inside the function.

animeaime
  • Members
  • 1045 posts
  • Last active: Jun 18 2011 04:44 AM
  • Joined: 04 Nov 2008

Oh, and the 'reset' parameter of the 'singleton' method does get used; it is supposed to re-create the singleton object inside the function.

ImplementationFactoryI_singleton(reset) { 
   return Class_sCall(A_ThisFunc, implementation) 
}
Ummm... but it's not used. It's a parameter (i.e. local) and not passed to Class_sCall. In other words, its not used. Maybe it is suppose to be used, but it's not. You will have to pass it to Class_sCall to even have the option to use it. As it is now, it cannot be used, as it is a local parameter. Also, implementation isn't passed as a parameter, should it be?


On a side note, Class_sCall needs the first parameter to be a Class object or class name - this is not optional; it is this class which will be used when making the dynamic call (internally in Class_sCall). Just like how you passed an object for Class_call, Class_sCall needs either an object or a class name, so that it knows which class to invoke the function.

Without it, 1) the interface won't function as you expect, and 2) the call to Class_sCall with only one parameter will cause an error when you try to run it. It will tell you that the function wasn't passed enough parameters (since it has two mandatory parameters, the class object / class name, and the second being the function name - the A_ThisFunc value).

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Oh, no, I understand that--I misunderstood what Class_scall was for, and simply don't fully understand static functions at this point--working on that.

The parameter passed to scall was supposed to be reset, not implementation--that was just a code error.

I guess I can't make an interface out of a factory class, since they look up the object in the function instead of being provided an object as a parameter that can be passed on to scall... I'll just have to keep a 'specification' on file so I keep them consistent.

Going back to my development hole for a bit to try and finish up this SAX driver, so that I can finish up my DOM Load and Save module, so that I can finish up my Config and XMLTemplate classes, so that I can finish up the next version of FOMS and SteamLab, so that I... oh, what? :)

joebodo
  • Members
  • 48 posts
  • Last active: Feb 13 2010 09:55 PM
  • Joined: 28 Apr 2008
Take a look if your interested. I took a different approach to implementing classes and objects. Still a work in progress, but some of the stuff may be useful for you.

Quick summary:
objects are passed by a reference string (like __Instance_43)
state for objects are stored internally as XML
you can add callbacks and notifications to existing objects
you can add custom properties to objects

http://www.autohotke...oebodo/tail.zip

The tail script is larger sample for testing the framework. But there's some shorter examples under src/gui/samples. Here's a preview:
windowTest(0)

windowTest(offset) {

	window := new("Window", "/x:" . 200+offset . " /y:" . 50+offset)

	okButton := new("Button", "/text:Click me"
		. " /window:" . window
		. " /offset:" . offset
		. " /x:5 /y:5 /w:100")
	addCallback(okButton, "onClick", "clicked")

	quitButton := new("Button", "/text:Quit"
		. " /window:" . window
		. " /x:5 /y:45 /w:100")
	addCallback(quitButton, "onClick", "quit")

	call(window, "show")
}

clicked(this, args) {
	offset := getProperty(this, "offset")
	windowTest(25+offset)
}

quit(this, args) {
	ExitApp
}

#include ../../..
#include src/core/Package.ahk
#include src/gui/Package.ahk


bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Interesting work joebodo. I like the classes you've created and the way they're implemented to an extent. I only wish AHK's lib dir would support subdirectories so that we could have a full class path.

It's somewhat unfortunate that, with so few OOP developers active in these forums, there are now two Class libraries for them to have to choose between--I think it would be great if we could take the best aspects of both libraries and unify the development work to create one great OOP library for AHK. But that's just my opinion :)

--

Incidentally, I've been writing a lot of classes utilizing callbacks with animeaime's library here--it works quite well there, too--you just have to implement it within the classes instead of through the Class library.

--

It probably won't help much since the Class format is different, but I'm writing two completely standardized XML implementations using animeaime's Class library, if anyone is going to be looking for full-featured XML support.

I am finished with the DOM Level 3 Core, and working on the Load and Save module now (Events, Validation, and XPath modules to follow).

I am also finished creating a SAX package. Now I'm writing some default XMLReader drivers to go along with it. Once it's bug tested and confirmed working properly, it might be neat to include SAX support (and maybe even DOM support) in the Class distribution :) So far both libraries come in together at about 500k

animeaime
  • Members
  • 1045 posts
  • Last active: Jun 18 2011 04:44 AM
  • Joined: 04 Nov 2008

I think it would be great if we could take the best aspects of both libraries and unify the development work to create one great OOP library for AHK.

Do you have a link for the other OOP library that I can check out?

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
It's included in joebodo's tail.zip file: http://www.autohotke...oebodo/tail.zip

Futurity
  • Members
  • 21 posts
  • Last active: Feb 08 2011 02:42 PM
  • Joined: 13 Feb 2007
I have this class that always crashes AutoHotkey, what I do wrong? :)

Main := Main_new()
Main_setRun(Main, "lol")
return

#include Class.ahk

Main_getTR_Selection(MainObject) 
{
    return Class_getValue(MainObject, "b1", "uint")
}
Main_setTR_Selection(MainObject, Object) 
{
    return Class_setValue(MainObject, "b1", Object, "obj")
}

Main_getRun(thisObject) 
{
    return Class_getString(thisObject, "b2")
}
Main_setRun(thisObject, list = "lol") 
{
  NumberOfT := 0
  NumberOfP := 0
  Loop, c:\* , 2, 0
  {
    PName := A_LoopFileName
    NumberOfP := a_index
      loop, %PName%\* , 2 , 0
      {
          NumberOfT += 1
          if (NumberOfT = 1)
            TR_TemplatesString := A_LoopFileName . "|" . PName
          else
            TR_TemplatesString := TR_TemplatesString . "$" . A_LoopFileName . "|" . PName
      }
      loop, %PName%\* , 2 , 0
      {
          NumberOfT += 1
          if (NumberOfT = 1)
            TR_TemplatesString := A_LoopFileName . "|" . PName
          else
            TR_TemplatesString := TR_TemplatesString . "$" . A_LoopFileName . "|" . PName
      }
  }
  return Class_setString(thisObject, "b2", TR_TemplatesString)
}

;if called, this function MUST be called only once,
;   and before creating any objects of this class.
;sets the size of the user-defined values (can change script to script)
Main_setUserDefinedSize(Size = "")
{
    static UserDefinedSize = 0

    if (Size != "")
        UserDefinedSize := Size

    return UserDefinedSize
}


;destroys the specified Main object and frees any memory that it occupies.
Main_destroy(ByRef MainObject)
{
  if (result := Class_destroy(MainObject))
    MainObject := ""
  return result
}

Main_equals(MainObject, ClassObject)
{
    return Class_equals(MainObject, ClassObject)
}

;stores a single copy of the class name
Main_initClass()
{
    ;specify Class options in paretheses
    static ClassName := "Main(Cloneable)"

    return &ClassName
}


/*
allocates memory for a new Main object and returns it's address

eases creation of multiple constructors
    see the new() function for use)
*/
Main_new()
{
    ;size of built-in values
    static BuiltInSize = 0
    ;remove if Class allows user-defined values
    static UserDefinedSize = 0
        
    ;Allocate Main info structure
    if MainObject := Class_Alloc(4 * UserDefinedSize + BuiltInSize)
    {
        ;Store "Class values" (must be present in ALL classes)
        Class_setClass(MainObject, Main_initClass())
        Class_setBuiltInSize(MainObject, BuiltInSize)
        Class_setUserDefinedSize(MainObject, UserDefinedSize)
    
        ;Set Built-in values (done in constructors)
        
    }
    
    return MainObject
}

;stores a single copy of the structure for the built-in values
;(note: this is NOT stored with the class object)
Main_getBuiltInValues(variableName = "")
{
    static BuiltInValues

    if (BuiltInValues = "")
    {
        ;only done once

        BuiltInValues =
        (LTrim Comments Join,
            ;list the built-in values here
            obj   
            string
            ;obj
            ;obj  
            ;list the TYPES (used by the SETTERS) for your Class values
            ;the order MUST mimic the setter order
            ;   first value ("b1") first, etc.
        )
    }

    return BuiltInValues
}

;returns a clone of the Main object
Main_clone(MainObject, DeepCopy = true)
{

    TheClone := Class_clone(MainObject, DeepCopy)
    
    if (DeepCopy = -1)
    {

    }

    ;%CallFunction%(TheClone, MainObject, DeepCopy)

    return TheClone
}


bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
The first thing I see is that you need to set your builtInSize in your constructor function.

Currently the static builtInSize = 0, but you should make it 8, to indicate two 4-byte values (b1 and b2).