0

我认为了解语言设计者想要的默认实践以及他们针对语言改进的默认实践总是有用的,即使后来偏离了这些约定。在 Scala 中,所有字段都必须在声明它们的类的构造函数中初始化。这是一个重要的限制。辅助构造函数也受到限制。构造函数中的任何临时变量都需要放在内部方法或闭包中,以避免不需要的字段,这会使构造函数的主体看起来混乱。所有这些都不利于使用构造函数体。即使从超类分配给抽象变量时也需要覆盖 val/var 语法,这消除了为构造而使用派生类的一些优势。

伴随对象可以访问其类的所有字段。然而,对于构造来说,这并不是它可能首先出现的优势,因为所有字段都必须在类的构造函数中初始化。所以看起来很自然的做法是使用对象中的方法对类变量进行任何处理,为类中的每个不可变集合创建一个临时可变集合,listbuffer 是默认值,然后将所有值和集合传递到一个无形的构造函数。工厂可以在任何对象甚至类中,但也可以在伴随对象中,除非有充分的理由。对象不能接受类型参数,但如果需要,它们的工厂方法可以。当然,你可以拥有尽可能多的工厂方法,因为你需要准构造函数,它们可以重用任何常见的算法。

它是否正确?

为了响应示例的请求,这是我正在从 C# 移植到 Scala 的构造函数,请注意多个类型参数在 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);
                        });                                                                         
                }
            }
        }
    }

创建上面的子类纯粹是为了给下面的基类提供一个构造函数,只是为了展示与构造相关的部分;

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;
    }
}

构造函数创建一个简单的统一十六进制网格。需要使用完全不同算法的其他构造函数,并且将创建需要相关但更复杂算法的其他构造函数。我不是专家,但我的印象是工厂在 C# 中的使用要少得多。

4

3 回答 3

2

如果我理解你的问题,我会说需要复杂构造方法的东西应该有应用方法来支持伴随对象的方法。但是我担心他们有如此复杂的施工要求。

于 2012-05-26T17:55:01.377 回答
1

通常,您通常通过(基)类的伴随对象上的方法提供这样的复杂构造。它提供了初始化代码和构建后使用代码的良好分离。

此外,拥有不可变数据可能有助于设计更小、更集中的关注点。例如,您有一个框(左、右、上、下),您可以将其分成自己的类(或者如果您愿意,也可以只是一个元组)。

于 2012-05-26T22:00:10.820 回答
1

关于您必须在类的构造函数中初始化所有字段的声明:您有两种方法来处理这个问题。

  1. 在类的构造函数中将更复杂的字段设置为默认值,例如val a: Int = _
  2. 在伴生对象的 apply 方法中进行计算并初始化构造函数中的所有字段(即,将所有这些预先计算的值传递给构造函数)

就个人而言,我更喜欢 2. 因为它保证了一个对象在构造后在语义上是完整的。

于 2012-05-27T09:25:38.073 回答