为不可变类型层次结构定义通用初始值设定项
我想为一组“记录”类型定义一个接口和抽象基类。记录通常传递到另一个系统或从另一个系统传递,该系统将记录表示为字符串。因此,每个记录类都需要一个 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
的默认构造函数不是公开曝光。
所以我的问题是:
- 是否有更好的设计可以让我拥有不可变的记录类型以及通用的
Parse
初始值设定项?或者尝试在基类级别使用通用初始化 API 创建不可变类型层次结构是否存在固有缺陷?
I'd like to define an interface and abstract base class for a set of "Record" types. Records are generally passed to/from another system that represents the record as a string. Thus, each record class will need a Parse(string str)
method and a ToString()
method. Record classes also need to define methods to compare for equality, as well as a few other common utility methods. A sample record class without a common base might look like:
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) { /* .. */ }
}
It will simplify the usage scenarios if I can design the records as immutable. The above class supports immutability by defining read-only properties, and doing all object initialization in the parameterized constructor or Parse
method. I have specifically not exposed the default constructor.
I'm having trouble applying this design to a base record class that concrete record types can inherit from. Specifically, I need a general Parse
method that can instantiate the derived type, without exposing a default constructor or partially-constructed objects. The design I have so far looks like:
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) { /* .. */ }
}
However the compiler complains when I try to call RecordBase.Parse<MyRecord>(..)
because MyRecord
's default constructor is not exposed publicly.
So my question is:
- Is there a better design that will allow me to have immutable record types as well as a common
Parse
initializer? Or is there an inherent flaw in trying to create an immutable type hierarchy with common initialization APIs at the base-class level?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
为记录添加解析函数时会破坏 SRP。
You are breaking SRP when adding parsing functions for your records.
我认为您正在寻找工厂模式(从数据解析)和奇怪的重复模板模式(用于克隆/工厂能力)。
例如
I think you are looking for a Factory Pattern (parse from data) and the Curiously Recurring Template Pattern (for the clone/factory ability).
E.g.
如果您保证非常小心,您也许可以使用 System.Runtime.Serialization.FormatterServices.GetUninitializedObject。但是,使用此方法需要保证所有 IRecord.Parse 实现都不会期望任何字段(根本)不为 null/0。
If you promise to be very careful, you might be able to use System.Runtime.Serialization.FormatterServices.GetUninitializedObject. However, this using this method will need the guarantee that none of the IRecord.Parse implementations expect any field (at all) to be not null/0.