Создание больших, неизменных объектов, не используя конструкторов, имеющих долго списки параметров

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

Это не чувствует себя хорошо, трудно использовать, и удобочитаемость страдает.

Это еще хуже, если поля являются своего рода типом набора как списки. Простое addSibling(S s) упростил бы создание объекта так, но представляет изменяемый объект.

Что делает Вас, парни используют в таких случаях?

Я нахожусь на Scala и Java, но я думаю, что проблемой является агностик языка, пока язык объектно-ориентирован.

Решения я могу думать:

  1. "Отвращение конструктора с длинными списками параметров"
  2. Шаблон разработчика
96
задан Aaron Hall 17 July 2019 в 23:44
поделиться

9 ответов

Что ж, вы хотите, чтобы после создания был как более простой для чтения, так и неизменяемый объект?

Я думаю, что свободный интерфейс ПРАВИЛЬНО СДЕЛАНО вам поможет.

Это будет выглядеть так (чисто выдуманный пример):

final Foo immutable = FooFactory.create()
    .whereRangeConstraintsAre(100,300)
    .withColor(Color.BLUE)
    .withArea(234)
    .withInterspacing(12)
    .build();

Я написал «ПРАВИЛЬНО СДЕЛАНО» жирным шрифтом, потому что большинство программистов Java неправильно понимают интерфейсы и загрязняют свой объект методом, необходимым для построения объект, что, конечно, совершенно неверно.

Хитрость в том, что только метод build () фактически создает Foo (следовательно, Foo может быть неизменяемым).

FooFactory.create () , whereXXX (..) и withXXX (..) все создают «что-то еще».

Что-то еще может быть FooFactory, вот один из способов сделать это ....

You FooFactory будет выглядеть так:

// Notice the private FooFactory constructor
private FooFactory() {
}

public static FooFactory create() {
    return new FooFactory();
}

public FooFactory withColor( final Color col ) {
    this.color = color;
    return this;
}

public Foo build() {
    return new FooImpl( color, and, all, the, other, parameters, go, here );
}
75
ответ дан 24 November 2019 в 05:38
поделиться

Что ж, рассмотрим это на Scala 2.8:

case class Person(name: String, 
                  married: Boolean = false, 
                  espouse: Option[String] = None, 
                  children: Set[String] = Set.empty) {
  def marriedTo(whom: String) = this.copy(married = true, espouse = Some(whom))
  def addChild(whom: String) = this.copy(children = children + whom)
}

scala> Person("Joseph").marriedTo("Mary").addChild("Jesus")
res1: Person = Person(Joseph,true,Some(Mary),Set(Jesus))

Это имеет свою долю проблем, конечно. Например, попробуйте заставить жениться и Option [Person] , а затем заключить брак между двумя людьми. Я не могу придумать способ решить это, не прибегая к частному var и / или частному конструктору плюс фабрика.

20
ответ дан 24 November 2019 в 05:38
поделиться

В Scala 2.8 вы могли использовать именованные параметры и параметры по умолчанию, а также метод copy на классе case. Вот пример кода:

case class Person(name: String, age: Int, children: List[Person] = List()) {
  def addChild(p: Person) = copy(children = p :: this.children)
}

val parent = Person(name = "Bob", age = 55)
  .addChild(Person("Lisa", 23))
  .addChild(Person("Peter", 16))
60
ответ дан 24 November 2019 в 05:38
поделиться

Можно также сделать так, чтобы неизменяемые объекты открывали методы, похожие на мутаторы (например, addSibling), но чтобы они возвращали новый экземпляр. Именно так поступают неизменяемые коллекции Scala.

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

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

5
ответ дан 24 November 2019 в 05:38
поделиться

Вот еще пара вариантов:

Вариант 1

Сделать саму реализацию изменяемой, но разделить интерфейсы, которые она предоставляет, на изменяемые и неизменяемые. Это взято из дизайна библиотеки Swing.

public interface Foo {
  X getX();
  Y getY();
}

public interface MutableFoo extends Foo {
  void setX(X x);
  void setY(Y y);
}

public class FooImpl implements MutableFoo {...}

public SomeClassThatUsesFoo {
  public Foo makeFoo(...) {
    MutableFoo ret = new MutableFoo...
    ret.setX(...);
    ret.setY(...);
    return ret; // As Foo, not MutableFoo
  }
}

Вариант 2

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

11
ответ дан 24 November 2019 в 05:38
поделиться

Рассмотрим четыре варианта:

new Immutable(one, fish, two, fish, red, fish, blue, fish); /*1 */

params = new ImmutableParameters(); /*2 */
params.setType("fowl");
new Immutable(params);

factory = new ImmutableFactory(); /*3 */
factory.setType("fish");
factory.getInstance();

Immutable boringImmutable = new Immutable(); /* 4 */
Immutable lessBoring = boringImmutable.setType("vegetable");

На мой взгляд, каждый из пунктов 2, 3 и 4 приспособлен к разным ситуациям. Первый вариант трудно полюбить, по причинам, указанным в ОП, и обычно является симптомом дизайна, который пострадал от некоторой ползучести и нуждается в рефакторинге.

То, что я перечислил как (2), хорошо, когда за "фабрикой" нет состояния, тогда как (3) - это дизайн выбора, когда состояние есть. Я нахожу себя использующим (2), а не (3), когда я не хочу беспокоиться о потоках и синхронизации, и мне не нужно беспокоиться об амортизации дорогостоящей настройки при производстве многих объектов. (3), с другой стороны, вызывается, когда идет реальная работа по созданию фабрики (настройка с SPI, чтение конфигурационных файлов и т.д.).

Наконец, в чьем-то ответе упоминался вариант (4), когда у вас много маленьких неизменяемых объектов, и предпочтительный паттерн - получать новые объекты из старых.

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

5
ответ дан 24 November 2019 в 05:38
поделиться

Другой потенциальный вариант - провести рефакторинг, чтобы иметь меньше настраиваемых полей. Если группы полей работают (в основном) только друг с другом, объедините их в свой собственный небольшой неизменяемый объект. Конструкторы / строители этого «маленького» объекта должны быть более управляемыми, как и конструктор / построитель для этого «большого» объекта.

4
ответ дан 24 November 2019 в 05:38
поделиться

Я использую C #, и это мои подходы. Рассмотрим:

class Foo
{
    // private fields only to be written inside a constructor
    private readonly int i;
    private readonly string s;
    private readonly Bar b;

    // public getter properties
    public int I { get { return i; } }
    // etc.
}

Вариант 1. Конструктор с необязательными параметрами

public Foo(int i = 0, string s = "bla", Bar b = null)
{
    this.i = i;
    this.s = s;
    this.b = b;
}

Используется, например, как новый Foo (5, b: новый бар (что угодно)) . Не для версий Java или C # до 4.0. но все же стоит показать, поскольку это пример того, как не все решения зависят от языка.

Вариант 2. Конструктор, принимающий объект с одним параметром

public Foo(FooParameters parameters)
{
    this.i = parameters.I;
    // etc.
}

class FooParameters
{
    // public properties with automatically generated private backing fields
    public int I { get; set; }
    public string S { get; set; }
    public Bar B { get; set; }

    // All properties are public, so we don't need a full constructor.
    // For convenience, you could include some commonly used initialization
    // patterns as additional constructors.
    public FooParameters() { }
}

Пример использования:

FooParameters fp = new FooParameters();
fp.I = 5;
fp.S = "bla";
fp.B = new Bar();
Foo f = new Foo(fp);`

C # начиная с версии 3.0 делает это более элегантным с синтаксисом инициализатора объекта (семантически эквивалентным предыдущему примеру):

FooParameters fp = new FooParameters { I = 5, S = "bla", B = new Bar() };
Foo f = new Foo(fp);

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

2
ответ дан 24 November 2019 в 05:38
поделиться

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

Неизменность Popsicle: это то, что я причудливо называют незначительное ослабление неизменность записи один раз. Можно было бы представьте себе объект или поле, которое некоторое время оставался изменчивым во время его инициализации, а затем «заморозили» навсегда. Этот вид неизменность особенно полезна для неизменяемых объектов, которые по кругу ссылаться друг на друга или неизменяемые объекты, которые были сериализованы в диск и при десериализации необходимо быть «текучим» до тех пор, пока весь Процесс десериализации выполняется, при В какой точке могут быть все объекты замороженный.

Таким образом, вы инициализируете свой объект, а затем устанавливаете какой-то флаг «зависания», указывающий, что он больше не доступен для записи. Предпочтительно, чтобы вы спрятались за функцией, чтобы функция по-прежнему была чистой для клиентов, использующих ваш API.

6
ответ дан 24 November 2019 в 05:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: