谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化。Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化数据的行为。开发者只要对接口进行特定的实现就可以满足对不同存储介质的访问,比如存储在Database,File System,Cache等等。软件开发领域有非常多类似的想法,比如JDBC就是定义了一套规范,而具体的厂商MySql,Oracle根据此开发对应的驱动。

Unity 中的Repository模式

在Unity 3D中,数据的存储其实有很多地方,比如最常见的内存可以高速缓存一些临时数据,PlayerPrefs可以记录一些存档信息,TextAsset可以存一些配置信息,日志文件可以用IO操作写入,关系型数据结构可以使用Sqlite存储。Repository 是个很抽象的概念,操作的数据也不一定要在本地,很有可能是存在远程服务器,所以也支持以Web Service的形式对数据进行访问和持久化。

根据上述的描述,Repository 模式的架构图如下所示:

可以看到,通过统一的接口,可以实现对不同存储介质的访问,甚至是访问远程数据。

定义Repository规范

Repository的规范就是接口,这个接口功能很简单,封装了数据增,删,查,改的行为:

  1. public interface IRepository<T> where T:class,new()
  2. {
  3. void Insert(T instance);
  4. void Delete(T instance);
  5. void Update(T instance);
  6. IEnumerable<T> Select(Func<T,bool> func );
  7. }

这只是一个最基本的定义,也是最基础的操作,完全可以再做扩展。

值得注意的是,对于一些只读数据,比如TextAssets,Insert,Delete,Update 往往不用实现。这就违反了『里式替换原则』,解决方案也很简单,使用接口隔离,对于只读的数据只实现 ISelectable 接口。但这往往会破环了我们的Repository结构,你可能会扩展很多不同的行为接口,从代码角度很优化,但可读性变差。所以,在uMVVM框架中,我为了保证Repository的完整性和可读性,选择违背『里式替换原则』。

开发者根据不同的存储介质,决定不同的操作方法,这是显而易见的,下面就是一些常见Repository实现。

定义UnityResourcesRepository:用来访问Unity的资源TextAssets

  1. public class UnityResourcesRepository<T> : IRepository<T> where T : class, new()
  2. {
  3. //...省略部分代码...
  4. public IEnumerable<T> Select(Func<T, bool> func)
  5. {
  6. List<T> items = new List<T>();
  7. try
  8. {
  9. TextAsset[] textAssets = Resources.LoadAll<TextAsset>(DataDirectory);
  10. for (int i = 0; i < textAssets.Length; i++)
  11. {
  12. TextAsset textAsset = textAssets[i];
  13. T item = Serializer.Deserialize<T>(textAsset.text);
  14. items.Add(item);
  15. }
  16. }
  17. catch (Exception e)
  18. {
  19. throw new Exception(e.ToString());
  20. }
  21. return items.Where(func);
  22. }
  23. }

定义PlayerPrefsRepository:用来访问和持久化一些存档相关信息

  1. public class PlayerPrefsRepository<T> : IRepository<T> where T : class, new()
  2. {
  3. //...省略部分代码...
  4. public void Insert(T instance)
  5. {
  6. try
  7. {
  8. string serializedObject = Serializer.Serialize<T>(instance, true);
  9. PlayerPrefs.SetString(KeysIndexName, serializedObject);
  10. }
  11. catch (Exception e)
  12. {
  13. throw new Exception(e.ToString());
  14. }
  15. }
  16. }

定义FileSystemRepository:用来访问和持久化一些日志相关信息

  1. public class FileSystemRepository<T> : IRepository<T> where T:class,new()
  2. {
  3. //...省略部分代码...
  4. public void Insert(T instance)
  5. {
  6. try
  7. {
  8. string filename = GetFilename(Guid.NewGuid());
  9. if (File.Exists(filename))
  10. {
  11. throw new Exception("Attempting to insert an object which already exists. Filename=" + filename);
  12. }
  13. string serializedObject = Serializer.Serialize<T>(instance, true);
  14. using (StreamWriter stream = new StreamWriter(filename))
  15. {
  16. stream.Write(serializedObject);
  17. }
  18. }
  19. catch (Exception e)
  20. {
  21. throw new Exception(e.ToString());
  22. }
  23. }
  24. }

定义MemoryRepository:用来高速缓存一些临时数据

  1. public class MemoryRepository<T> : IRepository<T> where T : class, new()
  2. {
  3. //...省略部分代码...
  4. private Dictionary<object, T> repository = new Dictionary<object, T>();
  5. public MemoryRepository()
  6. {
  7. FindKeyPropertyInDataType();
  8. }
  9. public void Insert(T instance)
  10. {
  11. try
  12. {
  13. var id = KeyPropertyInfo.GetValue(instance, null);
  14. repository[id] = instance;
  15. }
  16. catch (Exception e)
  17. {
  18. throw new Exception(e.ToString());
  19. }
  20. }
  21. private void FindKeyPropertyInDataType()
  22. {
  23. foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
  24. {
  25. object[] attributes = propertyInfo.GetCustomAttributes(typeof(RepositoryKey), false);
  26. if (attributes != null && attributes.Length == 1)
  27. {
  28. KeyPropertyInfo = propertyInfo;
  29. }
  30. else
  31. {
  32. throw new Exception("more than one repository key exist");
  33. }
  34. }
  35. }
  36. }

定义DbRepository:用来操作关系型数据库Sqlite

  1. public class DbRepository<T> : IRepository<T> where T : class, new()
  2. {
  3. private readonly SQLiteConnection _connection;
  4. //...省略部分代码...
  5. public void Insert(T instance)
  6. {
  7. try
  8. {
  9. _connection.Insert(instance);
  10. }
  11. catch (Exception e)
  12. {
  13. throw new Exception(e.ToString());
  14. }
  15. }
  16. }

定义RestRepository:以WebService的形式访问和持久化远程数据

  1. public class RestRepository<T, R>:IRepository<T> where T : class, new() where R : class, new()
  2. {
  3. //...省略部分代码...
  4. public void Insert(T instance)
  5. {
  6. //通过WWW像远程发送消息
  7. }
  8. }

小结

Repository 模式是很常见的数据层技术,对于.NET 程序员来说就是DAL,而对于Java程序员而言就是DAO。我们扩展了不同的Repository 对象来对不同的介质进行访问和持久化,这也是今后对缓存的实现做准备。

源代码托管在Github上,点击此了解

Unity应用架构设计(9)——构建统一的 Repository的更多相关文章

  1. Unity应用架构设计(3)——构建View和ViewModel的生命周期

    对于一个View而言,本质上是一个MonoBehaviour.它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等.这些是非常好的方法,可以让开发者在各个阶段 ...

  2. Unity 3D Framework Designing(9)——构建统一的 Repository

    谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...

  3. Unity应用架构设计(11)——一个网络层的构建

    对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...

  4. Unity应用架构设计(6)——设计动态数据集合ObservableList

    什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...

  5. Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 1)

    『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...

  6. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)

    MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...

  7. Unity应用架构设计(13)——日志组件的实施

    对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控.总之,日志的好处很多,特 ...

  8. Unity应用架构设计(7)——IoC工厂理念先行

    一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...

  9. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

随机推荐

  1. POJ 3140 Contestants Division 【树形DP】

    <题目链接> 题目大意:给你一棵树,让你找一条边,使得该边的两个端点所对应的两颗子树权值和相差最小,求最小的权值差. 解题分析: 比较基础的树形DP. #include <cstdi ...

  2. 001.Redis简介及安装

    一 Redis简介 1.1 Redis 简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key-value 缓存产品有以下三个特点: ...

  3. iOS Runtime(一)、objc_class深深的误解

    现在网上讲解的objc_class 绝大部分是错的.18年.19年依然很多童鞋写着错误的Runtime文章发到网上,面试的时候基本绝大部分人都说着网上所谓的"正确答案". 一.错误 ...

  4. 谈谈《Dotnet core结合jquery的前后端加密解密密码密文传输的实现》一文中后端解密失败的原因

    详情请看<Dotnet core结合jquery的前后端加密解密密码密文传输的实现>,正常来讲,这个博客里面的代码是没有问题的,但是我有时候却会直接报错,原因是后台解密失败:Interna ...

  5. vue中的表单

    v-model指令实现表单双向绑定数据.触发文本框的input事件.一.文本框 <div id="J_app"> <p>{{ info }}</p&g ...

  6. SQL总结——存储过程

    SQL总结(五)存储过程 概念 存储过程(Stored Procedure):已预编译为一个可执行过程的一个或多个SQL语句. 创建存储过程语法 CREATE proc | procedure pro ...

  7. 2017-9-17-Windows Live Writer常用快捷键

    Window Live Writer常用的快捷键: 将当前文章发布到博客:CTRL+SHIFT+P 打开新文章: CTRL+N 将草稿保存到计算机上: CTRL+S 切换到"普通" ...

  8. java中的lis数组转为json数据

    第一个想到的办法就是 javascript中的replace 也就是先将list数组转为 字符串再对 字符串  replace 但是万万没想到javascript的replace函数在替换数据时, 默 ...

  9. 编程菜鸟的日记-初学尝试编程-C++ Primer Plus 第4章编程练习4

    #include <iostream>#include <string>using namespace std;int main(){ string fname; string ...

  10. windbg foreach用法

    .foreach 关键字分析一个或多个命令的输出并将该输出中每一个值作为另一个或多个命令的输入 .foreach [Options] ( Variable  { InCommands } ) { Ou ...