Класс с членами, которые изменяются во время создания, но неизменны после


22

У меня есть алгоритм, который создает коллекцию объектов. Эти объекты являются изменяемыми во время создания, так как они начинаются с очень небольшого количества, но затем они заполняются данными в разных местах в алгоритме.

После завершения алгоритма объекты никогда не должны быть изменены - однако они используются другими частями программного обеспечения.

В этих сценариях считается ли хорошей практикой иметь две версии класса, как описано ниже?

  • Изменяемый создается алгоритмом, затем
  • По завершении алгоритма данные копируются в неизменяемые объекты, которые возвращаются.

3
Не могли бы вы отредактировать свой вопрос, чтобы уточнить, в чем заключается ваша проблема / вопрос?
Саймон Бергот

Вы также можете быть заинтересованы в этом вопросе: Как заморозить эскимо в .NET (сделать класс неизменным)
R0MANARMY

Ответы:


46

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


Ваше решение было правильным для моих требований (не все из которых были заявлены). После расследования возвращенные объекты не все имеют одинаковые характеристики. Класс построителя имеет много полей, которые можно обнулять, и когда данные собраны, он выбирает, какой из 3 разных классов генерировать: они связаны друг с другом наследованием.
Пол Ричардс

2
@Paul В этом случае, если этот ответ решил вашу проблему, вы должны пометить его как принятый.
Riking

24

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

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

Возьмите этот пример:

public interface IPerson 
{
    public String FirstName 
    {
        get;
    }

    public String LastName 
    {
        get;
    }
} 

public class PersonImpl : IPerson 
{
    private String firstName, lastName;

    public String FirstName 
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public String LastName 
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

class Factory 
{
    public IPerson MakePerson() 
    {
        PersonImpl person = new PersonImpl();
        person.FirstName = 'Joe';
        person.LastName = 'Schmoe';
        return person;
    }
}

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

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

Стоит отметить, что это не соответствует типичной тенденции, в которой неизменный объект создается раз и навсегда через его конструктор. Понятно, что вам, возможно, придется иметь дело со многими параметрами, но вы должны спросить себя, нужно ли все эти параметры определять заранее или некоторые из них могут быть введены позже. В этом случае следует использовать простой конструктор с только необходимыми параметрами. Другими словами, не используйте этот шаблон, если он скрывает другую проблему в вашей программе.


1
Безопасность не лучше, возвращая объект «только для чтения», потому что код, который получает объект, все еще может модифицировать объект, используя отражение. Даже строку можно изменить (не скопировать, изменить на месте) с помощью отражения.
MTilsted

Проблему безопасности можно аккуратно решить с помощью частного урока
Эсбен Сков Педерсен

@Esben: Вы все еще должны бороться с MS07-052: выполнение кода приводит к выполнению кода . Ваш код работает в том же контексте безопасности, что и их код, поэтому они могут просто подключить отладчик и делать все, что пожелают.
Кевин

Кевин1, ты можешь сказать это обо всех инкапсуляциях. Я не пытаюсь защитить от размышлений.
Эсбен Сков Педерсен

1
Проблема использования слова «безопасность» заключается в том, что сразу же кто-то предполагает, что то, что я называю более безопасным вариантом, эквивалентно максимальной безопасности и наилучшей практике. Я тоже никогда не говорил. Если вы передаете библиотеку кому-то для использования, если она не запутана (а иногда даже не запутана), вы можете забыть о гарантии безопасности. Тем не менее, я думаю, что мы все можем согласиться с тем фактом, что если вы вмешиваетесь в возвращенный фасадный объект, используя отражение для извлечения внутреннего объекта, содержащегося в нем, вы не используете библиотеку, как это следует использовать.
Нил

8

Вы можете использовать шаблон Builder, как говорит @JacquesB, или в качестве альтернативы подумать, почему на самом деле эти ваши объекты должны быть изменяемыми во время создания?

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

Потому что Строитель может быть хорошим решением для неправильной проблемы.

Если проблема в том, что в итоге вы получите конструктор длиной 10 параметров и хотите уменьшить его, построив объект понемногу, это может указывать на то, что дизайн испорчен, и эти 10 значений должны быть " "упакован в мешок" / сгруппирован в несколько объектов ... или основной объект разделен на несколько более мелких ...

В этом случае - придерживайтесь неизменности, просто улучшайте дизайн.


Было бы сложно создать все сразу, так как код выполняет несколько запросов к базе данных для получения данных; он сравнивает данные в разных таблицах и в разных базах данных.
Пол Ричардс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.