Как абстрактное создание синглтона с использованием XML-сериализации

63
7

Я пытаюсь создать общий репозиторий, используя шаблон 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) вместе, поэтому этот подход, возможно, был совершенно неправильным с самого начала. Я не могу точно определить, что не так из исключения (внутреннее исключение равно null), поэтому я надеюсь, что кто-то, кто знает эти шаблоны, может мне помочь.

ОБНОВИТЬ

Пример файла 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;
}
}

спросил(а) 2021-01-25T17:35:22+03:00 4 месяца, 4 недели назад
1
Решение
77

Я думаю, что GameRepository здесь избыточен. Вы можете сериализовать/десериализовать список игр непосредственно в xml файл (см. Ниже). Что касается общего дизайна, кажется, вы немного сработали с ума (слишком много в статическом контексте). Это затрудняет проверку и изменение кода. Я бы рекомендовал прочитать на инъекции зависимостей и использовать контейнер IoC для управления временем жизни объектов (в том числе одиночных). Мне удалось заставить ваш пример работать с некоторыми изменениями в коде чтения/записи и изменил дизайн, чтобы устранить любые проблемы с одиночками из конкретных реализаций. Таким образом, клиентский код может использовать одноэлементный или нет. Надеюсь это поможет.

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 Oddysee"
};
_gameRepository.Add(game);
Console.WriteLine("Added game " + game.Title);

_gameRepository.Save();
Console.WriteLine("Saved games");
}
}

ответил(а) 2021-01-25T17:35:22+03:00 4 месяца, 4 недели назад
63

Ваш 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. Сериализатор вообще не обрабатывает наследование - ему нужно знать, какой тип десериализуется.

Я не думал о хорошем решении этого вопроса, но я буду продолжать думать.

ответил(а) 2021-01-25T17:35:22+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема