Constructors
Constructors in Nit behave differently.
Their objective is double :
- be compatible with full multiple-inheritance
- be simple enough to be KISS and compatible with the principle of least surprise.
new
construction and simple classes
Classes in OO models are often a simple aggregates of attributes and methods.
By default, the new
construction requires a value for each attribute defined in a class without a default value.
class Product var id: String var description: String var price: Float end var p = new Product("ABC", "Bla bla", 15.95) assert p.id == "ABC"
In subclasses, additional attributes are automatically collected.
class Product var id: String var description: String var price: Float end class Book super Product var author: String end var book = new Book("ABC", "Bla bla", 15.95, "John Doe")
special init
method
The special init method is automatically invoked after the end of a new
construction.
It is used to perform additional systematic tasks.
Because the init
is run at the end of the initialization sequence, initialized attributes are usable in the body.
class Product var id: String var description: String var price: Float end class OverpricedProduct super Product init do price = price * 10.0 end end var op = new OverpricedProduct("ABC", "Bla bla", 15.95) assert op.price.is_approx(159.50, 0.001)
Uncollected attributes
There are three cases for which an attribute is not collected in a new
construction.
- Attributes with a default value
- Attributes with the annotation
noinit
- Attributes introduced in refinement of classes
class Product var id: String var description: String var price: Float end class TaxedProduct super Product var tax_rate = 9.90 var total_price: Float is noinit init do total_price = price * (1.0 + tax_rate100.0) end end var tp = new TaxedProduct("ABC", "Bla bla", 15.95) assert tp.total_price.is_approx(17.52905, 0.00001)
Note: The orchestration here is important. In order, the following is executed:
- All defauts values are computed and set
- Setters are invoked.
init
is invoked.
Therefore, total_price
cannot be initialised with a default value, because at the time of the computation of the default values, the attribute price
in not yet initialised.
Generalized initializers
Initializers are methods that are automatically invoked by new
.
In fact, by default, the setter of an attribute is used as an initializer.
autoinit
is used to register a method as a setter.
class Product var id: String var description: String var price: Float end class FooProduct super Product fun set_xy(x, y: Int) is autoinit do z = x * 10 + y var z: Int is noinit end var fp = new FooProduct("ABC", "Bla bla", 15.96, 1, 3) assert fp.z == 13
Generalized setters are a powerful tool, but only needed in rare specific cases.
In most cases, there is no reason for an argument of a new
construction to not be stored in the object as a real attribute.
Inheritance
As explained above, one of the main advantage of these constructors is their compatibility with multiple inheritance.
class Product var id: String var description: String var price: Float end class OverpricedProduct super Product init do price = price * 10.0 end end class TaxedProduct super Product var tax_rate = 9.90 var total_price: Float is noinit init do total_price = price * (1.0 + tax_rate100.0) end end class FooProduct super Product fun set_xy(x, y: Int) is autoinit do z = x * 10 + y var z: Int is noinit end class MultiProduct super OverpricedProduct super TaxedProduct super FooProduct end var mp = new MultiProduct("ABC", "Bla bla", 15.96, 1, 3) assert mp.id == "ABC" assert mp.price.is_approx(159.6, 0.001) assert mp.total_price.is_approx(175.4, 0.001) assert mp.z == 13
Named init
Named init
are less flexible trough inheritance, thus should no be used.
They allow to have additional constructor for classes and more control in the construction mechanism.
class Point var x: Float var y: Float init origin do init(0.0, 0.0) end init polar(r, phi: Float) do var x = r * phi.cos var y = r * phi.sin init(x, y) end redef fun to_s do return "({x},{y})" end var p1 = new Point(1.0, 2.0) assert p1.to_s == "(1.0,2.0)" var p2 = new Point.origin assert p2.to_s == "(0.0,0.0)" var p3 = new Point.polar(1.0, 2.0) assert p3.to_s == "(-0.416,0.909)"
Legacy init
nameless init
defined with argument or with an explicit visibility are still accepted as a fallback of the old-constructors.
They should not be used since they will be removed in a near future.
new
factories
new
factories allow to completely shortcut the class instantiation mechanism.
It could be used to provide new
syntax on non-concrete class (mainly extern class
).
new
factories behave like a top-level function that return the result of the construction.
It is basically some kind of syntactic sugar.
abstract class Person var age: Int new(age: Int) do if age >= 18 then return new Adult(age) else return new Child(age) end end end class Adult super Person # ... end class Child super Person # ... end