简短版:
我有一个抽象类A.它有一个方法需要知道特定于每个子类的静态类属性的值。名称和类型相同,只是每个子类的值都可以是唯一的。
我可以在基类A中定义这个静态属性,以便能够使用A中定义的方法访问它,但是保持不同子类的属性值不相关吗?
或者我将如何实现类似的东西?
长版:
让我们说我有一个数据模型的抽象基类。它有一个公共属性Id
(Int32)。
我想在基类中实现一个构造函数,它根据最后为子类对象分配的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
将是同一个实例。但我希望每个子类都有一个单独的计数器,同时仍然在基类构造函数中使用它。
答案 0 :(得分:4)
子类不能具有静态属性的不同值,因为静态属性是类的属性,而不是它的实例,并且它不是继承的。
您可以将抽象类上的单个计数器实现为静态属性,并使用抽象类的一个构造函数。
编辑:要为每个子类保存不同的计数器,您可以使用静态字典将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
为{ {1}}
答案 1 :(得分:4)
首先,我的拙见是实体不应该负责分配自己的唯一标识符。明确区分关注点。
该游戏中应该有另一个玩家应该分配这些临时唯一标识符(如果他们是负整数或正整数)。
通常,所谓的其他播放器是repository design pattern的实现,它负责将域(您的模型)转换为数据的确定表示,反之亦然。
通常repository有添加对象的方法。这应该是您设置这些临时标识符的地方:
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>
:
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();
}
答案 2 :(得分:1)
一种方法是反射,但它需要运行时并且容易出现运行时错误。正如其他人提到的:你不能强制继承类到 redeclare 一些静态字段,并且能够在祖先类中使用这个字段。所以我认为最小的代码冗余是必要的:每个继承类都应该提供它自己的密钥生成器。这个发生器当然可以保存在类的静态区域中。
(注意这不一定是线程安全的。)
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
答案 3 :(得分:0)
在将每个对象添加到数据库之前,只需为其生成一个GUID。您可以使用isAdded标志告诉您该对象应该被引用为GUID,或者在添加对象后清除GUID。使用GUID,您永远不必担心两个对象会发生冲突。此外,它还避免了每个子类需要单独的ID。我建议不要在两个州重复使用相同的属性。
https://msdn.microsoft.com/en-us/library/system.guid(v=vs.110).aspx
答案 4 :(得分:-1)
嗯,静态类不是继承的,所以这就是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
}
你甚至可以让工厂单身,所以你不必一直创建实例,它们甚至可以是模型类的子类,因此它们是紧密相连的。
作为旁注,如果您不确定继承/接口关系是什么,我经常会发现复制/粘贴重用更快,重构代码以引入基类和接口。通过这种方式,您知道公共代码是什么,并且可以将其重构为常用方法。否则,您很想尝试将所有放在基类中,并使用开关或其他结构根据派生类型更改逻辑。