Reputation: 6109
I think its always useful to know the default practices that the language designers intended, and the default practices towards which they target language improvements, even if one later deviates from those conventions. In Scala all fields have to be initialised in the constructor of the class they're declared in. This is a a significant restriction. Secondary constructors are also restricted. Any temporary variables in a constructor need to be placed in an inner method or closure, to avoid unwanted fields, which can make the body of a constructor look messy. All this militates against using constructor bodies. The override val/var syntax needed even when assigning to abstract variables from a super class remove some of the advantage of using derived classes for the sake of construction.
A companion object has access to all the fields of its class. However for construction this is not the advantage that it might first appear, as all fields must be initialised in the constructor of the class. So it would seem the natural practice is to use a method in an object to do any processing of the classes variables, creating a temporary mutable collection for each immutable collection in the class, listbuffer being the default and then pass all the values and collections into a bodiless constructor. The factory can be in any object or even class but might as well be in the companion object unless there's a good reason otherwise. Objects can't take type parameters but their factory methods can if needed. And of course you can have as many factory methods as you need quasi-constructors and they can reuse any common algorithms.
Is this correct?
In response to the request for an example, here's a constructor I'm in the process of porting across to Scala from C#, note the multiple type parameters are gone in Scala:
public class GridC : GridBase<HexC, SideC, UnitC, ISegC>
{
public Geometry<HexC, SideC, UnitC, ISegC> geomC { get; private set; }
internal GridC(Scen scen, int gridNum, int xDim, int yDim, int xOff, int yOff, Terr terr = Terr.Plain):
base(gridNum, scen, 10.0)
{
this.geomC = scen.geomC;
xIntLeft = xOff + 1;
yIntBottom = yOff;
xIntRight = xDim * 2 + 1 + xOff;
yIntTop = yDim * 2 + 2 + yOff;
Coodg hexCoodg;
for (int x = xOff; x < xDim * 2 + xOff; x += 2)
{
for (int y = yOff; y < yDim * 2 + yOff; y += 2)
{
if (x % 4 == y % 4)
{
hexCoodg = new Coodg(num, x + 2, y + 2);
HexC hexC = scen.hexCs.NewHexC(hexCoodg);
SideC sideC;
MiscStrat.sixDirn.ForEach(i =>
{
Coodg sideCoodg = hexCoodg + Cood.DirnTrans(i);
sideC = sides[sideCoodg];
if (sideC == null)
scen.sideCs.NewSide(hexC, i);
else
scen.sideCs.SetHex2(sideC, hexC, i);
});
}
}
}
}
The above sub class is created purely to provide a constructor for the following base class edited just to show the parts relevant to construction;
public class GridBase<HexT, SideT, UnitT, SegT> : IGridBase
where HexT : Hex where SideT : Side where UnitT : Unit where SegT : ISeg
{
public int num { get; private set; }
int IGridBase.num { get { return num; } }
IListsGeom<HexT, SideT, UnitT> iLists;
public HexList<HexT> hexs { get { return iLists.hexs; } }
public SideList<SideT> sides { get { return iLists.sides; } }
public Geometry<HexT, SideT, UnitT, SegT> geom { get; private set; }
public int xIntLeft { get; protected set; }
public int xIntRight { get; protected set; }
public int yIntBottom { get; internal set; }
public int yIntTop { get; internal set; }
public double scale { get; private set; }
protected GridBase(int num, IListsGeom<HexT, SideT, UnitT> iLists, double scale)
{
this.num = num;
this.iLists = iLists;
this.scale = scale;
}
}
The constructor creates a simple uniform Hex grid. Other constructors were required that used a completely different algorithm and others will be created that require a related but more complex algorithms. I'm not an expert but my impression is that factories are used a lot less in C#.
Upvotes: 0
Views: 929
Reputation: 146
About your statement that all fields must be initialized in the constructor of the class: you have two ways to cope with this.
val a: Int = _
Personally, I prefer 2. because it guarantees that an object is semantically complete after construction.
Upvotes: 1
Reputation: 4706
In general, you usually provide complex construction like this through methods on the companion object of your (base) class. It provides a nice clean separation of the initialisation code and the post-construction usage code.
Also, having immutable data may help design smaller, more focused concerns. For instance you have a box (left, right, top, bottom) that you could separate into its own class (or just a tuple if you like).
Upvotes: 1
Reputation: 2832
If I understand your question, I'd say that something that needs a complicated construction method should have apply methods to support that on the companion object. However I'd be concerned that they have such complicated construction requirements.
Upvotes: 2