14.6 Object orientation. 4tH 4tH Forth compiler
تبلیغات
تبلیغات
CHAPTER 14. PREPROCESSOR LIBRARIES
14.6
Object orientation
245
We already told you that C++ started its life as a mere preprocessor. In 4tH, all that is required to achieve almost full object orientation is a few humble preprocessor macro’s.
If you don’t like object orientation, that’s fine. You take the blue pill, the story ends, you wake up in your bed and believe whatever you want to believe. But if you take the red pill, you stay in Wonderland, and I show you how deep the rabbit hole really goes.
Object orientation comes with an abstract terminology that is incomprehensible to mere humans, but scratch away the shiny surface and you’ll understand what it’s all about. Stay with me.
14.6.1
Encapsulation
Object orientation defines encapsulation as ”a language construct that facilitates the bundling of data with the methods operating on that data”. So what are we actually talking about.
Let’s start with a structure that holds the data. Now add a few fields that hold execution tokens and you’re done. That’s your ”class” definition.
Building an object is nothing more than allocating storage space for your ”class”. You can reserve that space in static or dynamic memory. That’s the easy part. The problem is, the fields for our execution tokens are still empty and when you ”instantiate” an object, you want these execution tokens to be initialized. The dirty trick is, you have to do that secretly
- in true object oriented tradition.
In 4tH you define a class with a ”double colon” definition. A class consists of three parts, the data definition, the ”binding” of the ”methods” and information hiding, e.g.
:: Account class ffield: accountBalance virtual: CheckBalance end-class {
\ data definition
\ data initialization
;
500 s>f this -> accountBalance f!
:virtual CheckBalance { this -> accountBalance f@ } ;method
} private{ accountBalance }
\ method binding
\ information hiding
You can only use cells in the data definition. Sorry, you have to store your strings somewhere else. A data definition is almost identical to defining a structure, the only thing is you have to do it within the ”double colon” definition. Object orientation also requires a thing called ”open recursion”, which is nothing more than a ”this” or ”self” variable, which just holds the address of the ”object”. That is where the curly braces come in.
The curly braces puts the address (that is always on the top of stack when an object is invoked) on the return stack. Yes, you can still use the return stack, just think of the ”THIS” variable between two curly braces as the ’I’ in a ’DO..LOOP’ and you’ll be fine.
The body of a ”double colon” definition is actually executed when an object is created, so the floating point ”property” (we call it a field) ”accountBalance” is set to 500. The colon definition that starts with ”:VIRTUAL” is just a ’:NONAME’ definition, which leaves an execution token. ;METHOD” on its turn pokes this execution token into the equivalent
”VIRTUAL:” field of the ”class”. That’s it. We’re done, now let’s use it.
fvariable myBalance instance Account myAccount
\ define an FP variable
\ create an Account object
CHAPTER 14. PREPROCESSOR LIBRARIES 246
The ”INSTANCE” word creates an object on the heap. Well, not just that; it also secretly calls ”Account”, so the object is properly initialized. It also passes the address of the newly created object to ”Account”, so it knows what to initialize. The curly braces in ”Account” make sure that ”THIS” works.
myAccount => CheckBalance myBalance f! \ now pass a message myBalance f@ f. cr \ and store the result
You pass a message to an object by using the ”=>” operator. You can also use an ”adjective”
5
. E.g. you can write the same piece of code like this if you want: myAccount -> virtual CheckBalance myBalance f!
myBalance f@ f. cr
If you use ’->’ on its own you’ll just end up with the address of the methods field. When ”a message is passed”, a ”method is invoked”. ”Passing a message” means calling the object by name and passing all parameters. The object retrieves the reference of the actual method and executes it: that’s the ”invocation of a method”. In 4tH, ”methods” can be ”virtual”, which means you can store another execution token in that field at any time.
When a virtual ”method” is ”invoked”, 4tH secretly passes the address of the object, as a matter of fact right on top of the stack. That’s why the method itself has curly braces as well - to store the address. Now it starts making sense:
• The opening curly brace puts the address on the return stack;
• ”THIS” holds that address, so now ”accountBalance” points to the right place;
• The closing curly brace clears the return stack;
• The data stack was uncluttered the whole time.
I told you folks, it’s all smoke and mirrors. 4tH also supports static ”methods”, which means you can’t change them later on and they’re automatically passed to all subclasses.
We could rewrite our previous example like this:
:: Account class ffield: accountBalance method: CheckBalance end-class {
\ data definition
\ data initialization
;
500 s>f this -> accountBalance f!
:method CheckBalance { this -> accountBalance f@ } ;method
} private{ accountBalance }
\ method binding
\ information hiding
You cannot invoke static methods with the ”=>” operator, you’ll have to use ”->” instead.
Why? Well, a static ”method” is just an ordinary definition, so when you call it, the address of the object is consumed immediately. It is not expanded to an address to the ”method” field itself. Worse, static ”methods” do not even need a METHOD:” declaration, since they’re never changed and consequently don’t require any space. It’s just a little syntactic sugar that does absolutely nothing.
5
Yes, I know: normal people would call it a cast, but allow me to introduce another, completely superfluous concept to the already incomprehesible object orientation vocabulary.
دانلود
تبلیغات