Я работал над проектом клиент-сервер. Серверная часть реализована на PHP. Клиент реализован на C #. Веб-сокет используется для связи между ними.
Итак, вот в чем проблема. Клиент сделает запрос. Json используется для отправки объектов и проверки по схеме. Запрос ДОЛЖЕН ИМЕТЬ его имя и МОЖЕТ содержать аргументы. Аргументы подобны ассоциативному массиву (ключ => значение).
Сервер даст ответ. Ответ МОЖЕТ содержать аргументы, объекты, массив объектов. Например, клиент отправляет запрос как:
{
"name": "AuthenticationRequest",
"username": "root",
"password": "password",
"etc": "etc"}
Для этого сервер ответит ответом AuthSuccess или AuthFailed, например:
{
"name": "AuthFailed",
"args": [
{
"reason": "Reason text"}]
}
Если ответ AuthSuccess, клиент отправит запрос о том, кто в сети. Сервер должен отправить массив пользователей.
И так далее. Так что проблема в том, как хранить эти ответы на стороне клиента. Я имею в виду, что создание нового объекта для каждого типа ответа безумно. Это будут сотни типов запросов, и каждый из них требует своего ответа. И любое изменение в структуре запроса будет очень-очень сложным …
Нужен какой-то шаблон или трюк. Я знаю, что это своего рода нубийский способ … Но если у кого-то есть идея лучше реализовать структуру запрос / ответ, пожалуйста, сообщите об этом.
С наилучшими пожеланиями!
Я бы определенно выбрал новый класс для каждого типа запроса. Да, вам может понадобиться написать много кода, но это будет безопаснее. Дело (для меня) в том, кто напишет этот код?. Давайте прочитаем этот ответ до конца (или сразу перейдем к последнему предложенному варианту).
В этих примерах я буду использовать Dictionary<string, string>
за общий объекты, но вы можете / должны использовать правильный класс (который не предоставляет словарь), массивы, общие перечисления или все, что вам удобно.
Каждый запрос имеет свой строго типизированный класс, например:
abstract class Request {
protected Request(string name) {
Name = name;
}
public string Name { get; private set; }
public Dictionary<string, string> Args { get; set; }
}
sealed class AuthenticationRequest : Request
{
public AuthenticationRequest() : base("AuthenticationRequest") {
}
public string UserName { get; set; }
public string Password { get; set; }
}
Обратите внимание, что вы можете переключатель к полный типизированный подход также снижается Dictionary
за Args
в пользу типизированных классов.
То, что вы рассматривали как недостаток (изменения сложнее), является ИМО большим преимуществом. Если вы измените что-либо на стороне сервера, ваш запрос не будет выполнен, потому что свойства не будут совпадать. Нет тонких ошибок, когда поля остаются неинициализированными из-за опечаток в строках.
Он строго типизирован, тогда ваш код C # легче поддерживать, у вас есть проверки во время компиляции (как для имен, так и для типов).
Рефакторинг проще, потому что IDE может сделать это за вас, не нужно слепо искать и заменять необработанные строки.
Легко реализовать сложные типы, ваш аргументы не ограничиваются простой строкой (это может не быть проблемой сейчас, но вы можете потребовать ее позже).
У вас есть больше кода для написания в самом начале (однако иерархия классов также поможет вам выявить зависимости и сходства).
Общие параметры (имя и аргументы) набираются, но все остальное хранится в словаре.
sealed class Request {
public string Name { get; set; }
public Dictionary<string, string> Args { get; set; }
public Dictionary<string, string> Properties { get; set; }
}
При смешанном подходе вы сохраняете некоторые преимущества типизированных классов, но вам не нужно определять каждый тип запроса.
Это быстрее реализовать, чем почти / полностью типизированный подход.
У вас есть некоторая степень проверок во время компиляции.
Вы можете повторно использовать весь код (я полагаю, ваш Request
класс будет также повторно использован для Response
класс, и если вы перемещаете свои вспомогательные методы — такие как GetInt32()
— в базовый класс, то вы напишите код один раз).
Это небезопасный, неправильные типы (например, вы извлекаете целое число из строкового свойства) не обнаруживаются до тех пор, пока ошибка не произойдет во время выполнения.
Изменения не нарушат компиляцию: если вы измените имя свойства, вам придется вручную искать каждое место, где вы использовали это свойство. Автоматический рефакторинг не сработает. Это может вызвать ошибки, которые довольно трудно обнаружить.
Ваш код будет загрязнен строковыми константами (да, вы можете определить const string
поля) и отливки.
Сложно использовать сложные типы для вашего аргументы и вы ограничены строковыми значениями (или типами, которые можно легко сериализовать / преобразовать в обычную строку).
Динамические объекты позволяют вам определять объект и обращаться к его свойствам / методам как к типизированному классу, но на самом деле они будут динамично разрешается во время выполнения.
dynamic request = new ExpandoObject();
request.Name = "AuthenticationRequest";
request.UserName = "test";
Обратите внимание, что у вас также может быть такой простой в использовании синтаксис:
dynamic request = new {
Name = "AuthenticationRequest",
UserName = "test"};
Если вы добавляете свойство в свою схему, вам не нужно обновлять код, если вы его не используете.
Это немного более безопасно, чем нетипизированный подход. Например, если запрос заполнен:
request.UserName = "test";
Если вы ошибочно напишите это:
Console.WriteLine(request.User);
Вы будете иметь ошибку во время выполнения, и у вас все еще будет некоторая базовая проверка типов / преобразование.
Код немного более читабелен, чем полностью нетипизированный подход.
Это легко и возможно иметь сложные типы.
Даже если код немного более читабелен, чем полностью нетипизированный подход, вы все равно не сможете использовать функции рефакторинга вашей IDE, и у вас почти нет проверок во время компиляции.
Если вы изменяете имя свойства или структуру в своей схеме и забываете обновить свой код (где-то), у вас будет ошибка только во время выполнения, когда это произойдет, когда вы его используете.
Последнее, но самое лучшее … пока мы забыли одну важную вещь: у JSON есть схема, с которой он может быть проверен (см. json-schema.org).
Чем это может быть полезно? Ваши полностью типизированные классы могут быть сгенерированы из этой схемы, давайте посмотрим на JSON-схема для POCO. Если у вас нет / не хотите использовать схему, вы все равно можете генерировать классы из JSON Примеры: посмотрите на Генератор классов JSON C # проект.
Просто создайте один пример (или схему) для каждого запроса / ответа и используйте специальный генератор кода / задачу сборки для создания классов C # из этого, что-то вроде этого (см. Также MSDN о пользовательских инструментах сборки):
Cvent.SchemaToPoco.Console.exe -s %(FullPath) -o .\%(Filename).cs -n CsClient.Model
Все плюсы вышеуказанных решений.
Ничего, о чем я могу думать …
Почему сложно создать класс для каждого вида запроса / ответа? Если у вас есть сотни различных видов запросов и ответов, вы можете попытаться классифицировать их лучше.
Я бы сказал, что в ваших запросах или ответах есть общие закономерности. Например. FailureResponse
может всегда содержать некоторую информацию о состоянии и, возможно, UserData-object
(это может быть что угодно в зависимости от варианта использования). Это также может быть применено к другим категориям (например, SuccessResponse
).
dynamic
это новый статический тип, который действует как заполнитель для типа, не известного до времени выполнения. Однажды dynamic
объект объявлен, можно вызывать операции, получать и задавать свойства, даже передавать dynamic
экземпляр в значительной степени, как если бы это был любой нормальный тип. dynamic
дает нам много веревки, чтобы повеситься. При работе с объектами, типы которых могут быть известны во время компиляции, следует избегать dynamic
ключевое слово любой ценой
Вы можете прочитать больше о динамический