Шаблон пирога Scala для объектов с разным временем жизни

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

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

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

Это легко сделать в DI-фреймворках, таких как Guice: если мне нужно несколько экземпляров некоторого класса, я просто ввожу Provider<MyClass>; затем я вызываю метод get() для этого провайдера, и все зависимости MyClass разрешаются автоматически. Если MyClass требуются некоторые динамически вычисляемые данные, я могу использовать вспомогательное расширение для внедрения, но результирующий код по-прежнему сводится к поставщику/фабрике. Связанная концепция, области действия, также помогает.

Но я не могу придумать хороший способ сделать это, используя шаблон торта. В настоящее время я использую что-то вроде этого:

trait ModelContainerComponent {  // Globally scoped dependency
    def model: Model
}

trait SubpaneViewComponent {  // A part of dynamically created cake
    ...
}

trait SubpaneControllerComponent {  // Another part of dynamically created cake
    ...
}

trait DefaultSubpaneViewComponent {  // Implementation
    self: SubpaneControllerComponent with ModelContainerComponent =>
    ...
}

trait DefaultSubpaneControllerComponent {  // Implementation
    self: SubpaneViewComponent with ModelContainerComponent =>
    ...
}

trait SubpaneProvider {  // A component which aids in dynamic subpane creation
    def newSubpane(): Subpane
}

object SubpaneProvider {
    type Subpane = SubpaneControllerComponent with SubpaneViewComponent
}

trait DefaultSubpaneProvider {  // Provider component implementation
    self: ModelContainerComponent =>
    def newSubpane() = new DefaultSubpaneControllerComponent with DefaultSubpaneViewController with ModelContainerComponent {
        val model = self.model  // Pass global dependency to the dynamic cake
    }.asInstanceOf[Subpane]
}

Затем я добавляю DefaultSubpaneProvider в торт верхнего уровня и добавляю SubpaneProvider во все компоненты, которые должны создавать подпанели.

Проблема в этом подходе заключается в том, что мне приходится вручную передавать зависимости (model в ModelContainerComponent) от пирога верхнего уровня к динамически созданному пирогу. Это только тривиальный пример, но зависимостей может быть больше, а также может быть больше типов динамически создаваемых тортов. Все они требуют ручной передачи зависимостей; более того, простое изменение интерфейса какого-либо компонента может привести к большому количеству исправлений в нескольких провайдерах.

Есть ли более простой/чистый способ сделать это? Как эта проблема решается в шаблоне торта?


person Vladimir Matveev    schedule 08.07.2013    source источник
comment
Если мой ответ не касается вашего вопроса, я удалю его повторно.   -  person som-snytt    schedule 01.02.2014
comment
Как насчет чего-то вроде   -  person chemikadze    schedule 04.11.2014
comment
trait ModelContainerComponentProxy extends ModelContainerComponent { def originalModelContainer: ModelContainerComponentProxy; def model = originalModelContainer.model} -- это могло бы решить как минимум проблему явной передачи всего содержимого компонентов.   -  person chemikadze    schedule 04.11.2014
comment
Как насчет использования торта с DI-фреймворком, например MacWire? github.com/adamw/macwire   -  person Yuri    schedule 07.12.2014
comment
Зачем нужен актерский состав? Вы не можете написать def newSubpane():SubPane = {}   -  person Edmondo1984    schedule 26.12.2014
comment
@Edmondo1984 Edmondo1984, насколько я помню, приведение было необходимо IDEA для понимания кода. Скорее всего, в последних версиях он не нужен.   -  person Vladimir Matveev    schedule 26.12.2014


Ответы (2)


Рассматривали ли вы следующие альтернативы:

  • Используйте внутренние классы в Scala, так как они автоматически имеют доступ к своим переменным-членам родительского класса.

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

    • Hierarchy / supervision
    • Прослушивание создания/смерти компонентов
    • Правильная синхронизация, когда дело доходит до доступа к изменяемому состоянию

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

person Edmondo1984    schedule 26.12.2014
comment
Невозможно использовать внутренние классы для внедрения зависимостей, потому что они статически привязаны к окружающим их классам (я также не совсем понимаю, как, по-вашему, внутренние классы и шаблон торта связаны в этом отношении), и нельзя использовать акторы. с кодом графического интерфейса - лично я не знаю ни одной библиотеки графического интерфейса, основанной на актерах. Я также почти уверен, что код, который я предоставил, уже описывает проблему, и я не могу предоставить больше сейчас — я работал над этим проектом полтора года назад :) - person Vladimir Matveev; 26.12.2014
comment
Давайте обсудим это дальше офлайн, вы доступны в чате Google? Эдмондо. порку на gmail.com - person Edmondo1984; 26.12.2014
comment
Боюсь, вопрос уже устарел. Я работаю над другими проектами, и мне больше не нужен ответ :) - person Vladimir Matveev; 26.12.2014

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

у нас есть:

trait FooBarInterface {
    def printFoo: Unit
    def printBar: Unit
}

trait PrinterInterface {
    //def color: RGB
    def print(s: String): Unit
}

Для внедрения логики fooBar шаблон торта определяет:

trait FooBarComponent { 
    //The components being used in this component:
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.    
    def fooBarComp: FooBarInterface

    //The implementation of FooBarInterface 
    class FooBarImpl extends FooBarInterface {
        def printFoo = printComp.print("fOo")
        def printBar = printComp.print("BaR")
    }
}

Обратите внимание, что эта реализация не оставляет ни одного поля нереализованным, и когда дело доходит до смешивания всех этих компонентов вместе, у нас будет: val fooBarComp = new FooBarImpl. В тех случаях, когда у нас есть только одна реализация, нам не нужно оставлять fooBarComp нереализованным. вместо этого мы можем иметь:

trait FooBarComponent { 
    //The components being used in this component:
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.    
    def fooBarComp: new FooBarInterface {
        def printFoo = printComp.print("fOo")
        def printBar = printComp.print("BaR")
    }
}

Не все компоненты такие. Например, Printer, зависимость, используемая для печати foo или bar, должна быть настроена, и вы хотите иметь возможность печатать текст разными цветами. Таким образом, зависимость может потребоваться для динамического изменения или установки в какой-то момент программы.

trait PrintComponent {

    def printComp: PrinterInterface

    class PrinterImpl(val color: RGB) extends PrinterInterface {
        def print(s:String) = ...
    }
}

Для статической конфигурации при смешивании этого компонента мы могли бы, например, сказать:

val printComp = PrinterImpl(Blue)

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

trait BazInterface {
    def appendString: String
    def printBar(s: String): Unit
}

и компонент формы:

trait BazComponent { 
    //The components being used in this component:
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.    
    def bazComp(appendString: String) : Baz = new BazImpl(appendString)

    //The implementation of BazInterface 
    class BazImpl(val appendString: String) extends BazInterface {
        def printBaz = printComp.print("baZ" + appendString)
    }
}

Теперь, если бы у нас был компонент FooBarBaz, мы могли бы определить:

trait FooBarBazComponent { 
    //The components being used in this component:
    self: BazComponent with FooBarComponent => 

    val baz = bazComp("***")
    val fooBar = fooBarComp

    //The implementation of BazInterface
    class BazImpl(val appendString: String) extends BazInterface {
        def PrintFooBarBaz = {
            baz.printBaz()
            fooBar.printFooBar()
        }
    }
}

Итак, мы увидели, как можно настроить компонент:

  • статически. (в основном зависимости очень низкого уровня)
  • изнутри другого компонента. (обычно это один бизнес-уровень, настраивающий другой бизнес-уровень, см. «ЗАВИСИМОСТИ, КОТОРЫЕ ТРЕБУЮТ ПОЛЬЗОВАТЕЛЬСКИЕ ДАННЫЕ» в здесь)

Что отличалось в этих двух случаях, так это просто место, где происходит конфигурация. Один предназначен для низкоуровневых зависимостей на самом верхнем уровне программы, другой — для промежуточного компонента, настраиваемого внутри другого компонента. Вопрос в том, где должна происходить настройка такой службы, как Print? Два варианта, которые мы исследовали до сих пор, исключены. На мой взгляд, единственный вариант, который у нас есть, — это добавить Components-Configurer, который смешивает все компоненты, которые нужно настроить, и возвращает компоненты зависимостей, изменяя реализации. Вот простая версия:

trait UICustomiserComponent {
    this: PrintComponent =>

    private var printCompCache: PrintInterface = ???
    def printComp: PrintInterface = printCompCache
}

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

person ShS    schedule 01.10.2017