05/19/14, 03:06 AM | #1 |
|
The getset (gs) pattern
Ever since I started learning the WPF GUI System for .NET I have become a big friend of having get and set functions instead of directly exposed fields. In .NET get&set have been combined into the (almost entirely synrax sugar) construct of properties wich make setting them up a breeze.
They allow stuff like Validation and Change Notification to be in one place (wich in turn really simplifies GUI programming). The only thing you have to keep in mind are to never write the backing field directly (especially in the code that contains the property/get & set). Latest when you work with LibAddonMenu you need a getter and setter too. So my first ones looked like this: Code:
--Version 1, conventional get and set local function getSomeValue() return SomeValue end local function setSomeValue(value) SomeValue = value end Code:
--Version 2 local function gsSomeValue(value) if (value ~= nil) then SomeValue = value else return SomeValue end end This has one disadvantage though: nil values are impossible to set anymore (wich may be a good or bad thing, depending on case). After some thinking I realised that with Input Validation a immediate check of the real value would be needer after every set. So I put the return outside of the if to avoid having to make an extra function call: Code:
--Version 3 local function gsSomeValue(value) if (value ~= nil) then SomeValue = value end return SomeValue end Code:
local value = --some String input that has to be parsed in the setter value = gsSomeValue(value) Last edited by zgrssd : 05/19/14 at 03:09 AM. |
05/19/14, 03:40 AM | #2 |
Hey,
I do like to use getters and setters while writing code in objective launguages which offer some kind of encapsulation. But I don't use them in lua because I think it's just a syntax sugar and accessing a variable directly is faster than calling a function. In launguages such as c++ compiler can optimize such functions calls but in lua the function is called every time so it costs more. But that is just my opinion |
|
05/19/14, 01:22 PM | #3 |
|
Yeah, I too like getters and setters in object-oriented languages, and when forcing procedural languages to be object-oriented (PHP anyone?). I find they add clarity in the latter situations. So I use them some places in lua an not in others.
|
05/20/14, 05:03 AM | #4 |
|
In Lua you are either forced to use Local variables (wich is like Private in other languages) or risk colliding with other peoples code in the Global Variable Space.
I use this Namespace pattern to explictly expose stuff that should be global: Code:
--A bunch of local functions MyAddonNamespace = { foo = foo, --assign local fucntion to an entry on the Global table variable gsXIsEnabeled = gsIsEnabeled --assing getsetter to this public entry } --Some say this extra return is good practice, so I just keep it in return MyAddonNamespace Speed is a consideration, but among the first things I had to accept as programmer is that sometimes ease of use and productivity takes precedence over squeezing out the last ounce of speed. Sure it is slower, but it is still fast enough to be used. See part 2 here: http://ericlippert.com/2012/12/17/performance-rant/ |
05/20/14, 10:27 AM | #5 |
Code:
local value = --some String input that has to be parsed in the setter value = gsSomeValue(value) If I came across this function in the wild, it would make no sense at all. Ok, I can understand, to a certain degree, encapsulation on a theoretical level, but if it overcomplicates the entire process, it's not actually helping, only hindering. How is this in any way beneficial to the system? How is this easier to use than stripping it down to simple basics? |
|
05/20/14, 12:03 PM | #6 | |
|
If you set a value you know might be reinterpreted you have to check what the values is after the tried assignment. I just cut out a extra call to getSomeValue() or gsSomeValue by always letting the getsetter return the value and storing it directly (so it can be re-exposed to the UI). |
|
05/20/14, 05:51 PM | #7 |
Ah, I see.
Well, if preventing the programmer from being able to access/adjust values as they see fit was the aim, then 90% of the addons on this site would not work =) If every addon had perfectly hidden and controlled values, we'd never be able to change stuff we like. Having that sort of control is well and good if you -want- perfect and inviolable control over the UI. Thankfully ZO isn't quite that shortsighted. Leaving values available to be adjusted means that they can offer an API they don't have to work too much on, and meanwhile everyone else is happy, because they can change the things they want. I suppose it's more of an environmental consideration for me. At work, sure. Keep everything encapsulated, set aside, locked off from 'silly programmers'. In game? If they want to change stuff, let them. If they want to screw up setting a value? Go right ahead. It's a learning experience then, when the users come to them and say "Your addon is clashing with this one", and they go to see what went wrong. Many people are only just starting to learn how to code here. Preventing them from making simple mistakes where it just -doesn't matter- isn't helping them learn. Giving them a sandbox to play in where doing something wrong isn't causing real life downtime, or costing a company valuable money, is important imo, as it gives them the scope to try things out without serious consequence. They can learn the serious stuff later if they ever take their skills into the professional arena. But until then, this is still a game =) |
|
05/28/14, 03:31 AM | #8 |
|
My ultimate goal was to fully copy the functionality of the MVVM pattern to Lua. Having getter and setter folded into on function was the first part of it. Events was the second.
You see, most UI design patterns ask "How do I inform the UI of changes to my values", wich involves taking a reference to the UI. And also means that a different UI will be hard to do. MVVM says "That is a stupid question. I don't care for the design of the UI." Instead it says: "I only expose properties. I have change notification on those properties. Everyone who has a stake in the value of the property, can just register the change event. I do not need to know squat about the UI this way, meaning that any UI can work with my backend." Code:
--Create your local CallbackManager and a helper function to raise the events local LocalCallbackManager = ZO_CallbackObject:Subclass() --Backing variable for gsSomeValue, you might use a Setting Accessor Object or something similar as backing field instead. A string indexed table might be a good place to hold them while also allowing simplyfying the gs code. local _SomeValue = nil local function RaiseGSChanged(gsName, newValue) LocalCallbackManager:FireCallbacks(gsName .. "Changed", newValue) end local function gsSomeValue(value) if(value ~= nil and value ~= _SomeValue) _SomeValue = value RaiseGSChanged("gsSomeValue", _SomeValue) end return _SomeValue end --think a dozen more gs, all using this pattern being written here --Expose local CallbackManager and the getsetters ChangeNofiticationExample = { gsSomeValue = gsSomeValue CallbackManager = LocalCallbackManager } It just became unessesary to do expensive polling to get changes of the value. You register the event. You get informed. No mater where the change came from (as long as the original programmer always used the gs in his code too). No mater if you are in the same code, or code in a totally different addon. Or if you were written 10 years after the original addon. |
05/28/14, 04:28 AM | #9 |
|
The main intent of the "Does it need to be optimised?" part is you don't go making your code more complex (and error-prone) to get a little bit of optimization. What do you gain by combining your get/set functions besides making it needlessly complex? KISS - Keep it simple stupid.
Even though you don't get the full read-only guarantees on getters like you can in C++, it makes your API clearer. You can easily say "The get function has no side effects." For example, what happens if you want to actually use NIL as a value? There is no way to signal this to your getset combined function. What's worse is it will happily return a value silently and proceed on its way with no warnings. If you get a NIL passed in, you'll only see the results later of a NIL not being stored (not in a clean error message) and it could be a difficult bug to notice and track down. (PS: Doesn't mean you should abandon a MVVM pattern, just keep it with traditional get/set functions.) |
05/29/14, 03:51 AM | #10 |
|
This is quite common in Javascript (JQuery uses it a lot). Not a big fan of "gs" as it's a bit redundant - if you want to allow people to set properties to nil you can use varargs (which, again, is how you generally use this pattern in Javascript):
Code:
function SomeValue(...) if arg.n == 1 then _someValue = arg[1] end return _someValue end |
05/29/14, 04:28 AM | #11 | |
If you want the same functionality, you have to define arg first: Lua Code:
|
||
05/29/14, 04:51 AM | #12 |
|
Gotta love those Lua docs! So, in the spirit of DRY (donot repeat yourself):
Code:
local function Property(get, set) local prop = function(...) if select('#', ...) == 1 then set(({...})[1]) end return get() end return prop end local someValue local SomeValue = Property( function() return someValue end, function(value) someValue = value end ) SomeValue(1) print(SomeValue()) Last edited by zamalek : 05/29/14 at 05:05 AM. |
07/01/14, 05:22 AM | #13 |
|
getSet Pattern Mk.2 - now with Metatables.
Finally got around to properly reading into thier basics. With Metatables I am able to set up functions to be called in case a index is not set(metamethod __index). I can also set up a function to do work if somebody tries to set a index that does not exist yet (metamethod __newindex) With those two I can get a lot closer to simulating Properties in Lua, while even retaining all the syntax sugar of them (that they can be accessed like other fields). I just redirect every get and set attempts to my functions, preventing the table value from ever being set. Code:
--accessing a index that was never defined reading Instance["SomeValue"] --Is interpreted like this with a metamethod around: (getMetatable(Instance).)__index(Instance, "SomeValue") Code:
--trying to write a index that currently has no value Instance["SomeValue"] = "Hello World" --Is interpreted like this with a metamethod around: (getMetatable(Instance).)__newindex(Instance, "SomeValue", "Hello World") Of course the whole thing can be circumvented by getting the raw value (while ignoring metamtehods). But if somebody tries that, he or she better knows what the heck they are doing there. |
07/01/14, 09:15 AM | #14 |
|
While I generally agree with Wobin, I thought your quest (the part with restricting variable access) would be a good Lua excersise (for me at least ). So here's my shot on how you can completely hide the stored values from anything but get/set functions.
Lua Code:
And here's how you'd use it: Lua Code:
|
07/01/14, 01:45 PM | #15 |
|
For a more in-depth discussion on the metatables approach, take a look at:
http://lua-users.org/wiki/ReadOnlyTables http://lua-users.org/wiki/ObjectProperties |
ESOUI » Developer Discussions » General Authoring Discussion » The getset (gs) pattern |
«
Previous Thread
|
Next Thread
»
|
Display Modes |
Linear Mode |
Switch to Hybrid Mode |
Switch to Threaded Mode |
|
|