2017-01-31 10 views
2

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

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

trait AutoCompleter { 
    def topSuggestions(prefix: String): Iterator[String] 
    def update(sentence: String*): Unit 

    final def topSuggestions(prefix: String, n: Int): List[String] = topSuggestions(prefix).take(n).toList 
} 

Некоторые из них являются конкретные реализации, основанные на пользовательской реализации синтаксического дерева:

/** 
    * This auto-completer learns from the user's input, and therefore does not require a preliminary dictionary. 
    */ 
class ParrotAutoCompleter extends AutoCompleter { 
    private val trie = new WeightedTrie[Char, String]() 

    override def topSuggestions(prefix: String): Iterator[String] = trie.prefixedBy(prefix) 
    override def update(sentence: String*): Unit = sentence.foreach(trie += _) 
} 

и некоторые другие являются каскадные модификации:

/** 
    * This auto-completer will try returning some suggestions when the prefix did not match any known word by dropping the 
    * last character until it finds a suggestion 
    */ 
trait TolerantAutoCompleter extends AutoCompleter { 
    def MaxRetries: Int 

    abstract override def topSuggestions(prefix: String): Iterator[String] = { 
    if (MaxRetries < 1) throw new IllegalArgumentException("Should allow 1 retry minimum, but max retries was: " + MaxRetries) 
    for (attempt <- 0 to Math.min(prefix.length, MaxRetries)) { 
     val suggestions = super.topSuggestions(prefix.substring(0, prefix.length - attempt)) 
     if (suggestions.hasNext) return suggestions 
    } 
    Iterator() 
    } 
} 

И я использую их следующим образом:

val autoCompleter = new ParrotAutoCompleter with TolerantAutoCompleter { override val MaxRetries: Int = 5 } 

Эта реализация работает отлично, но у нее есть недостаток: проверка работоспособности, выполняемая на MaxRetries, выполняется позднее, только при использовании автокомплексора, а не при его создании. Более анекдотически, он запускается каждый раз, а не только один раз.

Проблема заключается в том, что любой код вне метода в черте запускается на выполнение сразу же, еще до MaxRetries был переопределен (независимо от того, объявлен как val или def).

Как я могу выполнить проверку работоспособности во время строительства, после переопределения и не теряя свойство быть штабелируемой модификацией?

ответ

2

Когда вы override val MaxRetries = 5, вы создаете поле, которое инициализируется только в конструкторе анонимного класса. Поток конструктора выглядит следующим образом:

<jvm>: Initialize MaxRetries field to 0 
<anon>: call super 
TolerantAutoCompleter: call super 
... 
TolerantAutoCompleter: sanity check MaxRetries (still 0!) 
TolerantAutoCompleter: return 
<anon>: set MaxRetries to 5 
<anon>: return 

Использование

new ParrotAutoCompleter with TolerantAutoCompleter { override def MaxRetries = 5 } 

def) вместо этого.

+0

Кажется, что ваш ответ правильный, но способ, которым вы его создавали, я думаю, что объяснение остается неясным. Я точно не знаю, для какого кода вы описываете этот поток (хотя, я думаю, вы говорите о моем фрагменте, за исключением того, что вы перемещаете проверку работоспособности в теле объекта), и что вы подразумеваете под «anon». , Не могли бы вы прояснить эти два вопроса? – Dici

+0

Это более стандартно использовать 'lazy val', чтобы избежать перерасчета (хотя в конкретном случае литерала' def' будет лучше). –

+0

@AlexeyRomanov Я действительно пробовал использовать 'lazy' в декларации признаков и не компилировался, но я понимаю, что должен был использовать его в конкретном использовании признака. Будет ли это исправлять проблему (не можете попробовать прямо сейчас)? Если да, не могли бы вы добавить ответ, описывающий правила порядка инициализации в этом контексте? – Dici