Digamos que tenemos una clase base (rasgo) con dos métodos (mismo nombre, parámetros diferentes y no implementados):

trait Base {
    def compute(arg1:type1, arg2:type2): returnType
    def compute(arg1:type1, arg2:type2, arg3:type3): returnType
}

Y tenemos algunas clases heredadas de Base. Supongamos que son A, B, C. A y B implementan "cálculo" con dos argumentos, mientras que C implementa "cálculo" con tres argumentos.

class A  extends Base {
    def compute(arg1:type1, arg2:type2): returnType = {
        //detailed implementations
    }
}

class B  extends Base {
    def compute(arg1:type1, arg2:type2): returnType = {
        //detailed implementations
    }
}

class C  extends Base {
    def compute(arg1:type1, arg2:type2, arg3:type3): returnType = {
        //detailed implementations
    }
}

Ahora, tengo un conjunto de estos objetos objs y quiero elegir automáticamente qué versión de "cálculo" usar:

val name = objs.map{ x =>
    val name = x.getClass.getSimpleName
    name match {
        case "C": x.compute(arg1, arg2, arg3)
        case _: x.compute(arg1, arg2)
    }
}

Sin embargo, compila el error:

class A(B the same) needs to be abstract, since method compute in trait Base of type (three parameters) is not defined

Estoy confundido con este error. ¿Es porque todos los métodos en Base deben implementarse en sus clases secundarias (A, B, C)?

¿Hay alguna solución elegante sin editar la clase A y la clase B (porque la clase C se diseñó más recientemente y es compute debe agregar un parámetro más, por lo que diseñé una función más arriba)?

0
user3162587 17 oct. 2017 a las 18:40

3 respuestas

La mejor respuesta

De acuerdo con @ Mik378. Pero si está en el proceso de migración de la versión de 2 args a la versión de 3 args, puede:

trait Base {
  // Mark this as deprecated
  // No default implementation here because otherwise, A & B would need to 
  // be modified to add the 'override' keyword
  @deprecated
  def compute(arg1:type1, arg2:type2): returnType

  // Provide a default implementation for old implementations e.g. A / B
  def compute(arg1:type1, arg2:type2, arg3:type3): returnType =
    compute(arg1, arg2)
}

// Convenience base class for new implementations e.g. C
abstract class NewBase extends Base {
  override def compute(arg1: type1, arg2: type2): returnType =
    throw new UnsupportedOperationException
}

class A  extends Base {
  def compute(arg1:type1, arg2:type2): returnType = {
    //detailed implementations
  }
}

class B  extends Base {
  def compute(arg1:type1, arg2:type2): returnType = {
    //detailed implementations
  }
}

// All new implementations extend 'NewBase' instead of 'Base'
class C  extends NewBase {
  override def compute(arg1:type1, arg2:type2, arg3:type3): returnType = {
    //detailed implementations
  }
}

Y ahora, puede usar la versión de 3 args para objs antiguos y nuevos,

val name = objs.map(_.compute(arg1, arg2, arg3))
1
PH88 18 oct. 2017 a las 02:27

¿Has oído hablar del principio de segregación de interfaz ?

El principio de segregación de interfaz (ISP) establece que ningún cliente debe ser forzado a depender de métodos que no utiliza. 1 Divisiones de ISP interfaces que son muy grandes en otras más pequeñas y más específicas que los clientes solo tendrán que saber sobre los métodos que son de interés para ellos. Estas interfaces reducidas también se denominan rol interfaces

Fuente: Wikipedia

Los rasgos son de alguna manera similares a los denominados "interfaces".

Básicamente, debes dividir el rasgo Base.
Los rasgos representan módulos en Scala y es una buena práctica mantenerlos pequeños para que podamos aumentar su capacidad de combinarse y obtener abstracciones más grandes.

Terminarías con dos Rasgos: (Simplemente modifiqué el nombre para que sea más claro)

trait Computation {
  def compute(arg1:Int, arg2:Int): Unit
}

trait SpecificComputation {
  def compute(arg1:Int, arg2:Int, arg3:Int)
}

class A extends Computation {
  def compute(arg1:Int, arg2:Int) = {
    //detailed implementations
  }
}

class B  extends Computation {
  def compute(arg1:Int, arg2:Int) = {
    //detailed implementations
  }
}

class C extends SpecificComputation {
  def compute(arg1:Int, arg2:Int, arg3:Int) = {
    //detailed implementations
  }
}

Si desea un class D que debe conocer esas dos variantes de método compute, escriba:

class D extends SpecificComputation with Computation {

      def compute(arg1:Int, arg2:Int) = {
        //detailed implementations
      }

      def compute(arg1:Int, arg2:Int, arg3:Int) = {
        //detailed implementations
      }
}
1
Mik378 17 oct. 2017 a las 21:00

Deberá definir compute(arg1:type1, arg2:type2, arg3:type3) en A y B y definir compute(arg1:type1, arg2:type2) en C o puede proporcionar una implementación predeterminada y no operativa en su rasgo

trait Base {
    def compute(arg1:type1, arg2:type2) {}
    def compute(arg1:type1, arg2:type2, arg3:type3) {}
}

También recomendaría definir el tipo de retorno explícitamente en Base

Editar

Un ejemplo de trabajo completo (simplificado) usando clases de casos:

trait Base {
  def compute(arg1: Int, arg2: Int): Int = 0
  def compute(arg1: Int, arg2: Int, arg3: Int): Int = 0
}

case class A() extends Base {
  override def compute(arg1: Int, arg2: Int): Int = arg1 + arg2
}

case class B() extends Base {
  override def compute(arg1: Int, arg2: Int): Int = arg1 - arg2
}

case class C() extends Base {
  override def compute(arg1: Int, arg2: Int, arg3: Int): Int = arg1 + arg2 - arg3
}

case class D(arg1: Int, arg2: Int, arg3: Int, objs: Seq[Base]) {
  val computed = objs map (_ match {
    case x: C    => x.compute(arg1, arg2, arg3)
    case x: Base => x.compute(arg1, arg2)
  })
}
1
Shane Perry 17 oct. 2017 a las 20:34