【问题标题】:Scala - How to avoid if/else condition for object factoryScala - 如何避免对象工厂的 if/else 条件
【发布时间】:2020-04-20 05:49:30
【问题描述】:

我正在尝试解决以下问题。 问题

  • 我必须写一个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->s3hdfs->hdfs。如何为local->hdfslocal->s3 实现相同的方法

  2. 如何将HDFSystemS3System 移动到单独的文件中?

  3. 如何在apply方法中避免if/else

【问题讨论】:

  • 您能详细说明您的第一点吗? “实际上并未提及源位置”是什么意思?另外,为什么需要将实现移动到单独的文件中?
  • 请查看类似的解决方案。也许它可以提供帮助:github.com/apache/carbondata/blob/master/core/src/main/java/org/…。请查看方法定义:getFileTypeWithActualPath、接口 FileTypeInterface 和 FileTypeInterface 的实现(例如:DefaultFileTypeProvider)。
  • @YuvalItzchakov moveFile 当前支持源和目标的相同文件系统。我也想要跨平台moveFileHDFSystemS3System 的实现将在一段时间内增长,因此拥有单独的文件不是一个好主意吗?在 Java 中,我可以通过接口和类轻松实现这一点。
  • 您可以在 Scala 中创建具有与 Java 相同语义的接口(特征)或抽象类(如您所做的那样),这意味着在抽象类定义之外定义实现。

标签: scala design-patterns scalability


【解决方案1】:

您可以将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")
    }
  }
}

老实说,我不喜欢上面的实现,并为下面的用例做了一些修改。您可以在没有 MoveFile 包装器的情况下将它们用作 ADT:


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-types 中。 HdfsSystemS3Sytem 应该实现 moveFile 方法

【讨论】:

    【解决方案2】:

    我会分开创建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 = ???
      }
    }
    

    我们可以进一步改进路径实际上源自使用Type Members 提供的底层文件系统:

    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 = ???
      }
    }
    

    【讨论】:

    • 谢谢。这似乎是一个非常好的解决方案。你能告诉我如何隐藏创建对象的复杂性。这样用户就可以传递配置文件并获取适当的对象。
    • 最终当用户通过配置时,您会根据可用的系统进行模式匹配。您可以在MultiFileSystemTransfer.apply 方法(我没有添加)中完成所有这些逻辑。这样当用户想要创建一个正确的对象时,他们只需写val res = MultiFileSystemTransfer(src, dst)
    • 谢谢。您能否让我知道如何调用退出函数。抱歉,我是 scala 的新手。并且无法理解type,现在在FileSystem 中有moveFile
    猜你喜欢
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 2019-03-27
    • 2021-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多