简体   繁体   English

Scala应用程序结构

[英]Scala application structure

I am learning Scala now and I want to write some silly little app like a console Twitter client, or whatever. 我现在正在学习Scala,我想写一些愚蠢的小应用程序,如控制台Twitter客户端,或其他什么。 The question is, how to structure application on disk and logically. 问题是,如何在磁盘上和逻辑上构建应用程序。 I know python, and there I would just create some files with classes and then import them in the main module like import util.ssh or from tweets import Retweet (strongly hoping you wouldn't mind that names, they are just for reference). 我知道python,在那里我只是创建一些带有类的文件然后在主模块中导入它们,如import util.ssh或者from tweets import Retweet (强烈希望你不介意这些名字,它们仅供参考)。 But how should I do this stuff using Scala? 但是我应该如何使用Scala来做这些事情呢? Also, I have not much experience with JVM and Java, so I am a complete newbie here. 另外,我对JVM和Java没有多少经验,所以我在这里是一个完整的新手。

I'm going to disagree with Jens , here, though not all that much. 我不同意Jens ,虽然不是那么多。

Project Layout 项目布局

My own suggestion is that you model your efforts on Maven's standard directory layout . 我自己的建议是你在Maven的标准目录布局上模拟你的工作。

Previous versions of SBT (before SBT 0.9.x) would create it automatically for you: 以前版本的SBT(在SBT 0.9.x之前)会自动为您创建:

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

So you'll put your source files inside myproject/src/main/scala , for the main program, or myproject/src/test/scala , for the tests. 因此,您将源文件放在myproject/src/main/scala ,用于主程序,或myproject/src/test/scala ,用于测试。

Since that doesn't work anymore, there are some alternatives: 由于这不再起作用,有一些替代方案:

giter8 and sbt.g8 giter8和sbt.g8

Install giter8 , clone ymasory's sbt.g8 template and adapt it to your necessities, and use that. 安装giter8 ,克隆ymasory的sbt.g8模板并根据您的需要进行调整,并使用它。 See below, for example, this use of unmodified ymasory's sbt.g8 template. 例如,参见下面未修改的ymasory的sbt.g8模板的使用。 I think this is one of the best alternatives to starting new projects when you have a good notion of what you want in all your projects. 我认为这是开始新项目的最佳选择之一,因为你对所有项目都有一个很好的概念。

$ 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 plugin np插件

Use softprops's np plugin for sbt. 使用softprops的sp 插件np插件 In the example below, the plugin is configured on ~/.sbt/plugins/build.sbt , and its settings on ~/.sbt/np.sbt , with standard sbt script. 在下面的示例中,插件在~/.sbt/plugins/build.sbt ,其设置在~/.sbt/np.sbt ,带有标准的sbt脚本。 If you use paulp's sbt-extras, you'd need to install these things under the right Scala version subdirectory in ~/.sbt , as it uses separate configurations for each Scala version. 如果您使用paulp的sbt-extras,则需要在~/.sbt的右侧Scala版本子目录下安装这些内容,因为它为每个Scala版本使用单独的配置。 In practice, this is the one I use most often. 在实践中,这是我经常使用的那个。

$ 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

You could simply create it with mkdir : 您可以使用mkdir创建它:

$ 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

Source Layout 来源布局

Now, about the source layout. 现在,关于源布局。 Jens recommends following Java style. Jens建议遵循Java风格。 Well, the Java directory layout is a requirement -- in Java. 嗯,Java目录布局是一项要求 - 在Java中。 Scala does not have the same requirement, so you have the option of following it or not. Scala没有相同的要求,因此您可以选择是否遵循它。

If you do follow it, assuming the base package is org.dcsobral.myproject , then source code for that package would be put inside myproject/src/main/scala/org/dcsobral/myproject/ , and so on for sub-packages. 如果你遵循它,假设基础包是org.dcsobral.myproject ,那么该包的源代码将放在myproject/src/main/scala/org/dcsobral/myproject/ ,等等用于子包。

Two common ways of diverging from that standard are: 偏离该标准的两种常见方式是:

  • Omitting the base package directory, and only creating subdirectories for the sub-packages. 省略基本包目录,仅创建子包的子目录。

    For instance, let's say I have the packages org.dcsobral.myproject.model , org.dcsobral.myproject.view and org.dcsobral.myproject.controller , then the directories would be myproject/src/main/scala/model , myproject/src/main/scala/view and myproject/src/main/scala/controller . 例如,假设我有包org.dcsobral.myproject.modelorg.dcsobral.myproject.vieworg.dcsobral.myproject.controller ,然后目录将是myproject/src/main/scala/modelmyproject/src/main/scala/viewmyproject/src/main/scala/controller

  • Putting everything together. 将所有东西放在一起。 In this case, all source files would be inside myproject/src/main/scala . 在这种情况下,所有源文件都在myproject/src/main/scala This is good enough for small projects. 这对小型项目来说已经足够了。 In fact, if you have no sub-projects, it is the same as above. 实际上,如果您没有子项目,则与上述相同。

And this deals with directory layout. 这涉及目录布局。

File Names 文件名

Next, let's talk about files. 接下来,我们来谈谈文件。 In Java, the practice is separating each class in its own file, whose name will follow the name of the class. 在Java中,实践是将每个类分隔在自己的文件中,该文件的名称将遵循类的名称。 This is good enough in Scala too, but you have to pay attention to some exceptions. 这在Scala中也足够好,但你必须注意一些例外。

First, Scala has object , which Java does not have. 首先,Scala具有Java所没有的object A class and object of the same name are considered companions , which has some practical implications, but only if they are in the same file. 具有相同名称的classobject被视为伴随 ,它具有一些实际含义,但前提是它们位于同一文件中。 So, place companion classes and objects in the same file. 因此,将伴侣类和对象放在同一个文件中。

Second, Scala has a concept known as sealed class (or trait ), which limits subclasses (or implementing object s) to those declared in the same file. 其次,Scala有一个称为sealed class (或trait )的概念,它将子类(或实现object )限制为在同一文件中声明的子类。 This is mostly done to create algebraic data types with pattern matching with exhaustiveness check. 这主要是为了创建具有模式匹配和穷举检查的代数数据类型。 For example: 例如:

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

If Tree was not sealed , then anyone could extend it, making it impossible for the compiler to know whether the match was exhaustive or not. 如果Tree没有sealed ,那么任何人都可以扩展它,使编译器无法知道匹配是否是详尽的。 Anyway, sealed classes go together in the same file. 无论如何, sealed类一起放在同一个文件中。

Another naming convention is to name the files containing a package object (for that package) package.scala . 另一个命名约定是命名包含package object (对于该包) package.scala

Importing Stuff 导入东西

The most basic rule is that stuff in the same package see each other. 最基本的规则是同一个包中的东西互相看见。 So, put everything in the same package, and you don't need to concern yourself with what sees what. 所以,把所有东西都放在同一个包里,你不需要关心自己看到了什么。

But Scala also have relative references and imports. 但Scala也有相对引用和导入。 This requires a bit of an explanation. 这需要一些解释。 Say I have the following declarations at the top of my file: 假设我的文件顶部有以下声明:

package org.dcsobral.myproject
package model

Everything following will be put in the package org.dcsobral.myproject.model . 以下所有内容都将放在org.dcsobral.myproject.model包中。 Also, not only everything inside that package will be visible, but everything inside org.dcsobral.myproject will be visible as well. 此外,不仅包装内的所有内容都可见,而且org.dcsobral.myproject内的所有org.dcsobral.myproject也都可见。 If I just declared package org.dcsobral.myproject.model instead, then org.dcsobral.myproject would not be visible. 如果我只是声明了package org.dcsobral.myproject.model ,那么org.dcsobral.myproject将不可见。

The rule is pretty simple, but it can confuse people a bit at first. 规则很简单,但最初可能会让人感到困惑。 The reason for this rule is relative imports. 此规则的原因是相对导入。 Consider now the following statement in that file: 现在考虑该文件中的以下语句:

import view._

This import may be relative -- all imports can be relative unless you prefix it with _root_. 此导入可能是相对的 - 除非您使用_root_.前缀,否则所有导入都可以是相对的_root_. . It can refer to the following packages: org.dcsobral.myproject.model.view , org.dcsobral.myproject.view , scala.view and java.lang.view . 它可以引用以下包: org.dcsobral.myproject.model.vieworg.dcsobral.myproject.viewscala.viewjava.lang.view It could also refer to an object named view inside scala.Predef . 它还可以引用scala.Predef名为view的对象。 Or it could be an absolute import refering to a package named view . 或者它可以是一个绝对导入,引用一个名为view的包。

If more than one such package exists, it will pick one according to some precedence rules. 如果存在多个这样的包,它将根据一些优先规则选择一个。 If you needed to import something else, you can turn the import into an absolute one. 如果您需要导入其他内容,可以将导入转换为绝对导入。

This import makes everything inside the view package (wherever it is) visible in its scope. 此导入使view包中的所有内容(无论它在何处)在其范围内可见。 If it happens inside a class , and object or a def , then the visibility will be restricted to that. 如果它发生在classobjectdef ,则可见性将限制在此范围内。 It imports everything because of the ._ , which is a wildcard. 它导入所有东西,因为._ ,这是一个通配符。

An alternative might look like this: 替代方案可能如下所示:

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

In that case, the packages view and controller would be visible, but you'd have to name them explicitly when using them: 在这种情况下, viewcontroller将是可见的,但您在使用它们时必须明确命名它们:

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

Or you could use further relative imports: 或者您可以使用进一步的相对导入:

import view.User

The import statement also enable you to rename stuff, or import everything but something. import语句还允许您重命名内容,或导入除了某些内容之外的所有内容。 Refer to relevant documentation about it for more details. 有关详细信息,请参阅相关文档。

So, I hope this answer all your questions. 所以,我希望这能回答你所有的问题。

Scala supports and encourages the package structure of Java /JVM and pretty much the same recommendation apply: Scala支持并鼓励Java / JVM的包结构,几乎相同的建议适用:

  • mirror the package structure in the directory structure. 镜像目录结构中的包结构。 This isn't necessary in Scala, but it helps to find your way around 这在Scala中不是必需的,但它有助于找到自己的方法
  • use your inverse domain as a package prefix. 使用逆域作为包前缀。 For me that means everything starts with de.schauderhaft. 对我而言,这意味着一切都始于de.schauderhaft。 Use something that makes sense for you, if you don't have you own domain 如果您没有自己的域名,请使用对您有意义的内容
  • only put top level classes in one file if they are small and closely related. 如果顶级类很小并且密切相关,则只将顶级类放在一个文件中。 Otherwise stick with one class/object per file. 否则每个文件都有一个类/对象。 Exceptions: companion objects go in the same file as the class. 例外:伴侣对象与该类位于同一文件中。 Implementations of a sealed class go into the same file. 密封类的实现进入同一个文件。
  • if you app grows you might want to have something like layers and modules and mirror those in the package structure, so you might have a package structure like this: <domain>.<module>.<layer>.<optional subpackage> . 如果你的应用程序增长,你可能希望有类似图层和模块的东西,并在包结构中镜像它们,所以你可能有这样的包结构: <domain>.<module>.<layer>.<optional subpackage>
  • don't have cyclic dependencies on a package, module or layer level 在包,模块或层级上没有循环依赖关系

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM