ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 3473

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Section 12.7

Chapter 12 · Traits

275

Table 12.1 · Linearization of types in Cat’s hierarchy

Type

Linearization

Animal

Animal, AnyRef, Any

Furry

Furry, Animal, AnyRef, Any

FourLegged

FourLegged, HasLegs, Animal, AnyRef, Any

HasLegs

HasLegs, Animal, AnyRef, Any

Cat

Cat, FourLegged, HasLegs, Furry, Animal, AnyRef, Any

 

 

Cat FourLegged HasLegs Furry Animal AnyRef Any

When any of these classes and traits invokes a method via super, the implementation invoked will be the first implementation to its right in the linearization.

12.7 To trait, or not to trait?

Whenever you implement a reusable collection of behavior, you will have to decide whether you want to use a trait or an abstract class. There is no firm rule, but this section contains a few guidelines to consider.

If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all.

If it might be reused in multiple, unrelated classes, make it a trait. Only traits can be mixed into different parts of the class hierarchy.

If you want to inherit from it in Java code, use an abstract class. Since traits with code do not have a close Java analog, it tends to be awkward to inherit from a trait in a Java class. Inheriting from a Scala class, meanwhile, is exactly like inheriting from a Java class. As one exception, a Scala trait with only abstract members translates directly to a Java interface, so you should feel free to define such traits even if you expect Java code to inherit from it. See Chapter 31 for more information on working with Java and Scala together.

If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class. The issue is that when a trait gains or loses a member, any classes that inherit from it must be recompiled, even if they have not changed. If outside clients will only call into the behavior, instead of inheriting from

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 12.8

Chapter 12 · Traits

276

it, then using a trait is fine.

If efficiency is very important, lean towards using a class. Most Java runtimes make a virtual method invocation of a class member a faster operation than an interface method invocation. Traits get compiled to interfaces and therefore may pay a slight performance overhead. However, you should make this choice only if you know that the trait in question constitutes a performance bottleneck and have evidence that using a class instead actually solves the problem.

If you still do not know, after considering the above, then start by making it as a trait. You can always change it later, and in general using a trait keeps more options open.

12.8 Conclusion

This chapter has shown you how traits work and how to use them in several common idioms. You saw that traits are similar to multiple inheritance, but because they interpret super using linearization, they both avoid some of the difficulties of traditional multiple inheritance, and allow you to stack behaviors. You also saw the Ordered trait and learned how to write your own enrichment traits.

Now that you have seen all of these facets, it is worth stepping back and taking another look at traits as a whole. Traits do not merely support the idioms described in this chapter. They are a fundamental unit of code that is reusable through inheritance. Because of this nature, many experienced Scala programmers start with traits when they are at the early stages of implementation. Each trait can hold less than an entire concept, a mere fragment of a concept. As the design solidifies, the fragments can be combined into more complete concepts through trait mixin.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index



Chapter 13

Packages and Imports

When working on a program, especially a large one, it is important to minimize coupling—the extent to which the various parts of the program rely on the other parts. Low coupling reduces the risk that a small, seemingly innocuous change in one part of the program will have devastating consequences in another part. One way to minimize coupling is to write in a modular style. You divide the program into a number of smaller modules, each of which has an inside and an outside. When working on the inside of a module—its implementation—you need only coordinate with other programmers working on that very same module. Only when you must change the outside of a module—its interface—is it necessary to coordinate with developers working on other modules.

This chapter shows several constructs that help you program in a modular style. It shows how to place things in packages, make names visible through imports, and control the visibility of definitions through access modifiers. The constructs are similar in spirit to constructs in Java, but there are some differences—usually ways that are more consistent—so it’s worth reading this chapter even if you already know Java.

13.1 Putting code in packages

Scala code resides in the Java platform’s global hierarchy of packages. The example code you’ve seen so far in this book has been in the unnamed package. You can place code into named packages in Scala in two ways. First, you can place the contents of an entire file into a package by putting a package clause at the top of the file, as shown in Listing 13.1.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 13.2

Chapter 13 · Packages and Imports

278

package bobsrockets.navigation class Navigator

Listing 13.1 · Placing the contents of an entire file into a package.

The package clause of Listing 13.1 places class Navigator into the package named bobsrockets.navigation. Presumably, this is the navigation software developed by Bob’s Rockets, Inc.

Note

Because Scala code is part of the Java ecosystem, it is recommended to follow Java’s reverse-domain-name convention for Scala packages that you release to the public. Thus, a better name for Navigator’s package might be com.bobsrockets.navigation. In this chapter, however, we’ll leave off the “com.” to make the examples easier to understand.

The other way you can place code into packages in Scala is more like C# namespaces. You follow a package clause by a section in curly braces that contains the definitions that go into the package. This syntax is called a packaging. The packaging shown in Listing 13.2 has the same effect as the code in Listing 13.1:

package bobsrockets.navigation { class Navigator

}

Listing 13.2 · Long form of a simple package declaration.

For such simple examples, you might as well use the syntactic sugar shown in Listing 13.1. However, one use of the more general notation is to have different parts of a file in different packages. For example, you might include a class’s tests in the same file as the original code, but put the tests in a different package, as shown in Listing 13.3.

13.2 Concise access to related code

When code is divided into a package hierarchy, it doesn’t just help people browse through the code. It also tells the compiler that code in the same

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 13.2

Chapter 13 · Packages and Imports

279

package bobsrockets { package navigation {

// In package bobsrockets.navigation class Navigator

package tests {

// In package bobsrockets.navigation.tests class NavigatorSuite

}

}

}

Listing 13.3 · Multiple packages in the same file.

package bobsrockets {

package

navigation {

class

Navigator {

// No need to say bobsrockets.navigation.StarMap

val

map = new StarMap

}

 

class

StarMap

}

 

class Ship {

// No

need to say bobsrockets.navigation.Navigator

val nav = new navigation.Navigator

}

 

package

fleets {

class

Fleet {

// No need to say bobsrockets.Ship

def

addShip() { new Ship }

}

 

}

}

Listing 13.4 · Concise access to classes and packages.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 13.2

Chapter 13 · Packages and Imports

280

package bobsrockets { class Ship

}

package bobsrockets.fleets { class Fleet {

// Doesn’t compile! Ship is not in scope. def addShip() { new Ship }

}

}

Listing 13.5 · Symbols in enclosing packages not automatically available.

//In file launch.scala package launch {

class Booster3

}

//In file bobsrockets.scala package bobsrockets {

package navigation { package launch {

class Booster1

}

class MissionControl {

val booster1 = new launch.Booster1

val booster2 = new bobsrockets.launch.Booster2 val booster3 = new _root_.launch.Booster3

}

}

package launch { class Booster2

}

}

Listing 13.6 · Accessing hidden package names.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 13.2

Chapter 13 · Packages and Imports

281

package is related in some way to each other. Scala takes advantage of this relatedness by allowing short, unqualified names when accessing code that is in the same package.

Listing 13.4 gives three simple examples. First, as you would expect, a class can be accessed from within its own package without needing a prefix. That’s why new StarMap compiles. Class StarMap is in the same package, bobsrockets.navigation, as the new expression that accesses it, so the package name doesn’t need to be prefixed.

Second, a package itself can be accessed from its containing package without needing a prefix. In Listing 13.4, look at how class Navigator is instantiated. The new expression appears in package bobsrockets, which is the containing package of bobsrockets.navigation. Thus, it can access package bobsrockets.navigation as simply navigation.

Third, when using the curly-braces packaging syntax, all names accessible in scopes outside the packaging are also available inside it. An example in Listing 13.4 is the way addShip() creates a new Ship. The method is defined within two packagings: an outer one for bobsrockets, and an inner one for bobsrockets.fleets. Since Ship is accessible in the outer packaging, it can be referenced from within addShip().

Note that this kind of access is only available if you explicitly nest the packagings. If you stick to one package per file, then—like in Java—the only names available will be the ones defined in the current package. In Listing 13.5, the packaging of bobsrockets.fleets has been moved to the top level. Since it is no longer enclosed in a packaging for bobsrockets, names from bobsrockets are not immediately in scope. As a result, new Ship gives a compile error. If nesting packages with braces shifts your code uncomfortably to the right, you can also use multiple package clauses without the braces.1 For instance, the code below also defines class Fleet in two nested packages bobrockets and fleets, just like you saw it in Listing 13.4:

package bobsrockets package fleets class Fleet {

// Doesn’t compile! Ship is not in scope. def addShip() { new Ship }

}

1This style of multiple package clauses without braces is called chained package clauses.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index