Создание объектов «Только для чтения» для возврата из API-оболочки? - PullRequest
2 голосов
/ 13 мая 2011

Я пишу оболочку для REST API (который является статическим классом со статическими методами) на работе, и он должен возвращать класс или структуру, содержащую весь проанализированный Json, возвращенный из запроса API.Я анализирую Json, используя System.Web.Script.Serialization примерно так:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResult>(json);

Затем я хочу установить два дополнительных параметра в QueryResult: исходный URL-адрес запроса и точный Json, возвращаемый API.Единственное противоречие заключается в том, что я хочу, чтобы весь объект читался только после его возвращения из оболочки API.Моя первая мысль - позволить переменным быть установленными только через конструктор, но анализировать Json так, как я никогда не позволяю мне использовать конструктор.Я думал о том, чтобы иметь два очень похожих объекта, то есть закрытый класс, который нельзя увидеть за пределами оболочки, используемой для синтаксического анализа, а затем открытый класс, который использует конструктор для установки параметров только для чтения один раз, но это оченьИзлишним, и я бы предпочел сделать это любым другим способом.

Есть ли какие-либо шаблоны дизайна или советы, чтобы позволить мне сделать это?Я хочу, чтобы они пытались присвоить одно из свойств синтаксической ошибке, а не просто игнорируемое назначение.

Ответы [ 5 ]

2 голосов
/ 13 мая 2011

Обновление

То, что я ищу, это способ быть возможность редактировать объект, а затем заблокировать это так, что каждое поле читается только после определенной строки кода.

Это звучит как подходящий вариант использования для шаблона построения. У вас есть изменяемый объект, который вы можете использовать просто для установки состояния того, что вы хотите построить; тогда этот тип отвечает за создание экземпляра неизменяемого («заблокированного») типа, который действительно может использоваться.

Например:

public class QueryResultBuilder
{
    private string _requestUrl;
    // plus other fields

    public QueryResultBuilder SetRequestUrl(string requestUrl)
    {
        _requestUrl = requestUrl;
        return this;
    }

    // plus other methods

    public QueryResult Build()
    {
        // This could be an internal constructor,
        // only used by this builder type.
        return new QueryResult(_requestUrl /* plus other parameters *);
    }
}

Тогда ваш код вызова может выглядеть так:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResultBuilder>(json)
                    .SetRequestUrl("url")
                    .Build();

Я, честно говоря, понятия не имею, является ли это подходящим ответом для вашего сценария, но одно общее решение проблемы «объект только для чтения, который может быть« обновлен »» состоит в том, чтобы иметь неизменный тип, который возвращает копии с изменениями (например, DateTime.Add).

Так что вы можете сделать операцию, как ...

class QueryResult
{
    // ...lots of other stuff...

    public QueryResult SetRequestUrl(string requestUrl)
    {
        QueryResult clone = this.Clone();

        // This property could have a private setter.
        clone.RequestUrl = requestUrl;

        return clone;
    }
}

Тогда для вызова кода нужно сделать что-то вроде:

JavaScriptSerializer jss = new JavaScriptSerializer();
QueryResult re = jss.Deserialize<QueryResult>(json).SetRequestUrl("url");
1 голос
/ 13 мая 2011

Вы, к сожалению, обнаружили, почему сериализацию часто считают ортогональной правильному дизайну ОО: она нарушает инкапсуляцию ради удобства.

Я думаю, вам лучше всего использовать объект-оболочку со свойствами get {}. Да, это избыточно, но работает и довольно просто.

Кроме того, вы можете подумать, зачем вам вообще нужна эта функциональность. То, что потребители вашей услуги делают с данными, не должно вас сильно беспокоить. Есть ли конкретная причина, которую вы имели в виду?

0 голосов
/ 13 мая 2011

Можете ли вы заставить QueryResult реализовать интерфейс IQueryResult?

JavaScriptSerializer jss = new JavaScriptSerializer();
IQueryResult re = jss.Deserialize<QueryResult>(json);

Сделать интерфейс, указывающий, что только свойство получает, а не устанавливает.

public interface IQueryResult
{
    int Foo { get; }
}

internal class QueryResult : IQueryResult
{
    public Foo { get; set; }
}

Конечно, это не совсем такгарантировано только для чтения.Но это позволяет избежать дублирования класса.Если вы можете сделать класс QueryResult internal, чтобы они не могли использовать его, как я делал выше.Таким образом вы можете манипулировать этим, но они не могут.

0 голосов
/ 13 мая 2011

вы можете добавить механизм к блокировке объекта после его сериализации ...

class QueryResult
{
    private bool _locked;

    // Called after deserialization to lock the object...
    internal void LockDown()
    {
        this._locked = true;
    }

    public String Foo
    {
        get { return this._foo; }
        set 
        {
            if (this._locked)
                throw new InvalidOperationException();

            this._foo = value;
        }
    }
}
0 голосов
/ 13 мая 2011

Я не вижу хорошего способа сделать это ошибкой во время компиляции, чтобы кто-то устанавливал свойство, все еще позволяя сериализатору работать, но вы можете выдать исключение, если установлено свойство, которое уже имеет значение:

class QueryResult
{
  public string Foo
  {
    get
    {
      return _foo; //private backing ivar
    }
    set
    {
      if (_foo == null)
        _foo = value;
      else
        throw new InvalidOperationException("Cannot set a property that already has a value.");
    }
  }
}

Просто убедитесь, что все ваши типы обнуляются или имеют значения по умолчанию, которые никогда не будут установлены (т. Е. Находятся за пределами реалистичного набора возможных значений свойств).

...