簡體   English   中英

C#中使用靜態屬性繼承抽象類

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

如果我像這樣實現它,我擔心對於子類model1model2 ,繼承的靜態屬性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-1b2.ID-2c1.ID-1c2.ID-2

首先,我的拙見是實體不應該負責分配自己的唯一標識符。 保持明確的關注點分離。

該游戲中應該有另一個玩家應該分配那些臨時唯一標識符(如果它們是負整數或正整數)。

通常,所謂的其他播放器是存儲庫設計模式的實現,它負責將域(您的模型)轉換為數據的確定表示,反之亦然。

通常, 存儲庫具有添加對象的方法。 這應該是您設置這些臨時標識符的點:

public void Add(Some some)
{
    some.Id = [call method here to set the whole id];
}

並且,大多數存儲庫實現是按實體進行的

  • CustomerRepository
  • InvoiceRepository
  • ...

...但這並不妨礙您定義一個基本存儲庫類,它可以實現處理某些實體類型時可能的共同點:

   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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM