Разработайте черту, которая содержит объект его типа внутри и выберите во время выполнения, что использовать - PullRequest
1 голос
/ 11 марта 2019

В Scala я пытаюсь создать следующую вещь.

У меня есть иерархия Config объектов. У меня сейчас около 10 различных подклассов Config, и это число будет расти.

Я хочу создать интерфейс Storage двумя способами: add(c: Config): Unit и get(name: String): Config, чтобы иметь возможность добавлять / извлекать Config объекты в базовое хранилище (дБ, файл, набор в памяти и т. Д.). ).

Каждый ConfigImpl объект должен храниться по-своему: например, ConfigImplA объекты будут храниться в StorageImplA, ConfigImplB в StorageImplB и т. Д.

Обычно каждый подтип ConfigImpl имеет свой собственный StorageImpl, который не зависит от других.

Наконец, я хочу создать класс MultiStorage extends Storage компонента верхнего уровня, который внутри содержит List[_ <: Storage] (с каждым хранилищем, содержащим объекты одного типа). get из MultiStorage будет пытаться извлечь Config из каждого хранилища в списке по порядку, в то время как add зарегистрирует новый c: Config в хранилище, 'хранящем' объект того же (sub) тип c:

List[_ <: Storage] storages = List(storageA, storageB, storageC, ...) \\ this is externally configured 

def add(c: Config) = storages.find(s => typeOf[s] == typeOf[c]).get.add(c)       

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

Ответы [ 2 ]

1 голос
/ 11 марта 2019

Используя List[_ <: Storage], вы запрашиваете удаление (статической) информации о типе.Когда вы получаете элементы из него, их тип просто Storage.(Также нет большой разницы между List[_ <: Storage] и List[Storage], потому что List является ковариантным.)

Но, похоже, для ваших требований достаточно информации о классе времени выполнения:

trait Storage {
  def configClass: Class[_ <: Config]
  // other methods
}

def add(c: Config) = storages.find(s => s.configClass.isInstance(c)).get.add(c)

С другой стороны, похоже, что ваш Storage сам должен быть универсальным, если он не может хранить любой Config, но только определенные подтипы:

trait Storage[T <: Config] {
  def configClass: Class[T]
  def add(c: T): Unit
  def get(name: String): T
}

val storages: List[Storage[_ <: Config]] = ...

, которыена самом деле add немного усложняет (но asInstanceOf на самом деле не делает его менее безопасным):

def add(c: Config) = storages.find(s => s.configClass.isInstance(c)).get.asInstanceOf[Storage[Config]].add(c)

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

0 голосов
/ 11 марта 2019

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

class SomeStorage extends Storage {
   def add(c: Config): Boolean = c match {
     case config: MyConfig => storeMyConfig(config); true
     case _ => false
   }
}

class MultiStorage(storages: Storage*) extends Storage {
  def add(c: Config): Boolean = storages.find(_.add(c)).isDefined
}

Это немного более гибко, чем параметризация, как предлагает другой ответ, потому что вы можете иметь одну и ту же реализацию Storage для обработки нескольких типов Config и даже строить MultiStorage в иерархическом порядке:

val someStorage = MultiStorage(
  MultiStorage(storage1, storage2),
  MultiStorage(storage3, storage4)
)
...