[英]inheritance of abstract class with static property in C#
简洁版本:
我有一个抽象类A.它有一个方法需要知道特定于每个子类的静态类属性的值。 名称和类型相同,只是每个子类的值可以是唯一的。
我可以在基类A中定义此静态属性,以便能够使用A中定义的方法访问它,但是保持不同子类的属性值不相关吗?
或者我将如何实现这样的东西?
长版:
假设我有一个数据模型的抽象基类。 它有一个公共财产Id
(Int32)。
我想在基类中实现一个构造函数,该构造函数基于最后为子类的对象分配的ID生成新的ID。
原因是实际 ID是由数据库自动分配的,但是每个数据模型对象在构造时必须具有唯一的ID而尚未写入数据库。 由于数据库仅将正整数分配为ID,因此我的计划是为新创建的数据模型对象分配一个临时的唯一否定ID。 一旦对象被写入,ID就会变为真实的ID。
由于我有很多不同的数据模型类都来自我的抽象基类,我认为将那些功能包含在那里以便不重复它会很好。 但是每个子类必须有自己的计数器,指向下一个自由否定ID,因为不同类的ID是不相关的。
所以我需要在每个子类中存储一个静态属性,存储该类的最后一个分配的临时ID,但是分配它的机制总是相同的,并且可以实现到抽象基类的构造函数中。 但是,我无法从必须由子类实现的基类访问属性,这意味着我必须在基类中定义它。 但是这个静态属性对所有子类都是全局的,这不是我想要的吗?
如何以最优雅的方式实现此临时ID计数器?
简化的代码示例:
public abstract class ModelBase
{
public Int32 Id { get; set; }
protected static Int32 LastTempId { get; set; } = 0;
public ModelBase()
{
Id = --LastTempId;
}
}
public class Model1 : ModelBase
{
public Model1 () : base ()
{
// do something model1-specific
}
}
public class Model2 : ModelBase
{
public Model2() : base()
{
// do something model2-specific
}
}
如果我像这样实现它,我担心对于子类model1
和model2
,继承的静态属性LastTempId
将是同一个实例。 但我想为每个子类创建一个单独的计数器,同时仍然在基类构造函数中使用它。
子类不能具有静态属性的不同值,因为静态属性是类的属性,而不是它的实例,并且它不是继承的。
您可以在抽象类上实现一个单独的计数器作为静态属性,并使用它的一个抽象类的构造函数。
编辑 :要为每个子类保存不同的计数器,您可以使用静态字典将Type(子类)映射到计数器。
public abstract class A<T>
{
public static Dictionary<Type, int> TempIDs = new Dictionary<Type, int>();
public int ID { get; set; }
public A()
{
if (!TempIDs.ContainsKey(typeof(T)))
TempIDs.Add(typeof(T), 0);
this.ID = TempIDs[typeof(T)] - 1;
TempIDs[typeof(T)]--;
}
}
public class B : A<B>
{
public string Foo { get; set; }
public B(string foo)
: base()
{
this.Foo = foo;
}
}
public class C : A<C>
{
public string Bar { get; set; }
public C(string bar)
: base()
{
this.Bar = bar;
}
}
B b1 = new B("foo");
B b2 = new B("bar");
C c1 = new C("foo");
C c2 = new C("foo");
b1.ID
为-1
, b2.ID
为-2
, c1.ID
为-1
, c2.ID
为-2
首先,我的拙见是实体不应该负责分配自己的唯一标识符。 保持明确的关注点分离。
该游戏中应该有另一个玩家应该分配那些临时唯一标识符(如果它们是负整数或正整数)。
通常,所谓的其他播放器是存储库设计模式的实现,它负责将域(您的模型)转换为数据的确定表示,反之亦然。
通常, 存储库具有添加对象的方法。 这应该是您设置这些临时标识符的点:
public void Add(Some some)
{
some.Id = [call method here to set the whole id];
}
并且,大多数存储库实现是按实体进行的
...但这并不妨碍您定义一个基本存储库类,它可以实现处理某些实体类型时可能的共同点:
public interface IRepository<TEntity> where TEntity : EntityBase
{
// Other repository methods should be defined here
// but I just define Add for the convenience of this
// Q&A
void Add(TEntity entity);
}
public class Repository<TEntity> : IRepository<TEntity>
where TEntity : EntityBase
{
public virtual void Add(TEntity entity)
{
entity.Id = [call method here to set the whole id];
}
}
...现在任何派生Repository<TEntity>
都能够为其专用实体生成临时标识符:
public class CustomerRepository : Repository<Customer> { }
public class InvoiceRepository : Repository<Invoice> { }
如何将唯一和临时实体标识符实现为抽象存储库类的一部分,并且能够为每个特定实体类型执行此操作?
使用字典将实现属性的每个实体最后分配的标识符Repository<TEntity>
到Repository<TEntity>
:
public Dictionary<Type, int> EntityIdentifiers { get; } = new Dictionary<Type, int>();
...以及减少下一个临时标识符的方法:
private static readonly object _syncLock = new object();
protected virtual void GetNextId()
{
int nextId;
// With thread-safety to avoid unwanted scenarios.
lock(_syncLock)
{
// Try to get last entity type id. Maybe the id doesn't exist
// and out parameter will set default Int32 value (i.e. 0).
bool init = EntityIdentifiers.TryGetValue(typeof(TEntity), out nextId);
// Now decrease once nextId and set it to EntityIdentifiers
nextId--;
if(!init)
EntityIdentifiers[typeof(TEntity)] = nextId;
else
EntityIdentifiers.Add(typeof(TEntity), nextId);
}
return nextId;
}
最后,您的Add
方法可能如下所示:
public virtual void Add(TEntity entity)
{
entity.Id = GetNextId();
}
一种方法是反射,但它需要运行时并且容易出现运行时错误。 正如其他人提到的:你不能强制继承类来重新声明一些静态字段,并且能够在祖先类中使用这个字段。 所以我认为最小的代码冗余是必要的:每个继承类都应该提供它自己的密钥生成器。 这个发生器当然可以保存在类的静态区域中。
(注意这不一定是线程安全的。)
class KeyGenerator
{
private int _value = 0;
public int NextId()
{
return --this._value;
}
}
abstract class ModelBase
{
private KeyGenerator _generator;
public ModelBase(KeyGenerator _generator)
{
this._generator = _generator;
}
public void SaveObject()
{
int id = this._generator.NextId();
Console.WriteLine("Saving " + id.ToString());
}
}
class Car : ModelBase
{
private static KeyGenerator carKeyGenerator = new KeyGenerator();
public Car()
: base(carKeyGenerator)
{
}
}
class Food : ModelBase
{
private static KeyGenerator foodKeyGenerator = new KeyGenerator();
public Food()
: base(foodKeyGenerator)
{
}
}
class Program
{
static void Main(string[] args)
{
Food food1 = new Food();
Food food2 = new Food();
Car car1 = new Car();
food1.SaveObject();
food2.SaveObject();
car1.SaveObject();
}
}
这会产生:
Saving -1
Saving -2
Saving -1
只需在每个对象添加到数据库之前为其生成GUID。 您可以使用isAdded标志告诉您该对象应该被引用为GUID,或者在添加对象后清除GUID。 使用GUID,您永远不必担心两个对象会发生冲突。 此外,它还避免了每个子类需要单独的ID。 我不会像你提议的那样为两个州重复使用相同的属性。
https://msdn.microsoft.com/en-us/library/system.guid(v=vs.110).aspx
好吧,静态类不是继承的,所以这就是out,m并且你不能强制子类实现一个静态方法,所以这也是。
为什么没有可以实现的基本接口,而不是将该方法放在类本身中。 然后你可以有一个可以抽象的实例方法:
public interface IDataModelFactory<T> where T:ModelBase
{
int GetLastTempId();
}
public Model1Factory : IDataModelFactory<Model1>
{
public int GetLastTempId()
{
// logic for Model1
}
}
public Model2Factory : IDataModelFactory<Model2>
{
public int GetLastTempId()
{
// logic for Model2
}
}
或者,如果逻辑对所有类都是通用的,则具有带(或不带)接口的抽象基类:
public DataModelFactory<T> : IDataModelFactory<T>
{
public virtual int GetLastTempId()
{
// common logic
}
// other common logic
}
你甚至可以让工厂单身,所以你不必一直创建实例,它们甚至可以是模型类的子类,因此它们是紧密相连的。
作为旁注,如果您不确定继承/接口关系是什么,我经常会发现复制/粘贴重用更快,重构代码以引入基类和接口。 通过这种方式,您可以了解常用代码的含义,并将其重构为常用方法。 否则,您很想尝试将所有内容放在基类中,并使用开关或其他构造来根据派生类型更改逻辑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.