Альтернатива выполнению большого количества вычислений в конструкторе - scala - PullRequest
7 голосов
/ 07 марта 2012

Я изучаю scala для нового проекта, стремясь к неизменности и функциональному стилю, где это возможно.

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

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

Однако мне кажется неправильным иметь столько конструкторов в конструкторе.Единственный способ обойти это, как я вижу, - это иметь вычисленные значения var с и предоставить метод run, который выполняет вычисления - но тогда этот метод можно вызывать несколько раз, что будет бессмысленно.

На самом ли деле это нормально, делать много в конструкторе scala?Например, нет вызовов в БД, только внутренние расчеты.Или есть какой-то шаблон для этого?

Вот основная идея в очень очень простой форме:

class Foo(val x:Int, val y:Int, calculations:List[Calculation]) {
  val xHistory = new collection.mutable.ListBuffer[Int]()
  val yHistory = new collection.mutable.ListBuffer[Int]()

  calculations.map { calc => calc.perform(this) }.foreach { result => 
    xHistory += result.x 
    yHistory += result.y
  }
}

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

Ответы [ 3 ]

6 голосов
/ 07 марта 2012

Работа внутри конструктора

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

Задержка инициализации

Я не знаю ничего, что могло бы быть неправильно при выполнении большого количества работы внутри конструктора.

Как отмечено в комментариях, могут быть проблемы с кодом, выполняющимся внутри конструктора, относительно параллелизма. Для этого черта DelayedInit была введена в Scala 2.8.0. Проблемы такого рода возникают, например, при работе с элементами графического интерфейса Swing.

Черта DelayedInit предоставляет еще один инструмент для настройки последовательности инициализации классов и объектов. Если класс или объект наследует от этой черты, весь его код инициализации упакован в замыкание и переадресация в качестве аргумента метода с именем delayedInit который определяется как абстрактный метод в признаке DelayedInit.

Реализации delayedInit, таким образом, имеют полную свободу при выполнении код инициализации. Например, новые фирменные магазины Scala App все последовательности инициализации во внутреннем буфере и выполняет их когда вызывается основной метод объекта.

Ленивые Конструкции

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

  • Вы можете использовать lazy val членов, которые будут вычислены при первом запросе.
  • Если вы вычисляете последовательность дорогих объектов, вы можете использовать «ленивую» структуру данных, такую ​​как Stream. Это похоже на List, который вычисляет следующий элемент только по требованию. Таким образом, в определенный момент времени была вычислена только начальная часть Stream, к которой уже был получен доступ.

Показания к применению lazy

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

Примечание относительно примера OP

Нельзя делать опасные вещи внутри конструктора; подобно тому, как позволить ссылкам на частично построенный объект ускользать от конструктора (через this -reference). Пример внутри OP делает это с calc.perform(this). Эта «потенциальная ошибка» не может быть исправлена ​​с помощью следующих предложений.

4 голосов
/ 07 марта 2012

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

object Foo {
    def create(input: ...) {
        val output = //long running computations
        new Foo(output)
    }
}

class Foo(val output: ...)

Возможно, вы захотите скрыть конструктор Foo class, как предложено @ Nicolas :

class Foo private (val output: ...)

Теперь вы можете написать:

val foo: Foo = Foo.create(input)
2 голосов
/ 07 марта 2012

Одна из возможностей заключается в инкапсуляции вычислений в инициализаторе значений.

class Foo(arg1:String, arg2:String){

val (x, y, z) = {  //tedious calculation using inputs
                        (result1, result2, result:3)}

}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...