Az elastiC objektum orientáltságának alapja az osztály, amit a class kulcsszó vezet be.
SCOPE class NAME extends SUPERCLASS
{
CLASS_STATEMENT
...
}
Ezzel a szintaxissal hozhatunk létre egy új objektummintát. Egy osztály láthatósága SCOPE ugyan úgy kétféle lehet, mint az alprogramoknál láttuk, és a jelentéstartama is ugyanaz, tehát egy public kulcsszóval deklarált osztály más csomagokból is látszik, belőle bárhol példány hozható létre, ahol az import részben felsorolták azt a csomag nevét, amelyben az osztály deklarálva van. Ezzel szemben a private osztályok csak a saját csomagjukon belülről láthatóak.
Bármely változó, amit az osztály törzsében hozunk létre, private változó lesz, és így csak az osztály belsejében látszik. A külvilág csak metódusokon keresztül érheti el, és módosíthatja ezeket. Kétféle módosítószóval deklarálhatunk az osztály törzsében változókat. A statikus változókat static kulcsszó segítségével hozhatunk létre, ezekből nem jön létre új példány az osztály példányosításakor, de minden ilyen típusú objektum elérheti, és módosíthatja őket, ezek a változók az osztály részét képezik. A másik lehetőség a példányváltozó, amelyet a local foglalt szó vezet be. Ezekből a változókból minden példány saját egyeddel rendelkezik. És ezeket a változókat csak az adott példány metódusai érhetik el, az osztály szintű metódusok nem. Adattagoknak értéked adni, csak metódusokon keresztül lehet, tehát az alábbi kód hibás.
public class Minden extends basic.Object
{
local valasz = 42; //hibás értékadás
}
Létezik minden objektumnak két előre definiált adattagja, az önmagára mutató referencia a self, és a közvetlen ősre mutató referencia a super.
A metódusoknak ahogyan az adattagoknak is két fajtája van. Ellentétben a globális függvényekkel, és a C/C++-al itt a method kulcsszó vezeti be az osztály objektumaihoz tartozó műveletek definícióját. Az osztályszintű műveletek pedig a class módosítószóval kell ellátni.
public class Valami extends basic.Object
{
//...
method Alma() //példány szintű metódus
{
}
class method StaticAlma() //osztályszintű metódus
{
}
//...
}
A metódusok láthatósága az adattagokkal ellentétben mindig publikus. A metódusok paraméterlistájának megadására kétféle szintaxist biztosít a nyelv. Az első módszer megegyezik a globális függvényeknél látottaknál, és az előző példában is ezt a szintaxist láthattuk. A második lehetőség bemutatására álljon itt egy újabb példa.
method drawLineFrom: pointA To: pointB
{
}
a metódus hívása:
[obj moveToX: 30 Y: 20];
Ebben a deklaráció a következővel ekvivalens, de kifejezőbb annál.
method drawLineFrom:To:(pointA, pointB)
{
}
a metódus hívása:
[obj moveTo 30, 20];
Mindkét módszer esetében használhatjuk a változó hosszú paraméterlista jelölésére a ... konstrukciót. Ezek a meglátások mind érvényesek a class method kulcsszó párossal bevezetett osztály szintű metódusokra is.
A metódushívások egymásba ágyazhatóak:
[[obj getMainObject] moveToX: 30 Y: 20];
vagy pl.
[obj moveToX: 30 Y: [obj getCurrentY]];
A metódusokban meghívatunk más metódusokat is ugyanazon az objektumon a self használatával:
method draw() { // a keret kirajzolása [self drawBorder]; // a belsejének kirajzolása [self drawInside]; }
A super kulcsszó segítségével az is lehetséges, hogy a szülőosztály metódusait hívjuk meg:
method draw() { // a szülő osztály draw metódusának meghívása: [super draw]; // az egyedi dolgok kirajzolása ... }
Megjegyezzük, hogy ha ebben a példában self -et használunk a super helyet, akkor egy végtelen rekurzióba kerülünk!
Minden osztályból a new kulcsszó segítségével hozhatunk létre új objektumpéldányt. Ilyenkor létrejön egy új objektum, majd lefut az osztály konstruktora, ami nem más, mint egy init nevű metódus, ha mi nem írtunk ilyet, a fordító létrehoz egyet üres paraméterlistával és üres törzzsel. Lehetőség van tetszőleges paraméterlistájú init írására, de minden osztály csak egy konstruktort tartalmaz, mert a nyelv a metódusok túlterhelését sem támogatja. Egy objektum műveletének meghívása "üzenetküldéssel" történik, amit a következő példa jól illusztrál.
public class Point extends basic.Object
{
method init()
{
x = 0;
y = 0;
}
method MoveTo(a,b)
{
x = a;
y = b;
}
local x;
local y;
}
private point = [new Point];
[point MoveTo 1, 2];
A nyelv lehetőséget biztosít az operátorok túlterhelésére az osztályokban, mégpedig úgy, hogy a basic.Object osztályban definiált, az egyes operátoroknak megfelelő függvényeket felüldefiniáljuk.
public class Hash extends basic.Object
{
method _getitem( key )
{
//...
}
method _setitem( key, val )
{
//...
}
}
private ht = [Hash new];
ht["Hello"] = "World"; //_setitem(key, val)
basic.print( ht["Hello"], '\n' ); //_getitem(key)
Minden osztály közvetve vagy közvetlenül a basic.Object osztály leszármazottja. Minden osztálydeklarációnál kötelező feltüntetni az ősosztályát, ha ilyen nincs, akkor az basic.Object-et. A nyelv csak egyszeres öröklődést tesz lehetővé, és nem támogatja az interface képzést. A nyelvben nem kell jelölni azokat a tagfüggvényeket, amelyeket később felül akarunk definiálni, ebből következik, hogy bármely ősosztálybeli függvény felüldefiniálható egy leszármazottban. A jobb szemléltetés végett álljon itt a megszokott alakzatos példaprogram az öröklődésre.
package shapes;
import basic;
import array;
public class Shape extends basic.Object
{
// instance variables
local name;
local color;
// methods
// a method with keywords
method setName: shapeName andColor: shapeColor
{
name = shapeName;
color = shapeColor;
}
// a method with function-like name
method print()
{
basic.print( [[self isA] name], "\n",
" name : ", name, "\n",
" color: ", color, "\n",
" area : ", [self getArea], "\n" );
}
}
public class Rectangle extends Shape
{
local w, h;
// initializer (called by new)
method init( name, color, width, height )
{
[super setName: name andColor: color];
w = width;
h = height;
}
method getArea()
{
return w * h;
}
}
public class Circle extends Shape
{
local r;
// initializer (called by new)
method init( name, color, radius )
{
[super setName: name andColor: color];
r = radius;
}
method getArea()
{
return 3.1415 * r * r;
}
}
public class Square extends Rectangle
{
// initializer (called by new)
method init( name, color, side )
{
[super init name, color, side, side];
}
}
// a Shape container
public class ShapeBag extends basic.Object
{
local bag;
// initializer (called by new)
method init()
{
// an empty array
bag = #[];
}
method addShape( shape )
{
array.push( bag, shape );
}
method print()
{
basic.print( "*** Shape Bag: ***\n\n" );
local shape;
local totalArea = 0;
for (shape in bag)
{
[shape print];
totalArea = totalArea + [shape getArea];
}
basic.print( "Total area: ", totalArea, "\n" );
}
}
// main code
private bag = [ShapeBag new];
[bag addShape [Rectangle new "r1", "red", 30, 20]];
[bag addShape [Rectangle new "r2", "green", 10, 15]];
[bag addShape [Square new "s1", "blue", 10]];
[bag addShape [Circle new "c1", "brown", 10]];
[bag addShape [Circle new "c2", "yellow", 15]];
[bag print];