Scala应用程序结构
我现在正在学习Scala,我想写一些像控制台Twitter客户端或者其他什么的傻小应用程序。 问题是,如何在磁盘上和逻辑上构build应用程序。 我知道python,在那里我只是创build一些类的文件,然后导入它们在主要模块像import util.ssh
或from 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.model
,org.dcsobral.myproject.view
和org.dcsobral.myproject.controller
包,那么这个目录就是myproject/src/main/scala/model
,myproject/src/main/scala/view
和myproject/src/main/scala/controller
。 -
把一切放在一起。 在这种情况下,所有源文件都在
myproject/src/main/scala
。 这对于小型项目来说已经足够了。 实际上,如果你没有子项目,就和上面一样。
这处理目录布局。
文件名称
接下来,我们来讨论一下文件。 在Java中,实践是将每个类在自己的文件中分开,其名称将遵循类的名称。 在Scala中这也足够了,但是你必须注意一些例外。
首先,Scala有object
,Java没有。 一个同名的class
和object
被认为是同伴 ,这有一些实际的意义,但只有当它们在同一个文件中。 因此,将伴随类和对象放在同一个文件中。
其次,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.view
, org.dcsobral.myproject.view
, scala.view
和java.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
在这种情况下, 包 view
和controller
将是可见的,但是在使用它们时必须明确地命名它们:
def post(view: view.User): Node =
或者你可以使用更多的相对导入:
import view.User
import
语句也可以让你重命名的东西,或导入一切,但东西。 有关更多详细信息,请参阅相关文档。
所以,我希望这回答你所有的问题。
Scala支持和鼓励Java / JVM的包结构,并且几乎适用于相同的build议:
- 镜像目录结构中的包结构。 在Scala中这不是必需的,但是它有助于find你的方式
- 使用你的反向域作为包前缀。 对我来说,这意味着一切都始于de.schauderhaft。 如果您没有自己的域名,请使用对您有意义的内容
- 只有把一级文件放在一个文件中,如果它们很小并且密切相关的话。 否则坚持每个文件一个类/对象。 例外情况:伴随对象与类相同。 一个密封的类的实现进入同一个文件。
- 如果你的应用程序增长了,你可能想要像图层和模块一样的东西,并将它们映射到包结构中,所以你可能有这样的包结构:
<domain>.<module>.<layer>.<optional subpackage>
。 - 在程序包,模块或层级上没有循环依赖关系