Подтвердить что ты не робот

Неизбежный дизайн: работа с безумием конструктора

По разным причинам я хотел бы начать использовать более неизменные типы в дизайнах. На данный момент я работаю с проектом, который имеет существующий класс следующим образом:

public class IssueRecord
{
    // The real class has more readable names :)
    public string Foo { get; set; }
    public string Bar { get; set; }
    public int Baz { get; set; }
    public string Prop { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
    public string Prop4 { get; set; }
    public string Prop5 { get; set; }
    public string Prop6 { get; set; }
    public string Prop7 { get; set; } 
    public string Prop8 { get; set; } 
    public string Prop9 { get; set; }
    public string PropA { get; set; }
}

Этот класс представляет собой некоторый формат на диске, который действительно имеет это много свойств, поэтому рефакторинг его на более мелкие биты практически не может быть и речи на данный момент.

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

4b9b3361

Ответ 1

Означает ли это, что конструктор этого класса действительно должен иметь 13 параметров в неизменяемом дизайне?

В общем, да. Для неизменяемого типа с 13 свойствами потребуются некоторые средства инициализации всех этих значений.

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

Этот класс представляет собой некоторый формат на диске, который действительно имеет это много свойств, поэтому рефакторинг его на более мелкие биты практически не может быть и речи на данный момент.

Если "формат на диске" - это то, что определяется во время выполнения, возможно, у вас может быть метод или конструктор factory, который принимает данные инициализации (то есть: имя файла? и т.д.) и строит полностью инициализированный тип для вас.

Ответ 2

Чтобы уменьшить количество аргументов, вы можете сгруппировать их в разумные наборы, но чтобы иметь действительно неизменяемый объект, вы должны инициализировать его в методе constructor/ factory.

Некоторым вариантом является создание класса "строитель", который можно настроить с помощью свободного интерфейса и запросить конечный объект. Это имеет смысл, если вы планируете создавать многие из таких объектов в разных местах кода, иначе многие аргументы в одном месте могут быть приемлемыми компромиссами.

var immutable = new MyImmutableObjectBuilder()
  .SetProp1(1)
  .SetProp2(2)
  .Build();

Ответ 3

Возможно, сохраните свой текущий класс как есть, предоставив разумные умолчания, если это возможно, и переименуйте в IssueRecordOptions. Используйте это как единственный параметр инициализации для вашего неизменяемого IssueRecord.

Ответ 4

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

Ответ 5

Вы можете создать структуру, но тогда вам все равно придется объявлять структуру. Но всегда есть массивы и тому подобное. Если это все тот же тип данных, вы можете группировать их несколькими способами, такими как массив, список или строка. Кажется, что вы правы, но все ваши неизменные типы должны каким-то образом пройти через конструктор, погоду через 13 параметров или через структуру, массив, список и т.д.

Ответ 6

Если ваше намерение запрещает присвоения во время компиляции, вы должны придерживаться назначений конструктора и частных сеттеров. Однако он имеет множество недостатков - вы не можете использовать новую инициализацию члена, а не xml-дезанализацию и т.д.

Я бы предложил что-то вроде этого:

    public class IssuerRecord
    {
        public string PropA { get; set; }
        public IList<IssuerRecord> Subrecords { get; set; }
    }

    public class ImmutableIssuerRecord
    {
        public ImmutableIssuerRecord(IssuerRecord record)
        {
            PropA = record.PropA;
            Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r));
        }

        public string PropA { get; private set; }
        // lacks Count and this[int] but it IReadOnlyList<T> is coming in 4.5.
        public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; }

        // you may want to get a mutable copy again at some point.
        public IssuerRecord GetMutableCopy()
        {
            var copy = new IssuerRecord
                           {
                               PropA = PropA,
                               Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy()))
                           };
            return copy;
        }
    }

ЭмитентRecord здесь гораздо более описательный и полезный. Когда вы передаете его где-то еще, вы можете легко создать неизменяемую версию. Код, который работает на неизменяемом, должен иметь логику только для чтения, поэтому на самом деле не стоит беспокоиться о том, является ли он тем же типом, что и в эмитентеRecord. Я создаю копию каждого поля вместо того, чтобы просто обертывать объект, потому что его все еще можно изменить где-то в другом месте, но это может быть не обязательно, особенно для последовательных вызовов синхронизации. Однако безопаснее хранить полную неизменяемую копию в какой-то коллекции "для более поздней". Это может быть оболочка, хотя для приложений, когда вы хотите, чтобы какой-то код запрещал модификации, но все же имел возможность получать обновления в состояние объекта.

var record = new IssuerRecord { PropA = "aa" };
if(!Verify(new ImmutableIssuerRecord(record))) return false;

если вы считаете, что в терминах С++ вы можете увидеть ImmutableIssuerRecords как "ЭмитентRecord const". Вы должны принять extracare, хотя для защиты объектов, принадлежащих вашему неизменяемому объекту, почему я предлагаю создать копию для всех детей (пример Subrecords).

ImmutableIssuerRecord.Subrecors здесь IEnumerable и отсутствует Count и this [], но IReadOnlyList входит в 4.5, и вы можете скопировать его из документов, если захотите (и упростите его перенос позже).

существуют и другие подходы, такие как Freezable:

public class IssuerRecord
{
    private bool isFrozen = false;

    private string propA;
    public string PropA
    { 
        get { return propA; }
        set
        {
            if( isFrozen ) throw new NotSupportedOperationException();
            propA = value;
        }
    }

    public void Freeze() { isFrozen = true; }
}

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

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