Definition:
|
class identifier extends base
{ [(public|private)[:]] ( field | method ) [ ',' ... ] }
end class
-- or --
class|identifier variable ( ';' | '=' ( new() | expr ) ) [ ',' ... ]
-- or --
bool res = class|identifier(object o)
|
Description:
|
Classes can also be extended, to create an inheritance hierarchy, or just simply embedded.
|
pwa/p2js:
|
Not supported.
|
Extending:
|
class politician extends person
string job = "habitual liar"
procedure show()
printf(1,"%s aged %g, %s\n",{this.name,this.age,this.job})
end procedure
end class
politician aj = new(),
mp = new({"boris",3.75})
aj.show() -- average joe aged 35, habitual liar
mp.show() -- boris aged 3.75, habitual liar
When a class(/struct) is extended, all fields are copied along with any defaults and privacy settings.
You can store every politician in a person variable, but not the other way round.
You can store every politician and every person in a class variable, ie the lowest level builtin type.
If you also had class honest_man extends person, a person can hold an honest_man or a politician, but an honest_man
variable cannot hold a politician, and a politician variable cannot hold a honest_man!
In other words variables of the base class (as in "extends base") can hold the extended class[es], but not vice-versa.
|
Embedding:
|
person jrm = new({"jacob",50})
class mogg
person p = jrm
string job = "minister for the early 1800s"
procedure show()
printf(1,"%s aged %g, %s\n",{this.p.name,this.p.age,this.job})
end procedure
end class
mogg m = new() -- or new({jrm,"oaf"}) or new({new("person",{"jacob",50}),"oaf"})
jrm.show() -- jacob, aged 50
m.p.show() -- jacob, aged 50
m.show() -- jacob aged 50, minister for the early 1800s
When a class(/struct) is embedded, unless otherwise specified the default is null and fields require extra subscripting, ie/eg this.p.name vs. this.name.
Note that embedding naturally (/usually) leads to shared references to a single instance; if the above inlined person p = new() it would behave the same,
as opposed to an explicit new() in a constructor routine.
Private fields of embedded classes can only be accessed via explicit getters and setters, rather than anything resembling "friends".
As shown, you cannot override methods in embedded fields, and nothing stops you having a same-named-method at a different level.
(Obviously you could easily declare "class person_with_overidden_show extends person" and extend/embed an instance of that instead.)
Inlining the new() of jrm on the "mogg m =" line would as shown require an explicit "person" (if that’s not an insult).
When a class is extended, all fields are given the inherited defaults, whereas an embedded class gets a null default (assuming nowt like jrm above).
Conversely, extending a person might need the same information updating in more than one place, unlike a single/shared new() being embedded.
You cannot store a mogg in a person variable or vice versa(!), but can store both in their common ancestor, which in this case is the root builtin class.
|
Implementation:
|
As per struct
|
See Also:
|
class
|
Technicalia
|
It is considered bad form to have a class extend a struct or vice versa, however in practice that is not specifically enforced.
It is also questionable as to whether there is any useful value in hierarchies of, and hence extending, plain structs.
Embed (rather than extend) structures when implementing composition, as in "favour composition over inheritance".
There are no static fields: I considered adding them, however an abstract mammal with a static next_uniq_id field, extended
into cats and dogs would (unavoidably) have separate next_uniq_id fields for cats and dogs ...
Much better would be to embed a separate class uniq containing said next_uniq, then it becomes entirely the programmer’s
choice whether to invoke uniq uid = new() just once and set/share that on all cats and all dogs ,
or invoke it twice and that way keep them separate. Obviously either way you need to (lock and) store next_uniq_id in an instance field as
each cat or dog is created, before incrementing (and unlocking) it. If you only need the one, in Phix you may as well store uid in a simple
static (ie file_level) variable or constant, rather than embed the exact same reference to it on each and every instance.
All methods are treated as virtual, ie can be overidden. It may become possible to explicitly specify virtual/final in a future release,
which may also perform additional routine signature and parameter type verification at compile-time (it is currently run-time-only).
You can also, for instance, declare procedure foo(); and get an invalid routine_id(0) error at run-time if it has not been
overridden in that particular derived class/instance. The trailing semicolon is significant: it signals that there is no body, and no
"end procedure", and should generate/store a routine_id of zero. At present, integer foo = 0 would behave identically.
There is no compile-time error should you attempt p.show = 5 in the above, however a subsequent p.show() would trigger a
fatal (but catchable) run-time error. You can copy methods about, just like the integers they really are, by the
simple act of using "()" when invoking and "no-()" to fetch/store. However, if you start trying to copy them between classes then it is,
with regret, you are in that world of pain all on your own, mate, and I wish you all the best of luck, since you’ll need it.
Multiple inheritance is permitted using the obvious extends a,b syntax, alternatively declare separate a and b member fields
for composition.
Note that conflicting field names in the second and subsequent extended classes must have the exact same default, and in fact the compiler
font-end fakes defaults as a means of detecting/rejecting conflicts. Any programmer using multiple inheritance is expected to assume much
responsibility (away from the compiler) for checking any such clashes the compiler misses, especially any dynamically-created ones.
In contrast, any conflicting/duplicate methods are simply overwritten in the order encountered (ditto programmer responsibility).
Phix is steadfastly single-dispatch: there is no problem with having a class-specific or even instance-specific method, but only one: the
idea that you would have(/want) ten dozen potential candidates for a routine (differing only by signature) and get the compiler to pick
one out for you (aka Koenig lookup) is simply not the way Phix does things - for example it has precisely one
sq_add(object, object), not ([u]short/[u]int/[u]long[long]/float/double,<ditto>)
<which be like 100 already> with however many you want or accidentally forgot about quietly and rudely coerced
into casting/rounding/truncation errors, before even starting on either/both parameters being sequence... </rant>
Classes grant Phix object orientated capabilities, however, fear not, you can still write an entire application without being forced to
use them at all, whereas already radicalised readers will no doubt be relieved to hear that object orientated techniques can always be applied,
even in the most awkward and utterly inappropriate of circumstances.
On a personal note, I have always been very sceptical of object orientation, have never seen any proof that it improves productivity
or shortens timescales, but far more importantly I have never seen anything remotely resembling a "good object oriented design",
and have (somewhat reluctantly) reached the conclusion that mythical beast simply does not exist. To clarify, I said and meant design,
not codebase, and I will concede it is Turing complete and can do lots, especially with decent libs, and that a well written IDE with fast
and efficient intellisense is abfab, but again, not what I just asked for. See also my anti-oo rant.
|