2011-10-08 14 views
11

Actualmente estoy experimentando con Scala y buscando las mejores prácticas. Me encontré teniendo dos enfoques opuestos para resolver un solo problema. Me gustaría saber qué es mejor y por qué, qué es más convencional, y si tal vez conoces otros enfoques mejores. El segundo me parece más bonito.Mejores prácticas de Scala: herencia de rasgo frente a enumeración

1. Enumeración basada en la solución

import org.squeryl.internals.DatabaseAdapter 
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter} 
import java.sql.Driver 

object DBType extends Enumeration { 
    val MySql, PostgreSql, H2 = Value 

    def fromUrl(url: String) = { 
    url match { 
     case u if u.startsWith("jdbc:mysql:") => Some(MySql) 
     case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql) 
     case u if u.startsWith("jdbc:h2:") => Some(H2) 
     case _ => None 
    } 
    } 
} 

case class DBType(typ: DBType) { 
    lazy val driver: Driver = { 
    val name = typ match { 
     case DBType.MySql => "com.mysql.jdbc.Driver" 
     case DBType.PostgreSql => "org.postgresql.Driver" 
     case DBType.H2 => "org.h2.Driver" 
    } 
    Class.forName(name).newInstance().asInstanceOf[Driver] 
    } 
    lazy val adapter: DatabaseAdapter = { 
    typ match { 
     case DBType.MySql => new MySQLAdapter 
     case DBType.PostgreSql => new PostgreSqlAdapter 
     case DBType.H2 => new H2Adapter 
    } 
    } 
} 

2. solución basada en Singleton

import org.squeryl.internals.DatabaseAdapter 
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter} 
import java.sql.Driver 

trait DBType { 
    def driver: Driver 
    def adapter: DatabaseAdapter 
} 

object DBType { 
    object MySql extends DBType { 
    lazy val driver = Class.forName("com.mysql.jdbc.Driver").newInstance().asInstanceOf[Driver] 
    lazy val adapter = new MySQLAdapter 
    } 

    object PostgreSql extends DBType { 
    lazy val driver = Class.forName("org.postgresql.Driver").newInstance().asInstanceOf[Driver] 
    lazy val adapter = new PostgreSqlAdapter 
    } 

    object H2 extends DBType { 
    lazy val driver = Class.forName("org.h2.Driver").newInstance().asInstanceOf[Driver] 
    lazy val adapter = new H2Adapter 
    } 

    def fromUrl(url: String) = { 
    url match { 
     case u if u.startsWith("jdbc:mysql:") => Some(MySql) 
     case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql) 
     case u if u.startsWith("jdbc:h2:") => Some(H2) 
     case _ => None 
    } 
    } 
} 

Respuesta

12

Si se declara una sealed trait DBType, que puede coincidir con el patrón en él con exhaustividad comprobación (es decir, Scala le dirá si olvida un caso).

De todos modos, no me gusta el Enumeration de Scala, y no estoy solo en eso. Nunca lo uso, y si hay algo para lo cual la enumeración es realmente la solución más limpia, es mejor simplemente escribirla en Java, usando la enumeración de Java.

+0

Estoy totalmente de acuerdo. Las enumeraciones de Scala son absolutamente inútiles. Solo proporcionan autogeneración de valores secuenciales, lo que dudo que alguien necesite en absoluto. Contrariamente, no hay una buena forma de buscar un valor por ID de cadena (la reflexión se emplea debajo) y no existe una forma legal de resolver la enumeración a partir del valor de enumeración #. –

4

me gustaría ir para la variante de producto único, ya que permite la subclasificación más clara.

También es posible que tenga que hacer cosas/anulaciones específicas de DB, ya que algunas consultas/subconsultas/operadores pueden ser diferentes.

Pero me gustaría probar algo como esto:

import org.squeryl.internals.DatabaseAdapter 
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter} 
import java.sql.Driver 

abstract class DBType(jdbcDriver: String) { 
    lazy val driver = Class.forName(jdbcDriver).newInstance().asInstanceOf[Driver] 
    def adapter: DatabaseAdapter 
} 


object DBType { 
    object MySql extends DBType("com.mysql.jdbc.Driver") { 
    lazy val adapter = new MySQLAdapter 
    } 

    object PostgreSql extends DBType("org.postgresql.Driver") { 
    lazy val adapter = new PostgreSqlAdapter 
    } 

    object H2 extends DBType("org.h2.Driver") { 
    lazy val adapter = new H2Adapter 
    } 

    def fromUrl(url: String) = { 
    url match { 
     case _ if url.startsWith("jdbc:mysql:") => Some(MySql(url)) 
     case _ if url.startsWith("jdbc:postgresql:") => Some(PostgreSql(url)) 
     case _ if url.startsWith("jdbc:h2:") => Some(H2(url)) 
     case _ => None 
    } 

} 

si esto ayudó, por favor considere a este :) 1

+0

Los rasgos no pueden tener parámetros. ¿Se suponía que era una clase abstracta? –

+0

sí, lo siento, acabo de copiar el código y me olvidé del rasgo allí. –

+0

Quiere decir que copió el código de mojojojo. –

10

Para este caso particular realmente no necesita clases para cada tipo de base de datos; solo son datos. A menos que el caso real sea dramáticamente más complejo, usaría una solución basada en el análisis de secuencia y mapa para minimizar la cantidad de duplicación de código:

case class DBRecord(url: String, driver: String, adapter:() => DatabaseAdapter) {} 

class DBType(record: DBRecord) { 
    lazy val driver = Class.forName(record.driver).newInstance().asInstanceOf[Driver] 
    lazy val adapter = record.adapter() 
} 

object DBType { 
    val knownDB = List(
    DBRecord("mysql", "com.mysql.jdbc.Driver",() => new MySQLAdapter), 
    DBRecord("postgresql", "org.postgresql.Driver",() => new PostgreSqlAdapter), 
    DBRecord("h2", "org.h2.Driver",() => new H2Adapter) 
) 

    val urlLookup = knownDB.map(rec => rec.url -> rec).toMap 

    def fromURL(url: String) = { 
    val parts = url.split(':') 
    if (parts.length < 3 || parts(0) != "jdbc") None 
    else urlLookup.get(parts(1)).map(rec => new DBType(rec)) 
    } 
}