Class variables and methods
It is often useful to have class variables and methods. These are for
functionality that is common to all objects created from the class, but not
related to one specific object. For example: A method to create a Date object
by parsing a String such as "Feb 12 2009".
In Java these are defined with "static". These do not have access to object
variables and methods, since they are not associated with any specific object.
Thus they actually live in a completely different scope. Mixing static
(class) and non-static (object) items causes confusion. When instantiating an
object from a class all the static items are ignored. It messes up the
meaning of the items in a class. Many Java teachers need to spend time
on explaining this to students.
One could say that class variables and methods make the class itself an
object. Would it be a solution to avoid classes and only use objects? This
means one would make a new object by copying the declared object. Every
object then has a reference to the class methods. That only makes things
more complicated, especially for class variables.
Alternative: Instantiate a class. That also allows having multiple of them.
We should call it something else then. ObjectClass? ClassObject? This
introduces a new mechanism, while it's not really needed. One can always
put everything in an object. So let's not do this.
Observation: A class is to make objects from. It cannot contain global
variables (static in Java). The class variables and methods do have a
relation with the objects, so they need to be defined together somehow.
Thus let's keep the class restricted to variables and methods for the object,
and put the rest in the same scope but outside of the class.
1. Put methods and variables in a namespace.
CLASS Date
int time
string toString()
...
}
}
NAMESPACE DateFactory
Date dateFromString(string entry)
...
}
}
...
Date d = DateFactory.dateFromString(line)
+ Clear separation.
- Need two names, even though the two things are bound very tight.
1a. Like 1, but give the namespace the same name as the class.
+ Clear separation.
- When using Date.abc it's not clear what Date refers to.
2. Use one name for both class and namespace. Keep all items together in
one module.
MODULE Date
FUNC dateFromString(string entry): Date
...
}
CLASS Date
int time
FUNC toString(): string
...
}
}
...
}
...
Date d = Date.dateFromString(line)
+ Somewhat clear separation.
+ Need only one name.
+ Less confusing than Java (mixing static and non-static methods)
+ No need for a class that can't be instantiated.
o Having one name for class and module may cause a bit of confusion.
The compiler will be able to give good error messages though.
- When extending the class, what happens with the module members?
- In a large module it can be difficult to find the class with the
same name.
3. Like 2, but put MODULE and CLASS at the same level. Classes defined in
the MODULE are local to that module scope.
The compiler checks that an imported file only defines one identifier at
the toplevel, it can be a MODULE, a CLASS or both.
MODULE Date
FUNC dateFromString(string entry): Date
...
}
CLASS Month
...
}
}
CLASS Date
int time
Month month
FUNC toString(): string
...
}
+ It's easier to locate the Date class, it can't be halfway the module.
- The scope of a local class, "Month" in the example, is messy.
- When extending the class, what happens with the module members?
4. Like 2, but require that the class is the first item in the module.
MODULE Date
CLASS Date
int time
FUNC toString(): string
...
}
}
FUNC dateFromString(string entry): Date
...
}
...
}
...
Date d = Date.dateFromString(line)
+ All the advantages of 2.
o Having one name for class and module may cause a bit of confusion.
The compiler will be able to give good error messages though.
+ In a large module it is immediately clear whether a module has a
class with the same name.
- When extending the class, what happens with the module members?
5. Turn it around, use the class as the highest level. Put members shared between
all objects in a shared section, which must come last:
CLASS Date
int time
FUNC toString(): string
...
}
SHARED
FUNC dateFromString(string entry): Date
...
}
...
}
}
...
Date d = Date.dateFromString(line)
+ Clear separation between instance and non-instance members.
+ Need only one name, no confusion.
+ Less confusing than Java (mixing static and non-static methods)
+ No need for a class that can't be instantiated.
+ When extending the class, it's clear that the shared section is included.
+ A shared section can be added to a class later without other changes.
- In a large class it can be a difficult to see if a line is in the shared section.
6. Put items shared between objects at the same level as the class, prepend the class name:
CLASS Date
int time
FUNC toString(): string
...
}
}
FUNC Date.dateFromString(string entry): Date
...
}
...
Date d = Date.dateFromString(line)
+ advantages from 5.
+ easy to see that an item is not part of the object.
+ item name at definition same as where it's used.
- private class members are visible to shared items, this isn't clear from the layout.
- need to repeat the class name many times.
7. Like 5, but mark all member variables and methods with $. Here $ is (more or less)
short for "THIS.".
CLASS Date
int $time
FUNC $toString(): string
RETURN $time.toString()
}
SHARED
FUNC dateFromString(string entry): Date
...
}
...
}
}
...
Date d = Date.dateFromString(line)
+ advantages from 5.
+ easy to see that an item is part of the object and not in the SHARED section.
+ object members clearly stand out from arguments and local variables.
+ allows using function arguments with the same name as an object member:
PROC $setTime(int time)
$time = time
}
- Have to type $ quite often
8. Like 7, but allow using SHARED multiple times. This is to allow for class members
to be located close to the methods where they are used. It is still possible to put
common functionality at the end of the class.
CLASS Date
FUNC $monthName(int month) string
RETURN monthList[month]
}
SHARED
list<string> monthList = ["Jan", "Feb", "Mar" ... ]
}
FUNC $weekdayName(int day) string
RETURN weekdayList[day]
}
SHARED
list<string> weekdayList = ["Monday", "Tuesday" ... ]
}
}
Choice: 8. This matches with how inheritance works and works best in practice.
Shadowing members of the SHARED section
When nesting classes, each class can have its own SHARED section.
What if the same variable or method appears in both?
Example
CLASS Outer
int $someNumber
CLASS Inner
int $someNumber
SHARED
FUNC createFromString(string s): Inner
Inner i = NEW()
i.someNumber = s.toInt()
RETURN i
}
}
}
SHARED
FUNC createFromString(string s): Outer
Outer o = NEW()
o.someNumber = s.toInt()
RETURN o
}
}
}
The Inner.createFromString() shadows the Outer.createFromString()
1. Allow shadowing. When the Inner.createFromString() method would
not be there then the Outer.createFromString() will be used in the
Inner class.
- In a large class it becomes unclear what method is invoked, this leads to errors.
2. Disallow shadowing. In the above example Inner.createFromString()
cannot be declared.
- Often use method names cannot be used, some undesired name has to be used.
3. An inner class cannot use members of the SHARED section of an outer class
directly, it must be prepended with the class name.
+ directly clear what method is used
o slightly longer names in rare cases
Choice: 3.