[英]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.