Unity應用架構設計(9)——構建統一的 Repository

木宛城主發表於2017-08-10

談到 『Repository』 倉儲模式,第一映像就是封裝了對資料的訪問和持久化。Repository 模式的理念核心是定義了一個規範,即介面『Interface』,在這個規範裡面定義了訪問以及持久化資料的行為。開發者只要對介面進行特定的實現就可以滿足對不同儲存介質的訪問,比如儲存在Database,File System,Cache等等。軟體開發領域有非常多類似的想法,比如JDBC就是定義了一套規範,而具體的廠商MySql,Oracle根據此開發對應的驅動。

Unity 中的Repository模式

在Unity 3D中,資料的儲存其實有很多地方,比如最常見的記憶體可以快取記憶體一些臨時資料,PlayerPrefs可以記錄一些存檔資訊,TextAsset可以存一些配置資訊,日誌檔案可以用IO操作寫入,關係型資料結構可以使用Sqlite儲存。Repository 是個很抽象的概念,操作的資料也不一定要在本地,很有可能是存在遠端伺服器,所以也支援以Web Service的形式對資料進行訪問和持久化。

根據上述的描述,Repository 模式的架構圖如下所示:

可以看到,通過統一的介面,可以實現對不同儲存介質的訪問,甚至是訪問遠端資料。

定義Repository規範

Repository的規範就是介面,這個介面功能很簡單,封裝了資料增,刪,查,改的行為:

public interface IRepository<T> where T:class,new()
{
    void Insert(T instance);
    void Delete(T instance);
    void Update(T instance);
    IEnumerable<T> Select(Func<T,bool> func );
}複製程式碼

這只是一個最基本的定義,也是最基礎的操作,完全可以再做擴充套件。

值得注意的是,對於一些只讀資料,比如TextAssets,Insert,Delete,Update 往往不用實現。這就違反了『裡式替換原則』,解決方案也很簡單,使用介面隔離,對於只讀的資料只實現 ISelectable 介面。但這往往會破環了我們的Repository結構,你可能會擴充套件很多不同的行為介面,從程式碼角度很優化,但可讀性變差。所以,在uMVVM框架中,我為了保證Repository的完整性和可讀性,選擇違背『裡式替換原則』。

開發者根據不同的儲存介質,決定不同的操作方法,這是顯而易見的,下面就是一些常見Repository實現。

定義UnityResourcesRepository:用來訪問Unity的資源TextAssets

public class UnityResourcesRepository<T> : IRepository<T> where T : class, new()
{
    //...省略部分程式碼...
    public IEnumerable<T> Select(Func<T, bool> func)
    {
        List<T> items = new List<T>();
        try
        {
            TextAsset[] textAssets = Resources.LoadAll<TextAsset>(DataDirectory);
            for (int i = 0; i < textAssets.Length; i++)
            {
                TextAsset textAsset = textAssets[i];
                T item = Serializer.Deserialize<T>(textAsset.text);
                items.Add(item);
            }
        }
        catch (Exception e)
        {
            throw new Exception(e.ToString());
        }
        return items.Where(func);
    }
}複製程式碼

定義PlayerPrefsRepository:用來訪問和持久化一些存檔相關資訊

public class PlayerPrefsRepository<T> : IRepository<T> where T : class, new()
{
    //...省略部分程式碼...
    public void Insert(T instance)
    {
        try
        {
            string serializedObject = Serializer.Serialize<T>(instance, true);
            PlayerPrefs.SetString(KeysIndexName, serializedObject);

        }
        catch (Exception e)
        {
            throw new Exception(e.ToString());
        }

    }

}複製程式碼

定義FileSystemRepository:用來訪問和持久化一些日誌相關資訊

public class FileSystemRepository<T> : IRepository<T> where T:class,new()
{    
    //...省略部分程式碼...
    public void Insert(T instance)
    {
        try
        {
            string filename = GetFilename(Guid.NewGuid());
            if (File.Exists(filename))
            {
                throw new Exception("Attempting to insert an object which already exists. Filename=" + filename);
            }

            string serializedObject = Serializer.Serialize<T>(instance, true);
            using (StreamWriter stream = new StreamWriter(filename))
            {
                stream.Write(serializedObject);
            }
        }
        catch (Exception e)
        {
            throw new Exception(e.ToString());
        }

    }

}複製程式碼

定義MemoryRepository:用來快取記憶體一些臨時資料

public class MemoryRepository<T> : IRepository<T> where T : class, new()
{

    //...省略部分程式碼...

    private Dictionary<object, T> repository = new Dictionary<object, T>();

    public MemoryRepository()
    {
        FindKeyPropertyInDataType();
    }

    public void Insert(T instance)
    {
        try
        {
            var id = KeyPropertyInfo.GetValue(instance, null);
            repository[id] = instance;
        }
        catch (Exception e)
        {
            throw new Exception(e.ToString());
        }
    }

    private void FindKeyPropertyInDataType()
    {
        foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
        {
            object[] attributes = propertyInfo.GetCustomAttributes(typeof(RepositoryKey), false);
            if (attributes != null && attributes.Length == 1)
            {
                KeyPropertyInfo = propertyInfo;
            }
            else
            {
                throw new Exception("more than one repository key exist");
            }
        }

    }
}複製程式碼

定義DbRepository:用來操作關係型資料庫Sqlite

public class DbRepository<T> : IRepository<T> where T : class, new()
{
    private readonly SQLiteConnection _connection;

    //...省略部分程式碼...

    public void Insert(T instance)
    {
        try
        {
            _connection.Insert(instance);
        }
        catch (Exception e)
        {
           throw new Exception(e.ToString());
        }
    }

}複製程式碼

定義RestRepository:以WebService的形式訪問和持久化遠端資料

public class RestRepository<T, R>:IRepository<T> where T : class, new() where R : class, new()
{
    //...省略部分程式碼...

    public void Insert(T instance)
    {
        //通過WWW像遠端傳送訊息
    }
}複製程式碼

小結

Repository 模式是很常見的資料層技術,對於.NET 程式設計師來說就是DAL,而對於Java程式設計師而言就是DAO。我們擴充套件了不同的Repository 物件來對不同的介質進行訪問和持久化,這也是今後對快取的實現做準備。
原始碼託管在Github上,點選此瞭解

歡迎關注我的公眾號:

相關文章