Scala - Как избежать условия if / else для фабрики объектов - PullRequest
3 голосов
/ 20 апреля 2020

Я пытаюсь решить следующую проблему. Проблема

  • Мне нужно написать copy method для копирования из одной файловой системы в другую. (то есть локально для hdfs, от s3 до s3 и еще немного позже).
  • Эта файловая система (локальная, s3, hdfs) может увеличиться в будущем, также как и действия (копировать, переместить, удалить)
  • некоторые действия являются кросс-файловой системой (т.е. копирование, перемещение) некоторые не являются т. е. (Удалить, Список, Найти)
  • У меня есть файл свойств, который содержит местоположение источника и места назначения, а также некоторые другие поля (например, счетчик), которые помогают мне понять, куда копировать файл.

Я попытался решить проблему с помощью Factory следующим образом, однако он все еще не может решить проблему с кроссплатформенным действием. И код не выглядит элегантно.

Реализация

abstract class FileSystem(propFileURI: String) {
  def moveFile(): Unit
}

object FileSystem {

  private class HDFSystem(propFileURI: String) extends FileSystem(propFileURI) {
    override def moveFile(): Unit = {
      println(" HDFS  move file")
    }
  }

  private class S3System(propFileURI: String) extends FileSystem(propFileURI) {
    override def moveFile(): Unit = {
      println("S3 Move File ")
    }
  }

  def apply(propFileURI: String): Option[FileSystem] = {
    val properties: Properties = new Properties()

    val source = Source.fromFile( System.getProperty("user.dir")+"\\src\\main\\resources\\"+propFileURI).reader
    properties.load(source)
    val srcPath = properties.getProperty("srcPath")
    val destPath = properties.getProperty("destPath")

    if (destPath.contains("hdfs")){
       Some(new HDFSystem(propFileURI))
    }
    if (srcPath.contains("s3") && destPath.contains("s3")){
      Some(new S3System(propFileURI))
    }else{
       None
    }

  }

  def main(args: Array[String]): Unit = {
    val obj = FileSystem("test.properties")
    obj match {
      case Some(test) => test.moveFile()
      case None => println("None returned")
    }
  }
}

Вопрос:

  1. текущая реализация moveFile только ручки s3->s3 и hdfs->hdfs. Как реализовать тот же метод для local->hdfs и local->s3

  2. Как переместить HDFSystem и S3System в отдельный файл?

  3. Как избежать if/else в apply методе?

Ответы [ 2 ]

1 голос
/ 20 апреля 2020

Я бы отделил создание FileSystem, которое может перемещать файлы внутри одной и той же файловой системы, и создание, которое может делать это между файловыми системами.

Для простой реализации файловой системы, я бы создал sealed trait с различными системами:

sealed trait FileSystem {
  def moveFile(path: String)
}
object FileSystem {
  class HDFSSystem extends FileSystem {
    override def moveFile(path: String): Unit = ???
  }

  class S3FileSystem extends FileSystem {
    override def moveFile(path: String): Unit = ???
  }

  def apply(path: String): Either[Throwable, FileSystem] = {
    val properties: Properties = new Properties()
    properties.load(
      Source
        .fromFile(s"${System.getProperty("user.dir")}\\src\\main\\resources\\$path")
        .reader
    )

    val srcPath = properties.getProperty("srcPath")
    val destPath = properties.getProperty("destPath")

    if (!srcPath.equalsIgnoreCase(destPath))
      Left(new Exception("Source and dest paths should be equal"))
    else {
      path.toLowerCase() match {
        case s3 if s3.startsWith("s3")       => Right(new S3FileSystem)
        case hdfs if hdfs.startsWith("hdfs") => Right(new HDFSSystem)
        case _                               => Left(new Exception(s"Received unknown file system prefix: $path"))
      }
    }
  }
}

Для многократной передачи файловой системы я бы использовал другую абстракцию, заключающую в себе FileSystem. Вот эскиз:

abstract class MultiFileSystemTransfer[A <: FileSystem, B <: FileSystem](
  val srcSystem: A,
  val dstSystem: B
) {
  def moveFile(srcPath: String, dstPath: String): Unit
}

object MultiFileSystemTransfer {
  class S3ToS3FileSystemTransfer private
      extends MultiFileSystemTransfer[FileSystem.S3FileSystem, FileSystem.S3FileSystem] {
    override def moveFile(srcPath: String, dstPath: String): Unit = ???
  }
}

Мы можем еще больше улучшить пути, которые на самом деле берутся из базовых файловых систем, предоставляемых с использованием Тип членов :

sealed trait FileSystem {
  type Path
  def moveFile(path: Path)
}
object FileSystem {
  class HDFSSystem extends FileSystem {
    type Path = String
    override def moveFile(path: Path): Unit = ???
  }
}

abstract class MultiFileSystemTransfer[A <: FileSystem, B <: FileSystem](
  val srcSystem: A,
  val dstSystem: B
) {
  def moveFile(srcPath: srcSystem.Path, dstPath: dstSystem.Path): Unit
}

object MultiFileSystemTransfer {
  class S3ToS3FileSystemTransfer(srcPath: FileSystem.S3FileSystem, dstPath: FileSystem.S3FileSystem)
      extends MultiFileSystemTransfer[FileSystem.S3FileSystem, FileSystem.S3FileSystem](
        srcPath,
        dstPath
      ) {
    override def moveFile(srcPath: srcSystem.Path, dstPath: dstSystem.Path): Unit = ???
  }
}
1 голос
/ 20 апреля 2020

Вы можете заменить if-else на сопоставление с образцом. Но это не просто заявление if-else, верно? Так что это можно записать следующим образом:


sealed abstract class FileSystem(propFileURI: String) {
  def moveFile(): Unit
}

case class HDFSystem(propFileURI: String) extends FileSystem(propFileURI) {
  override def moveFile(): Unit =
    println(" HDFS  move file")
}

case class S3System(propFileURI: String) extends FileSystem(propFileURI) {
  override def moveFile(): Unit =
    println("S3 Move File ")
}
case class MoveFile(hdfs: Option[HDFSystem] = None, s3: Option[S3System] = None)

object FileSystem {

  def apply(propFileURI: String): MoveFile = {
    val properties: Properties = new Properties()
    val source = Source.fromFile(System.getProperty("user.dir") + "\\src\\main\\resources\\" + propFileURI).reader
    properties.load(source)

    val srcPath = Option(properties.getProperty("srcPath")).fold(false)(_.contains("hdfs"))
    val destPath = Option(properties.getProperty("destPath")).fold(false)(_.contains("s3"))

    (destPath, srcPath) match {
      case (true, true) =>
        MoveFile(
          hdfs = Option(HDFSystem(propFileURI)),
          s3 = Option(S3System(propFileURI))
        )
      case (false, true) =>
        MoveFile(s3 = Option(S3System(propFileURI)))
      case (true, false) =>
        MoveFile(hdfs = Option(HDFSystem(propFileURI)))
      case _ =>
        MoveFile()
    }
  }
}

object TestObj {

  def main(args: Array[String]): Unit = {
    val obj = FileSystem("test.properties")
    (obj.hdfs, obj.s3) match {
      case (Some(hdfs), _) => hdfs.moveFile()
      case (_, Some(s3)) => s3.moveFile()
      case (_, _) => println("None returned")
    }
  }
}

Честно говоря, мне не нравится вышеприведенная реализация и она немного изменена для варианта использования ниже. Вы можете использовать их как ADT без MoveFile оболочки:


def testMethod(fs: FileSystem): Unit = {
  fs.moveFile()
}

def main(args: Array[String]): Unit = {
// You can have a logic here for which way to go
  val obj = S3System("test.properties")
  testMethod(obj)
  val obj1 = HDFSystem("test.properties")
  testMethod(obj1)
}

В этом случае вы можете полностью удалить FileSystem объект. Если вы хотите иметь некоторую проверку пути, вы можете иметь их внутри каждого sub-type s. HdfsSystem и S3Sytem должны реализовывать moveFile метод

...