Rich Oliver
Rich Oliver

Reputation: 6109

Scala the default way to create objects that require a complex construction process

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

Answers (3)

hiltym
hiltym

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.

  1. Set the more complicated fields to a default value in the constructor of the class, e.g. val a: Int = _
  2. Do the calculations in the apply method of the companion object and initialize all fields in the constructor (i.e., pass all those pre-computed values to the constructor)

Personally, I prefer 2. because it guarantees that an object is semantically complete after construction.

Upvotes: 1

Jed Wesley-Smith
Jed Wesley-Smith

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

Sean Parsons
Sean Parsons

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

Related Questions