Скрытие унаследованной реализации родительского класса case в 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 && 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), но есть ли способ изменить этот код, чтобы можно было прикрепить это случайное целое число?

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

Bogdan Vakulenko 10.04.2019 17:40

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

Geoffrey Saunders 10.04.2019 17:46

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

Tim 10.04.2019 17:51

@ Тим, этот случай гипотетический, поэтому не думайте, что это именно то, что я ищу :)

Geoffrey Saunders 10.04.2019 17:53

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

Tim 10.04.2019 17:56

@Tim Функция RandomInt - это просто пример - я хочу иметь сложную функцию, которая вычисляет значение перекоса на основе других параметров дочернего класса, например: MyClass1(value1: Int, value2: Int, skew: Int = ComplexFunc(value1, value2)), но я также хочу иметь возможность делать MyClass2(value1: Int, value2: Int, value3: Int, skew: Int = ComplexFunc(value1, value2, value2)

Geoffrey Saunders 10.04.2019 17:59

@ Тим, я добавил дополнительное уточнение к вопросу - теперь он гораздо более конкретен.

Geoffrey Saunders 10.04.2019 18:16

@GeoffreySaunders Я сомневаюсь, что это возможно реализовать так, как вы просили, потому что вам нужно skew как собственное поле класса case, но вы не можете расширить класс case за классом case. Таким образом, кажется, что единственный способ скрыть поле skew от вызывающей стороны — это реализовать метод apply в сопутствующем объекте для каждого из ваших новых классов case, потому что это единственное место, где доступны все параметры, и вы можете использовать их для передачи вам ComplexFunc .

Bogdan Vakulenko 10.04.2019 20:25

Должно ли значение skew влиять на проверку равенства для класса? Если я создам val z = MyClass1(value1 = 52, value2 = 63), должно ли y == z быть true или false, или это не имеет значения?

Tim 11.04.2019 08:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
160
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

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, но не обязательно какие-либо другие детали. Лучшее решение — просто использовать здесь трейт.

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) && (v2 != 700)
}

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

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

Tim 11.04.2019 09:00

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

Geoffrey Saunders 17.04.2019 18:36

Если вы хотите скрыть детали реализации 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 && 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
Ответ принят как подходящий

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

def computeSkew(value1: Int, value2: Int) =
  if (value1 == 500 && 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 к основным объектам данных — это правильный способ решения проблемы дублирования. Было бы лучше обернуть skew вокруг объекта при использовании его в таблице и сохранить исходные данные нетронутыми.

Простая оболочка будет выглядеть так:

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 в таблице, чтобы избежать дублирования, и извлекаете поле data, когда вы снова удаляете строки из таблицы.


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

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 и позволяет при необходимости адаптировать расчет перекоса к каждому конкретному классу.

Итак, по сути, в последнем ответе все, что нужно сделать классу, — это расширить WithSkew и, при необходимости, переопределить функцию перекоса, чтобы выполнить некоторые другие вычисления перекоса?

Geoffrey Saunders 17.04.2019 18:50

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

Tim 17.04.2019 19:21

Другие вопросы по теме