如何使用 XML 序列化抽象创建单例
How to Abstract Creation of Singleton Using XML Serialization
我正在尝试使用 Singleton 模式创建一个通用存储库,该模式持续到 XML 文件。目前有 3 个具体的存储库,每个都从不同的 XML 文件加载。我无法弄清楚如何从 XML 文件中正确抽象存储库的创建。这是我的 类 这个存储库的一个实现。
型号
public interface IEntity
{
string Name { get; }
}
public class Game : IEntity
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("ExecutablePath")]
public string ExecutablePath { get; set; }
private Game() { } // Required for XML serialization.
public Game(string name, string executablePath)
{
Name = name;
ExecutablePath = executablePath;
}
}
存储库
public interface IRepository<TEntity> where TEntity: IEntity
{
List<TEntity> Items { get; }
void Add(TEntity entity);
void Delete(TEntity entity);
}
public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
public abstract List<TEntity> Items { get; set; }
private static Repository<TEntity> _instance;
public static Repository<TEntity> Instance
{
get
{
if (_instance == null)\
_instance = RepositoryFactory<Repository<TEntity>>.LoadFromFile();
return _instance;
}
}
public void Add(TEntity entity)
{
Items.Add(entity);
}
public void Delete(TEntity entity)
{
Items.Remove(entity);
}
}
public static class RepositoryFactory<TRepository>
{
public static TRepository LoadFromFile()
{
using (TextReader reader = new StreamReader("Games.xml"))
{
XmlSerializer serializer = new XmlSerializer(typeof(TRepository));
return (TRepository)serializer.Deserialize(reader);
}
}
}
[XmlRoot("Games")]
public class GameRepository : Repository<Game>
{
[XmlElement("Game")]
public override sealed List<Game> Items { get; set; }
private GameRepository()
{
Items = new List<Game>();
}
}
尝试调用 'GameRepository.Instance' 时,我得到了这个非常普遍的异常:
{"<Games xmlns=''> was not expected."}
无可否认,这是我第一次尝试将这些设计模式 (singleton/factory) 融合在一起,因此这种方法可能从一开始就完全错误。我无法准确诊断出异常的错误(内部异常为空),所以我希望知道这些模式的人可以帮助我。
更新
样本Games.xml文件
<?xml version="1.0" encoding="utf-8" ?>
<Games>
<Game>Test</Game>
<ExecutablePath>ExecutablePath</ExecutablePath>
</Games>
示例代码 - 仍然失败
class Program
{
static void Main(string[] args)
{
IRepository<Game> gameRepository = new XmlRepository<Game>();
Test test = new Test(gameRepository);
Console.WriteLine(test.GetFirstGame());
Console.Read();
}
}
class Test
{
private IRepository<Game> GameRepository { get; set; }
public Test(IRepository<Game> gameRepository)
{
GameRepository = gameRepository;
}
public string GetFirstGame()
{
return GameRepository.Items.Value.FirstOrDefault().Name;
}
}
您的 RepositoryFactory
尝试使用类型 TRepository
反序列化文件 - 现在,您的 GameRepository
将 Game
作为 TRepository
传递,所以它正在尝试反序列化 Game
类型的对象——当然,这不是您序列化的对象。
您已经序列化了一个 GameRepository
类型的对象,并试图将其反序列化为 Game
.
类型的对象
试试这个:
[XmlRoot("Games")]
public class GameRepository : Repository<GameRepository>
{
[XmlElement("Game")]
public override sealed List<Game> Items { get; set; }
private GameRepository()
{
Items = new List<Game>();
}
}
编辑:
经审查,该答案大部分是错误的。
确实RepositoryFactory
使用了错误的类型来反序列化,但是它没有将Game
作为类型,而是将Repository<Game>
作为类型,这与GameRepository
。序列化器根本不处理继承——它需要知道它正在反序列化的具体类型。
暂时还没有想到好的解决办法,我会继续思考的。
我认为GameRepository
在这里是多余的。您可以 serialize/deserialize 游戏列表直接到 xml 文件(见下文)。至于整体设计,看起来你有点疯狂(在静态上下文中做太多)。这使得代码难以测试和更改。我建议阅读依赖注入,并使用 IoC 容器来管理对象(包括单例)的生命周期。我能够通过对 read/write 代码进行一些修改来让您的示例工作,并且更改了设计以从具体实现中消除对单例的任何担忧。通过这种方式,客户端代码可以选择是否使用单例。希望这有帮助。
public class XmlRepository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
private readonly string _filePath;
private readonly Lazy<List<TEntity>> _items;
public XmlRepository(string filePath)
{
_filePath = filePath;
_items = new Lazy<List<TEntity>>(Load);
}
public IEnumerable<TEntity> Items
{
get { return _items.Value; }
}
public void Add(TEntity item)
{
if (_items.Value.Contains(item))
throw new InvalidOperationException();
_items.Value.Add(item);
}
public void Delete(TEntity item)
{
_items.Value.Remove(item);
}
public void Save()
{
var serializer = new XmlSerializer(typeof(List<TEntity>));
using (var reader = File.CreateText(_filePath))
{
serializer.Serialize(reader, _items.Value);
}
}
private List<TEntity> Load()
{
if (!File.Exists(_filePath))
return new List<TEntity>();
var serializer = new XmlSerializer(typeof(List<TEntity>));
using (var reader = File.OpenText(_filePath))
{
return (List<TEntity>)serializer.Deserialize(reader);
}
}
}
public static class RepositorySingletons
{
private static IRepository<Game> _gameRepository;
public static IRepository<Game> GameRepository
{
get
{
return _gameRepository ??
(_gameRepository = new XmlRepository<Game>("Game.xml"));
}
}
}
public class MyGamesApplication
{
private readonly IRepository<Game> _gameRepository;
public MyGamesApplication(IRepository<Game> gameRepository)
{
// I don't care if I have a singleton or not :)
_gameRepository = gameRepository;
}
public MyGamesApplication()
{
// I need to fetch a singleton :(
_gameRepository = RepositorySingletons.GameRepository;
}
public void Run()
{
var game = new Game
{
Id = 55378008,
Title = "Abe's Oddysee"
};
_gameRepository.Add(game);
Console.WriteLine("Added game " + game.Title);
_gameRepository.Save();
Console.WriteLine("Saved games");
}
}
我正在尝试使用 Singleton 模式创建一个通用存储库,该模式持续到 XML 文件。目前有 3 个具体的存储库,每个都从不同的 XML 文件加载。我无法弄清楚如何从 XML 文件中正确抽象存储库的创建。这是我的 类 这个存储库的一个实现。
型号
public interface IEntity
{
string Name { get; }
}
public class Game : IEntity
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("ExecutablePath")]
public string ExecutablePath { get; set; }
private Game() { } // Required for XML serialization.
public Game(string name, string executablePath)
{
Name = name;
ExecutablePath = executablePath;
}
}
存储库
public interface IRepository<TEntity> where TEntity: IEntity
{
List<TEntity> Items { get; }
void Add(TEntity entity);
void Delete(TEntity entity);
}
public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
public abstract List<TEntity> Items { get; set; }
private static Repository<TEntity> _instance;
public static Repository<TEntity> Instance
{
get
{
if (_instance == null)\
_instance = RepositoryFactory<Repository<TEntity>>.LoadFromFile();
return _instance;
}
}
public void Add(TEntity entity)
{
Items.Add(entity);
}
public void Delete(TEntity entity)
{
Items.Remove(entity);
}
}
public static class RepositoryFactory<TRepository>
{
public static TRepository LoadFromFile()
{
using (TextReader reader = new StreamReader("Games.xml"))
{
XmlSerializer serializer = new XmlSerializer(typeof(TRepository));
return (TRepository)serializer.Deserialize(reader);
}
}
}
[XmlRoot("Games")]
public class GameRepository : Repository<Game>
{
[XmlElement("Game")]
public override sealed List<Game> Items { get; set; }
private GameRepository()
{
Items = new List<Game>();
}
}
尝试调用 'GameRepository.Instance' 时,我得到了这个非常普遍的异常:
{"<Games xmlns=''> was not expected."}
无可否认,这是我第一次尝试将这些设计模式 (singleton/factory) 融合在一起,因此这种方法可能从一开始就完全错误。我无法准确诊断出异常的错误(内部异常为空),所以我希望知道这些模式的人可以帮助我。
更新
样本Games.xml文件
<?xml version="1.0" encoding="utf-8" ?>
<Games>
<Game>Test</Game>
<ExecutablePath>ExecutablePath</ExecutablePath>
</Games>
示例代码 - 仍然失败
class Program
{
static void Main(string[] args)
{
IRepository<Game> gameRepository = new XmlRepository<Game>();
Test test = new Test(gameRepository);
Console.WriteLine(test.GetFirstGame());
Console.Read();
}
}
class Test
{
private IRepository<Game> GameRepository { get; set; }
public Test(IRepository<Game> gameRepository)
{
GameRepository = gameRepository;
}
public string GetFirstGame()
{
return GameRepository.Items.Value.FirstOrDefault().Name;
}
}
您的 RepositoryFactory
尝试使用类型 TRepository
反序列化文件 - 现在,您的 GameRepository
将 Game
作为 TRepository
传递,所以它正在尝试反序列化 Game
类型的对象——当然,这不是您序列化的对象。
您已经序列化了一个 GameRepository
类型的对象,并试图将其反序列化为 Game
.
试试这个:
[XmlRoot("Games")]
public class GameRepository : Repository<GameRepository>
{
[XmlElement("Game")]
public override sealed List<Game> Items { get; set; }
private GameRepository()
{
Items = new List<Game>();
}
}
编辑: 经审查,该答案大部分是错误的。
确实RepositoryFactory
使用了错误的类型来反序列化,但是它没有将Game
作为类型,而是将Repository<Game>
作为类型,这与GameRepository
。序列化器根本不处理继承——它需要知道它正在反序列化的具体类型。
暂时还没有想到好的解决办法,我会继续思考的。
我认为GameRepository
在这里是多余的。您可以 serialize/deserialize 游戏列表直接到 xml 文件(见下文)。至于整体设计,看起来你有点疯狂(在静态上下文中做太多)。这使得代码难以测试和更改。我建议阅读依赖注入,并使用 IoC 容器来管理对象(包括单例)的生命周期。我能够通过对 read/write 代码进行一些修改来让您的示例工作,并且更改了设计以从具体实现中消除对单例的任何担忧。通过这种方式,客户端代码可以选择是否使用单例。希望这有帮助。
public class XmlRepository<TEntity> : IRepository<TEntity> where TEntity : IEntity
{
private readonly string _filePath;
private readonly Lazy<List<TEntity>> _items;
public XmlRepository(string filePath)
{
_filePath = filePath;
_items = new Lazy<List<TEntity>>(Load);
}
public IEnumerable<TEntity> Items
{
get { return _items.Value; }
}
public void Add(TEntity item)
{
if (_items.Value.Contains(item))
throw new InvalidOperationException();
_items.Value.Add(item);
}
public void Delete(TEntity item)
{
_items.Value.Remove(item);
}
public void Save()
{
var serializer = new XmlSerializer(typeof(List<TEntity>));
using (var reader = File.CreateText(_filePath))
{
serializer.Serialize(reader, _items.Value);
}
}
private List<TEntity> Load()
{
if (!File.Exists(_filePath))
return new List<TEntity>();
var serializer = new XmlSerializer(typeof(List<TEntity>));
using (var reader = File.OpenText(_filePath))
{
return (List<TEntity>)serializer.Deserialize(reader);
}
}
}
public static class RepositorySingletons
{
private static IRepository<Game> _gameRepository;
public static IRepository<Game> GameRepository
{
get
{
return _gameRepository ??
(_gameRepository = new XmlRepository<Game>("Game.xml"));
}
}
}
public class MyGamesApplication
{
private readonly IRepository<Game> _gameRepository;
public MyGamesApplication(IRepository<Game> gameRepository)
{
// I don't care if I have a singleton or not :)
_gameRepository = gameRepository;
}
public MyGamesApplication()
{
// I need to fetch a singleton :(
_gameRepository = RepositorySingletons.GameRepository;
}
public void Run()
{
var game = new Game
{
Id = 55378008,
Title = "Abe's Oddysee"
};
_gameRepository.Add(game);
Console.WriteLine("Added game " + game.Title);
_gameRepository.Save();
Console.WriteLine("Saved games");
}
}