控制抽象

作者: 刘光聪 | 来源:发表于2016-07-10 11:22 被阅读733次

    根据正交设计的基本原则,如果设计出现重复的控制逻辑,可抽象出稳定的抽象;借助于Scala强大的可扩展能力,可以将「小括号」神奇地转换为「大括号」,让用户代码感觉是一种新的控制结构。

    本文通过一个简单的例子,通过若干迭代,遵循正交设计的基本原则,灵活地应用重构,逐渐改进设计,以供参考。

    需求1:搜索目录下扩展名为.scala的所有文件

    快速实现

    object FileMatchers {
      def ends(file: File, ext: String) = {
        for (file <- file.listFiles if file.getName.endsWith(ext))
          yield filelist
      }
    }
    

    需求2:搜索目录下名字包含Test的所有文件

    重复

    object FileMatcher {
      def ends(file: File, ext: String) = {
        for (file <- file.listFiles if file.getName.endsWith(ext))
          yield file
      }
      
      def contains(file: File, query: String) = {
        for (file <- file.listFiles if file.getName.contains(query))
          yield file
      }
    }
    

    需求3:搜索目录下名字正则匹配特定模式的所有文件

    再现重复

    object FileMatcher {
      def ends(file: File, ext: String) = {
        for (file <- file.listFiles if file.getName.endsWith(ext))
          yield file
      }
      
      def contains(file: File, query: String) = {
        for (file <- file.listFiles if file.getName.contains(query))
          yield file
      }
          
      def matches(file: File, regex: String) = {
        for (file <- file.listFiles if file.getName.matches(regex))
          yield file
      }
    }
    

    提取抽象

    消除消除上述实现的重复,最重要的是提取公共的关注点: Matcher: (String, String) => Boolean

    object FileMatcher {
      private def list(file: File, query: String, 
        matcher: (String, String) => Boolean) = {
        for (file <- file.listFiles if matcher(file.getName, query))
          yield file
      }
    
      def ends(file: File, ext: String) =
        list(file, ext, (fileName, ext) => fileName.endsWith(ext))
        
      def contains(file: File, query: String) =
        list(file, query, (fileName, query) => fileName.contains(query))
              
      def matches(file: File, regex: String) =
        list(file, regex, (fileName, regex) => fileName.matches(regex))
    }
    

    类型推演

    借助于Scala强大的类型推演能力,可以得到更为简洁的函数字面值。

    object FileMatcher {
      private def list(file: File, query: String, 
        matcher: (String, String) => Boolean) = {
        for (file <- file.listFiles if matcher(file.getName, query))
          yield file
      }
    
      def ends(file: File, ext: String) =
        list(file, ext, _.endsWith(_))
        
      def contains(file: File, query: String) =
        list(file, query, _.contains(_))
              
      def matches(file: File, regex: String) =
        list(file, regex, _.matches(_))
    }
    

    类型别名

    list的参数由于类型修饰,显得有点过长而影响阅读;可以通过「类型别名」的机制缩短函数的类型修饰符,以便改善表达力。

    object FileMatcher {
      private type Matcher = (String, String) => Boolean
    
      private def list(file: File, query: String, matcher: Matcher) = {
        for (file <- file.listFiles if matcher(file.getName, query))
          yield file
      }
    
      def ends(file: File, ext: String) =
        list(file, ext, _.endsWith(_))
        
      def contains(file: File, query: String) =
        list(file, query, _.contains(_))
              
      def matches(file: File, regex: String) =
        list(file, regex, _.matches(_))
    }
    

    简化参数

    简化参数传递,消除不必要的冗余,是简单设计基本原则之一。

    object FileMatcher {
      private type Matcher = String => Boolean
    
      private def list(file: File, matcher: Matcher) = {
        for (file <- file.listFiles if matcher(file.getName))
          yield file
      }
    
      def ends(file: File, ext: String) =
        list(file, _.endsWith(ext))
        
      def contains(file: File, query: String) =
        list(file, _.contains(query))
              
      def matches(file: File, regex: String) =
        list(file, _.matches(regex))
    }
    

    替换for comprehension

    可以通过定制「高阶函数」替代语法较为复杂的「for comprehension」,以便改善表达力。

    object FileMatcher {
      private type Matcher = String => Boolean
    
      private def list(file: File, matcher: Matcher) =
        file.listFiles.filter(f => matcher(f.getName))
    
      def ends(file: File, ext: String) =
        list(file, _.endsWith(ext))
        
      def contains(file: File, query: String) =
        list(file, _.contains(query))
              
      def matches(file: File, regex: String) =
        list(file, _.matches(regex))
    }
    

    柯里化

    应用「柯里化」,漂亮的「大括号」终于登上了舞台。

    object FileMatcher {
      private type Matcher = String => Boolean
    
      def list(file: File)(matcher: Matcher) =
        file.listFiles.filter(f => matcher(f.getName))
    
      def ends(file: File, ext: String) =
        list(file) { _.endsWith(ext) }
    
      def contains(file: File, query: String) =
        list(file) { _.contains(query) }
    
      def matches(file: File, regex: String) =
        list(file) { _.matches(regex) }
    }
    

    相关文章

      网友评论

      • _张逸_:一切皆在于函数的抽象能力。我个人比较喜欢的表达是,如何将多个具体的场景抽象为不带领域含义,而只是一种抽象的类型转换。当你捕捉到这个transform时,就是function粉墨登场的时候了。如果采用这种方式,那么中间的“简化参数”环节基本上就可以忽略了,因为你要抽象的就是将File转换为Boolean,如此而已。至于curry,不过是Scala(或者所有实现FP语言)对你的额外奖赏。它与你这里讲的抽象无关,但Curry比较重要的一点是,它其实满足了可以将所有函数(无论有多少入参)都视为一个类型到另一个类型的转换。
        刘光聪:@逸見 不带领域的类型转换,复用性价值很大,感谢逸哥的点评。

      本文标题:控制抽象

      本文链接:https://www.haomeiwen.com/subject/vvddjttx.html