Скрытие унаследованной реализации родительского класса case в scala

#scala

#scala

Вопрос:

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

Предположим, у вас есть много идентичных строк в таблице — чтобы отличить одну строку от другой, мы хотели бы потенциально добавить столбец случайных чисел, который может помочь предотвратить искажение данных. Например:

 --------------------------
|         MyClass1       |
--------------------------
| value1 | value2 | skew |
--------------------------
|   4    |   6    | 4962 |
--------------------------
|   4    |   6    | 6510 |
--------------------------
|   500  |   700  | 0    |
--------------------------
|   500  |   700  | 0    |
--------------------------
  

В этом случае перекос помогает предотвратить скопление данных в одном месте.

Можно было бы сделать следующее:

 case class MyClass1(value1: Int, value2: Int, skew: Int)
  

и создание экземпляра искажения будет оставлено вызывающему, т.е.:

 MyClass1(value1 = 4, value2 = 6, skew = ComplexFunc(value1, value2))
  

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

 case class SkewClass(skew: Int)
object SkewClass {
    def apply(skewCondition: Boolean) : SkewClass = {
       if (skewCondition) SkewClass(RandomInt()) else SkewClass(0)
    }
}

case class MyClass1 extends SkewClass(
    val value1 : Int = 0
    val value2 : Int = 0
    val skew : Int = this.apply(value1 != 500 amp;amp; value2 != 700)
}
  

Короче говоря, я хочу иметь возможность создать экземпляр MyClass1 с потенциально случайным целым числом, прикрепленным в конце, называемым «перекос». т.е.:

 scala> val x = MyClass1(value1 = 500, value2 = 700)
x: MyClass1 = MyClass1(500, 700, 0)

scala> val y = MyClass1(value1 = 52, value2 = 63)
y: MyClass1 = MyClass1(52, 63, 5347)

scala>
  

Очевидно, что приведенный выше код не компилируется (я все еще новичок в scala), но есть ли способ изменить этот код, чтобы разрешить привязку к этому случайному целому числу?

Комментарии:

1. работает ли это для вас со значением по умолчанию для skew параметра case class MyClass1(value1: Int, value2: Int, skew: Int = RandomInt) ?

2. @BogdanVakulenko Извините, я не думаю, что я был достаточно ясен. Я отредактировал вопрос, чтобы он был более четким

3. Можете ли вы объяснить, почему вы хотите различать одинаковые строки? Могут быть другие способы сделать это (например count , поле, а не несколько строк), поэтому это похоже на вопрос XY .

4. @Tim этот случай гипотетический, поэтому не думайте, что это именно то, что я ищу 🙂

5. @BogdanVakulenko уже дал решение вашего вопроса tl; dr , поэтому неясно, что еще вы ищете.

Ответ №1:

Вы не можете создать a case class с полями, которые инициализируются из других полей, но вы можете использовать объект класса для этого. Начните с функции для вычисления перекоса:

 def computeSkew(value1: Int, value2: Int) =
  if (value1 == 500 amp;amp; value2 == 700) 0 else Random.nextInt()
  

Затем используйте объект класса для создания его экземпляров:

 case class MyClass1 private(value1: Int, value2: Int, skew: Int)

object MyClass1 {
  def apply(value1: Int, value2: Int): MyClass1 =
    MyClass1(value1, value2, computeSkew(value1, value2))
}
  

Другой вариант — добавить поле, расширив Skew класс, но объекты с одинаковыми value1 и value2 всегда будут сравниваться как равные, потому skew что они не будут включены в equals тест.


Как упоминалось в комментариях, я не уверен, что добавление skew к объектам core data является правильным способом решения проблемы дублирования. Было бы лучше обернуть перекос вокруг объекта при его использовании в таблице и сохранить исходные данные нетронутыми.

Простая оболочка будет выглядеть следующим образом:

 case class WithSkew[T] private(data: T, skew: Int)

object WithSkew {
  def apply[T](data: T): WithSkew[T] =
    WithSkew(data, Random.nextInt())
}

val x = MyClass1(500, 700)
val xSkew = WithSkew(x)
  

Затем вы используете xSkew in в таблице, чтобы избежать дублирования, и извлекаете data поле, когда вы снова извлекаете строки из таблицы.


В предыдущей версии не выполнялось пользовательское вычисление перекоса на основе value1 и value2 . Это можно решить, введя typeclass для вычисления перекоса для определенного класса:

 trait HasSkew[T] {
  def skew(instance: T): Int
}

case class DefaultSkew[T]() extends HasSkew[T] {
  def skew(instance: T): Int = Random.nextInt()
}

object HasSkew {
  implicit object skew1 extends HasSkew[MyClass1] {
    def skew(data: MyClass1): Int =
      computeSkew(data.value1, data.value2)
  }
}

case class WithSkew[T] private(data: T, skew: Int)

object WithSkew {
  def apply[T](data: T)(implicit sk: HasSkew[T] = DefaultSkew[T]()): WithSkew[T] =
    WithSkew(data, sk.skew(data))
}
  

WithSkew Оболочка будет использовать вычисление перекоса в DefaultSkew , если HasSkew для этого класса не существует неявного экземпляра.

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

Комментарии:

1. Итак, по сути, в последнем ответе все, что нужно сделать классу, это расширить с помощью skew и, при необходимости, переопределить функцию перекоса, чтобы выполнить какое-то другое вычисление перекоса?

2. Нет, вы вообще не касаетесь исходного класса. Вы используете WithSkew , чтобы обернуть исходный объект перед помещением его в таблицу точно так же, как и во второй версии. Последняя версия просто добавляет возможность создания пользовательского метода для вычисления перекоса для определенного класса.

Ответ №2:

В этом случае вы хотите, чтобы все ваши структуры совместно использовали свойство, Skew , но не обязательно какие-либо другие детали. Лучшее решение — просто использовать здесь признак.

 trait Skew {
  def skewCondition: Boolean
  lazy val skew: Int = if (skewCondition) RandomInt() else 0
}

case class AlwaysSkew(v1: Int, v2: Int) extends Skew {
  override val skewCondition: Boolean = true
}

case class ConditionalSkew(v1: Int, v2: Int) extends Skew {
  override val skewCondition: Boolean = (v1 != 500) amp;amp; (v2 != 700)
}

val x = ConditionalSkew(500, 700)
val y = ConditionalSkew(1234,5678)
x.skew //0
y.skew //A random Int
  

Комментарии:

1. Обратите внимание, что все экземпляры с одинаковыми value1 и value2 равны, даже если skew значение отличается. В каждом объекте данных также есть лишнее val skewCondition , которое кажется ненужным.

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

Ответ №3:

Я не совсем уверен, что вы здесь ищете, но, основываясь на вашем описании, это то, что я получил:

 class SkewedClass(values: Int*) {
    lazy val skew: Int = {
        values.sum * 1234
    }
}

case class MyClass1(value1: Int, value2: Int) extends SkewedClass(value1, value2)
case class MyClass2(value1: Int, value2: Int, value3: Int) extends SkewedClass(value1, value2, value3)
  

Замените реализацию skew любой сложной функцией, которую вы хотите использовать.

Ответ №4:

Если вы хотите скрыть детали реализации skew, вы можете использовать собственный тип scala.

 trait SkewTrait {
   def skew: Int
}


// package MyClass1
sealed class SkewClass1(value1: Int, value2: Int) {
this: SkewTrait =>
override def skew: Int = if (value1 == 500 amp;amp; value2 == 700) 3 else 0
}

case class MyClass1(value1:Int, value2: Int) extends SkewClass1(value1, value2) with SkewTrait


// package MyClass2
sealed class SkewClass2(value1: Int) {
   this: SkewTrait =>
   override def skew: Int = if (value1 == 300) 4 else 0
}

case class MyClass2(value1: Int, value2: Int) extends SkewClass2(value1) with SkewTrait


println(MyClass1(1,2).skew)
println(MyClass2(300,5).skew)
  

Результат:

 0
4