Scala应用程序结构

我现在正在学习Scala,我想写一些像控制台Twitter客户端或者其他什么的傻小应用程序。 问题是,如何在磁盘上和逻辑上构build应用程序。 我知道python,在那里我只是创build一些类的文件,然后导入它们在主要模块像import util.sshfrom tweets import Retweet (强烈希望你不会介意的名字,他们只是供参考)。 但是,我应该如何使用Scala来做这件事呢? 另外,我对JVM和Java没有多less经验,所以在这里我是一个完整的新手。

在这里,我不同意延斯的观点,尽pipe不是那么多。

项目布局

我自己的build议是,你在Maven的标准目录布局上build模。

以前版本的SBT(在SBT 0.9.x之前)会为你自动创build:

 dcs@ayanami:~$ mkdir myproject dcs@ayanami:~$ cd myproject dcs@ayanami:~/myproject$ sbt Project does not exist, create new project? (y/N/s) y Name: myproject Organization: org.dcsobral Version [1.0]: Scala version [2.7.7]: 2.8.1 sbt version [0.7.4]: Getting Scala 2.7.7 ... :: retrieving :: org.scala-tools.sbt#boot-scala confs: [default] 2 artifacts copied, 0 already retrieved (9911kB/134ms) Getting org.scala-tools.sbt sbt_2.7.7 0.7.4 ... :: retrieving :: org.scala-tools.sbt#boot-app confs: [default] 15 artifacts copied, 0 already retrieved (4096kB/91ms) [success] Successfully initialized directory structure. Getting Scala 2.8.1 ... :: retrieving :: org.scala-tools.sbt#boot-scala confs: [default] 2 artifacts copied, 0 already retrieved (15118kB/160ms) [info] Building project myproject 1.0 against Scala 2.8.1 [info] using sbt.DefaultProject with sbt 0.7.4 and Scala 2.7.7 > quit [info] [info] Total session time: 8 s, completed May 6, 2011 12:31:43 PM [success] Build completed successfully. dcs@ayanami:~/myproject$ find . -type d -print . ./project ./project/boot ./project/boot/scala-2.7.7 ./project/boot/scala-2.7.7/lib ./project/boot/scala-2.7.7/org.scala-tools.sbt ./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt ./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4 ./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/compiler-interface-bin_2.7.7.final ./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/compiler-interface-src ./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/compiler-interface-bin_2.8.0.RC2 ./project/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.4/xsbti ./project/boot/scala-2.8.1 ./project/boot/scala-2.8.1/lib ./target ./lib ./src ./src/main ./src/main/resources ./src/main/scala ./src/test ./src/test/resources ./src/test/scala 

所以你会把你的源文件放在myproject/src/main/scala ,对于主程序,或者myproject/src/test/scala来进行testing。

既然这样不行,那么还有一些select:

giter8和sbt.g8

安装giter8 ,克隆ymasory的sbt.g8模板,并将其适应您的必需品,并使用它。 例如见下面这个使用未修改的ymasory的sbt.g8模板。 我认为这是开始新项目的最佳select之一,当你对所有项目都有一个很好的概念。

 $ g8 ymasory/sbt project_license_url [http://www.gnu.org/licenses/gpl-3.0.txt]: name [myproj]: project_group_id [com.example]: developer_email [john.doe@example.com]: developer_full_name [John Doe]: project_license_name [GPLv3]: github_username [johndoe]: Template applied in ./myproj $ tree myproj myproj ├── build.sbt ├── LICENSE ├── project │  ├── build.properties │  ├── build.scala │  └── plugins.sbt ├── README.md ├── sbt └── src └── main └── scala └── Main.scala 4 directories, 8 files 

np插件

使用softprops的np插件 。 在下面的例子中,插件是在~/.sbt/plugins/build.sbt ,它的设置在~/.sbt/np.sbt ,用标准的sbt脚本。 如果你使用paulp的sbt-extras,你需要在~/.sbt的正确的Scala版本子目录下安装这些东西,因为它为每个Scala版本使用不同的configuration。 在实践中,这是我最常使用的一个。

 $ mkdir myproj; cd myproj $ sbt 'np name:myproj org:com.example' [info] Loading global plugins from /home/dcsobral/.sbt/plugins [warn] Multiple resolvers having different access mechanism configured with same name 'sbt-plugin-releases'. To avoid conflict, Remove duplicate project resolvers (`resolvers`) or rename publishing resolver (`publishTo`). [info] Set current project to default-c642a2 (in build file:/home/dcsobral/myproj/) [info] Generated build file [info] Generated source directories [success] Total time: 0 s, completed Apr 12, 2013 12:08:31 PM $ tree . ├── build.sbt ├── src │  ├── main │  │  ├── resources │  │  └── scala │  └── test │  ├── resources │  └── scala └── target └── streams └── compile └── np └── $global └── out 12 directories, 2 files 

MKDIR

您可以简单地使用mkdir创build它:

 $ mkdir -p myproj/src/{main,test}/{resource,scala,java} $ tree myproj myproj └── src ├── main │  ├── java │  ├── resource │  └── scala └── test ├── java ├── resource └── scala 9 directories, 0 files 

源布局

现在,关于源代码布局。 Jensbuild议遵循Java风格。 那么,Java目录布局是一个需求 – 在Java中。 斯卡拉没有相同的要求,所以你可以select跟随或不跟随。

如果你遵循它,假设基础包是org.dcsobral.myproject ,那么这个包的源代码将放在myproject/src/main/scala/org/dcsobral/myproject/ ,等等子包。

与标准不同的两种常见方式是:

  • 省略基本软件包目录,并且只为子软件包创build子目录。

    例如,假设我有org.dcsobral.myproject.modelorg.dcsobral.myproject.vieworg.dcsobral.myproject.controller包,那么这个目录就是myproject/src/main/scala/modelmyproject/src/main/scala/viewmyproject/src/main/scala/controller

  • 把一切放在一起。 在这种情况下,所有源文件都在myproject/src/main/scala 。 这对于小型项目来说已经足够了。 实际上,如果你没有子项目,就和上面一样。

这处理目录布局。

文件名称

接下来,我们来讨论一下文件。 在Java中,实践是将每个类在自己的文件中分开,其名称将遵循类的名称。 在Scala中这也足够了,但是你必须注意一些例外。

首先,Scala有object ,Java没有。 一个同名的classobject被认为是同伴 ,这有一些实际的意义,但只有当它们在同一个文件中。 因此,将伴随类和对象放在同一个文件中。

其次,Scala有一个被称为sealed class (或trait )的概念,它将子类(或实现object )限制为在同一个文件中声明的类。 这主要是通过模式匹配和穷举检查来创build代数数据types。 例如:

 sealed abstract class Tree case class Node(left: Tree, right: Tree) extends Tree case class Leaf(n: Int) extends Tree scala> def isLeaf(t: Tree) = t match { | case Leaf(n: Int) => println("Leaf "+n) | } <console>:11: warning: match is not exhaustive! missing combination Node def isLeaf(t: Tree) = t match { ^ isLeaf: (t: Tree)Unit 

如果Tree没有被sealed ,那么任何人都可以扩展它,这样编译器就不可能知道比赛是否是详尽的。 无论如何, sealed课程在同一个文件中汇集在一起​​。

另一个命名约定是命名包含一个package object (对于该包)的文件package.scala

导入东西

最基本的规则是,在同一个包中的东西看到对方。 所以,把所有东西放在同一个包里,你不需要关心什么就可以看到什么。

但斯卡拉也有相对的参考和import。 这需要一点解释。 说我在我的文件的顶部有以下声明:

 package org.dcsobral.myproject package model 

以下所有内容将放在org.dcsobral.myproject.model包中。 另外,不仅包装内的所有东西都可见,而且org.dcsobral.myproject所有东西都可以看到。 如果我只是声明package org.dcsobral.myproject.model ,那么org.dcsobral.myproject将不可见。

规则非常简单,但起初可能会让人们感到困惑。 这个规则的原因是相对的import。 现在考虑在该文件中的以下声明:

 import view._ 

这个导入可能是相对的 – 除非用_root_.前缀,否则所有导入都可以是相对的_root_. 。 它可以引用下面的包: org.dcsobral.myproject.model.vieworg.dcsobral.myproject.viewscala.viewjava.lang.view 。 它也可以引用scala.Predef名为view的对象。 或者它可以是一个绝对的导入,引用一个名为view的包。

如果存在多个这样的包,它将根据一些优先规则select一个。 如果您需要导入其他内容,则可以将导入转换为绝对导入。

这个导入使view包内的所有东西(无论它在哪里)都在其范围内可见。 如果它发生在一个class ,一个object或一个def ,那么这个可见性将被限制在这个范围内。 它会导入一切,因为._ ,这是一个通配符。

替代scheme可能如下所示:

 package org.dcsobral.myproject.model import org.dcsobral.myproject.view import org.dcsobral.myproject.controller 

在这种情况下, viewcontroller将是可见的,但是在使用它们时必须明确地命名它们:

 def post(view: view.User): Node = 

或者你可以使用更多的相对导入:

 import view.User 

import语句也可以让你重命名的东西,或导入一切,但东西。 有关更多详细信息,请参阅相关文档。

所以,我希望这回答你所有的问题。

Scala支持和鼓励Java / JVM的包结构,并且几乎适用于相同的build议:

  • 镜像目录结构中的包结构。 在Scala中这不是必需的,但是它有助于find你的方式
  • 使用你的反向域作为包前缀。 对我来说,这意味着一切都始于de.schauderhaft。 如果您没有自己的域名,请使用对您有意义的内容
  • 只有把一级文件放在一个文件中,如果它们很小并且密切相关的话。 否则坚持每个文件一个类/对象。 例外情况:伴随对象与类相同。 一个密封的类的实现进入同一个文件。
  • 如果你的应用程序增长了,你可能想要像图层和模块一样的东西,并将它们映射到包结构中,所以你可能有这样的包结构: <domain>.<module>.<layer>.<optional subpackage>
  • 在程序包,模块或层级上没有循环依赖关系