为不可变类型层次结构定义公共初始值设定项

时间:2011-04-22 17:57:38

标签: c# design-patterns immutability

我想为一组“Record”类型定义一个接口和抽象基类。记录通常传递给另一个系统,该系统将记录表示为字符串。因此,每个记录类都需要Parse(string str)方法和ToString()方法。记录类还需要定义比较相等性的方法,以及一些其他常用的实用方法。没有公共基数的示例记录类可能如下所示:

public class MyRecord : IEquatable<MyRecord>
{
    public string FieldA { get; private set; }
    public int FieldB { get; private set; }

    public MyRecord(string fieldA, int fieldB /* .. */) { }
    public static MyRecord Parse(string recordString) { /* .. */ }

    public override string ToString() { /* .. */  }
    public override int GetHashCode() { /* .. */ }
    public override bool Equals(object obj) { /* .. */ }
    public bool Equals(MyRecord other) { /* .. */ }
}

如果我可以将记录设计为不可变的,它将简化使用场景。上面的类通过定义只读属性,并在参数化构造函数或Parse方法中执行所有对象初始化来支持不可变性。我特意没有暴露默认构造函数。

我无法将此设计应用于具体记录类型可以继承的基本记录类。具体来说,我需要一个可以实例化派生类型的通用Parse方法,而不暴露默认构造函数或部分构造的对象。到目前为止我的设计看起来像:

public interface IRecord { /* .. */ }

public abstract class RecordBase : IRecord
{
    public static TRecord Parse<TRecord>(string recordStr)
        where TRecord: RecordBase, new()
    {
        TRecord record = new TRecord();
        record.Initialize(recordStr);

        return record;
    }

    protected abstract void Initialize(string recordStr);
}

public class MyRecord : RecordBase, IEquatable<MyRecord>
{
    public string FieldA { get; private set; }
    public int FieldB { get; private set; }

    public MyRecord(string fieldA, int fieldB /* .. */) { /* .. */ }

    public override string ToString() { /* .. */  }
    public override int GetHashCode() { /* .. */ }
    public override bool Equals(object obj) { /* .. */ }
    public bool Equals(MyRecord other) { /* .. */ }

    protected MyRecord() { }
    protected override void Initialize(string recordStr) { /* .. */ }
}

但是,当我尝试调用RecordBase.Parse<MyRecord>(..)时,编译器会抱怨,因为MyRecord的默认构造函数未公开显示。

所以我的问题是:

  • 是否有更好的设计可以让我拥有不可变的记录类型以及常见的Parse初始值设定项?或者在尝试使用基类级别的公共初始化API创建不可变类型层次结构时是否存在固有缺陷?

3 个答案:

答案 0 :(得分:2)

在为记录添加解析函数时,您正在破坏SRP。

  1. 创建一个单独的解析器类。
  2. 使用属性标记:[ParserFor(typeof(MyRecord))]
  3. 创建ParserService类。
  4. 使用反射在具有ParserFor属性的类型之后扫描所有已加载的程序集。
  5. 使用解析器服务解析并创建所有记录。

答案 1 :(得分:0)

我认为您正在寻找工厂模式(数据解析)和奇怪的重复模板模式(克隆/工厂能力)。

E.g。

abstract class Base<T> : IBase // IBase for common demoninator
{
    abstract T Parse(Stream data);
}

Derived : Base<Derived>
{

}

答案 2 :(得分:0)

如果您保证要非常小心,您可以使用System.Runtime.Serialization.FormatterServices.GetUninitializedObject。但是,使用此方法需要保证所有IRecord.Parse实现都不希望任何字段(根本不是)为null / 0.