C06.物件導向設計(OOP)

Understand classes

Scala 不同於 Java, 更偏愛純化的OO思維. - WisdomFish.ORG

定義 Class 為物件的設計藍圖, 之後一般情況下都可透過 new 來實例化成物件.




For Java Developer



相同

  1. Class
    1. 宣告同樣以 class 為開頭.
  2. Members
    1. 為提高實例物件的健壯性(Robustness), 可冠上 'private' (同 Java 效果)
    2. Fields
      1. Java keyword 'final' 效果同 Scala keyword 'val'
    3. Methods
      1. Java 的 'void' 宣告, 在 Scala 使用 ': Unit' 效果是相同的
  3. 實例化(Instance)
    1. val 的最初實例化指向的物件指標恆不變, 但其非 val 的類別宣告, 欄位與方法仍可被改變.


不同

  1. Class
    1. class 宣告無前冠任何權限修飾詞.
  2. 欄位(Fields)
    1. 權限不撰寫在 Scala 是指公開的(Public), 非 Java 的 Package Level.
    2. 欄位的定義可以是 val 或者 var.
  3. Method
    1. 方法的定義冠以 def
    2. 傳遞給方法的任何參數都可以在方法內部使用。Scala 裡方法參數的一個重要特徵是它們都是val,不是var。參數是val 的理由是 val 更容易講清楚。你不需要多看 Code 以確定是否 val 被重新賦值,而 var 則不然。
    3. 如果沒有發現任何顯式的返回語句(return ...),Scala 方法將返回方法中最後一個計算得到的值。
    4. 設計選擇取決於設計內容,Scala 使得編寫具有多個,顯式的 return 的方法變得容易,如果那的確是你期望的。
    5. 不需要 return 的方法。有另一種簡寫方式是,假如某個方法僅計算單個結果表達式,則可以去掉大括號。如果結果表達式很短,甚至可以把它放在def同一行裡
      def checksum(): Int = ~(sum & 0xFF) + 1 
    6. 當你去掉方法體前面的等號'='時,它的結果類型將注定是Unit。不論方法體裡面包含什麼都不例外,因為Scala編譯器可以把任何類型轉換為Unit
  4. 實例化(Instance)
    1. 可省略預設空建構子的空括號: ( )



Notes


  • 對於方法來說推薦的風格實際是避免顯式的尤其是多個返回語句。代之以把每個方法當作是創建返回值的表達式。這種哲學將鼓勵你製造很小的方法,把較大的方法分解為多個更小的方法。


Syntax


  1. 設計 Class
    1. class HelloWorld { }
    2. class HelloWorld(name: String) { }
  2.  實例化
    1. new HelloWorld
  3. def greet2(v: Boolean): Unit = { 
            greeting2 = false
        }
    1. def greet2(v: Boolean) = { 
              greeting2 = false
          }
    2. def greet2(v: Boolean)  {  greeting2 = false } 
    3. 結果類型為Unit的方法,執行的目的就是它的副程序。通常我們定義副程序為在方法外部某處改變狀態或者執行I/O活動。比方說,在 add 這個例子裡,副作用就是sum被重新賦值了。表達這個方法的另一種方式是去掉結果類型和等號,把方法體放在大括號裡。例如,如果方法的最後結果是String,但方法的結果類型被聲明為Unit,那麼String將被轉變為Unit並失去它的值。String被轉變為Unit因為Unit是函數f聲明的結果類型。Scala編譯器會把一個以過程風格定義的方法,就是說,帶有大括號但沒有等號的,在本質上當作是顯式定義結果類型為Unit的方法
      def add(b: Byte) { sum += b }



Syntax-2

class FishKuo(val x: Int, val y: Int) {

def sum(): Int = {

x + y

}


def mul = x + y

def mul1() = x + y

}



val test = new IntMath(9, 9)


test.sum()

test.sum

test sum

test mul

test mul1




建構子多載(Multiple Constructors)



def this( )


http://daily-scala.blogspot.com/2009/11/multiple-constructors.html





Keyword: override



override def methodName ...




Class, Trait, and Object Definition


public class -> class

class Bar(name: String)
new Bar("Helllo ...")

instance
  1. new Foo
  2. new Foo( )
class Baz(name: String) {
// constructor code is inline
if (name == null) throw new Exception("Name is null")
}


Trait
Let’s define the trait Dog:
trait Dog
To add the Dog trait to the Fizz2 class:
class Fizz2(name: String) extends Bar(name) with Dog


Scala does not allow you to declare static methods or variables, but it does support an alternative model for singletons called objects. If you declare something as an object, only one instance of it exists in the scope in which it was declared. An object will be instantiated the first time it is accessed. A Scala object can exist at the package scope, and it replaces Java’s static methods and variables. The advantage of Scala’s object over Java’s static
mechanism is that a Scala object is an instance of a class and can be passed as a parameter to methods. You declare a singleton object with the object keyword instead of the class or trait keyword:

Reference


Understand classes and singleton objects

Up to this point you've written Scala scripts to try out the concepts presented in this article. For all but the simplest projects, however, you will likely want to partition your application code into classes. To give this a try, type the following code into a file called greetSimply.scala:

// In greetSimply.scala

class SimpleGreeter {
val greeting = "Hello, world!"
def greet() = println(greeting)
}

val g = new SimpleGreeter
g.greet()

greetSimply.scala is actually a Scala script, but one that contains a class definition. This first, example, however, illustrates that as in Java, classes in Scala encapsulate fields and methods. Fields are defined with either val or var. Methods are defined with def. For example, in class SimpleGreeter, greeting is a field and greet is a method. To use the class, you initialize a val named g with a new instance of SimpleGreeter. You then invoke the greet instance method on g. If you run this script with scala greetSimply.scala, you will be dazzled with yet another Hello, world!.

Although classes in Scala are in many ways similar to Java, in several ways they are quite different. One difference between Java and Scala involves constructors. In Java, classes have constructors, which can take parameters, whereas in Scala, classes can take parameters directly. The Scala notation is more concise—class parameters can be used directly in the body of the class; there’s no need to define fields and write assignments that copy constructor parameters into fields. This can yield substantial savings in boilerplate code; especially for small classes. To see this in action, type the following code into a file named greetFancily.scala:

// In greetFancily.scala

class FancyGreeter(greeting: String) {
def greet() = println(greeting)
}

val g = new FancyGreeter("Salutations, world")
g.greet

Instead of defining a constructor that takes a String, as you would do in Java, in greetFancily.scala you placed the greeting parameter of that constructor in parentheses placed directly after the name of the class itself, before the open curly brace of the body of class FancyGreeter. When defined in this way, greeting essentially becomes a value (not a variable—it can't be reassigned) field that's available anywhere inside the body. In fact, you pass it to println in the body of the greet method. If you run this script with the command scala greetFancily.scala, it will inspire you with:

Salutations, world!

This is cool and concise, but what if you wanted to check the String passed to FancyGreeter's primary constructor for null, and throw NullPointerException to abort the construction of the new instance? Fortunately, you can. Any code sitting inside the curly braces surrounding the class definition, but which isn't part of a method definition, is compiled into the body of the primary constructor. In essence, the primary constructor will first initialize what is essentially a final field for each parameter in parentheses following the class name. It will then execute any top-level code contained in the class's body. For example, to check a passed parameter for null, type in the following code into a file named greetCarefully.scala:

// In greetCarefully.scala
class CarefulGreeter(greeting: String) {

if (greeting == null) {
throw new NullPointerException("greeting was null")
}

def greet() = println(greeting)
}

new CarefulGreeter(null)

In greetCarefully.scala, an if statement is sitting smack in the middle of the class body, something that wouldn't compile in Java. The Scala compiler places this if statement into the body of the primary constructor, just after code that initializes what is essentially a final field named greeting with the passed value. Thus, if you pass in null to the primary constructor, as you do in the last line of the greetCarefully.scala script, the primary constructor will first initialize the greeting field to null. Then, it will execute the if statement that checks whether the greeting field is equal to null, and since it is, it will throw a NullPointerException. If you run greetCarefully.scala, you will see a NullPointerException stack trace.

In Java, you sometimes give classes multiple constructors with overloaded parameter lists. You can do that in Scala as well, however you must pick one of them to be the primary constructor, and place those constructor parameters directly after the class name. You then place any additional auxiliary constructors in the body of the class as methods named this. To try this out, type the following code into a file named greetRepeatedly.scala:

// In greetRepeatedly.scala
class RepeatGreeter(greeting: String, count: Int) {

def this(greeting: String) = this(greeting, 1)

def greet() = {
for (i <- 1 to count)
println(greeting)
}
}

val g1 = new RepeatGreeter("Hello, world", 3)
g1.greet()
val g2 = new RepeatGreeter("Hi there!")
g2.greet()

RepeatGreeter's primary constructor takes not only a String greeting parameter, but also an Int count of the number of times to print the greeting. However, RepeatGreeter also contains a definition of an auxiliary constructor, the this method that takes a single String greeting parameter. The body of this constructor consists of a single statement: an invocation of the primary constructor parameterized with the passed greeting and a count of 1. In the final four lines of the greetRepeatedly.scala script, you create two RepeatGreeters instances, one using each constructor, and call greet on each. If you run greetRepeatedly.scala, it will print:

Hello, world
Hello, world
Hello, world
Hi there!

Another area in which Scala departs from Java is that you can't have any static fields or methods in a Scala class. Instead, Scala allows you to create singleton objects using the keyword object. A singleton object cannot, and need not, be instantiated with new. It is essentially automatically instantiated the first time it is used, and as the “singleton” in its name implies, there is ever only one instance. A singleton object can share the same name with a class, and when it does, the singleton is called the class's companion object. The Scala compiler transforms the fields and methods of a singleton object to static fields and methods of the resulting binary Java class. To give this a try, type the following code into a file named WorldlyGreeter.scala:

// In WorldlyGreeter.scala

// The WorldlyGreeter class
class WorldlyGreeter(greeting: String) {
def greet() = {
val worldlyGreeting = WorldlyGreeter.worldify(greeting)
println(worldlyGreeting)
}
}

// The WorldlyGreeter companion object
object WorldlyGreeter {
def worldify(s: String) = s + ", world!"
}

In this file, you define both a class, with the class keyword, and a companion object, with the object keyword. Both types are named WorldlyGreeter. One way to think about this if you are coming from a Java programming perspective is that any static methods that you would have placed in class WorldlyGreeter in Java, you'd put in singleton object WorldlyGreeter in Scala. In fact, when the Scala compiler generates bytecodes for this file, it will create a Java class named WorldlyGreeter that has an instance method named greet (defined in the WorldlyGreeter class in the Scala source) and a static method named worldify (defined in the WorldlyGreeter companion object in Scala source). Note also that in the first line of the greet method in class WorldlyGreeter, you invoke the singleton object's worldify method using a syntax similar to the way you invoke static methods in Java: the singleton object name, a dot, and the method name:

// Invoking a method on a singleton object from class WorldlyGreeter 
// ...
val worldlyGreeting = WorldlyGreeter.worldify(greeting)
// ...

To run this code, you'll need to create an application. Type the following code into a file named WorldlyApp.scala:

// In WorldlyApp.scala
// A singleton object with a main method that allows
// this singleton object to be run as an application
object WorldlyApp {
def main(args: Array[String]) {
val wg = new WorldlyGreeter("Hello")
wg.greet()
}
}

Because there's no class named WorldlyApp, this singleton object is not a companion object. It is instead called a stand-alone. object. Thus, a singleton object is either a companion or a stand-alone object. The distinction is important because companion objects get a few special privileges, such as access to private members of the like-named class.

One difference between Scala and Java is that whereas Java requires you to put a public class in a file named after the class—for example, you'd put class SpeedRacer in file SpeedRacer.java—in Scala, you can name .scala files anything you want, no matter what Scala classes or code you put in them. In general in the case of non-scripts, however, it is recommended style to name files after the classes they contain as is done in Java, so that programmers can more easily locate classes by looking at file names. This is the approach we've taken with the two files in this example, WorldlyGreeter.scala and WorldlyApp.scala.

Neither WorldlyGreeter.scala nor WorldlyApp.scala are scripts, because they end in a definition. A script, by contrast, must end in a result expression. Thus if you try to run either of these files as a script, for example by typing:

scala WorldlyGreeter.scala # This won't work!

The Scala interpreter will complain that WorldlyGreeter.scala does not end in a result expression. Instead, you'll need to actually compile these files with the Scala compiler, then run the resulting class files. One way to do this is to use scalac, which is the basic Scala compiler. Simply type:

scalac WorldlyApp.scala WorldlyGreeter.scala

Given that the scalac compiler starts up a new JVM instance each time it is invoked, and that the JVM often has a perceptible start-up delay, the Scala distribution also includes a Scala compiler daemon called fsc (for fast Scala compiler). You use it like this:

fsc WorldlyApp.scala WorldlyGreeter.scala

The first time you run fsc, it will create a local server daemon attached to a port on your computer. It will then send the list of files to compile to the daemon via the port, and the daemon will compile the files. The next time you run fsc, the daemon will already be running, so fsc will simply send the file list to the daemon, which will immediately compile the files. Using fsc, you only need to wait for the the JVM to startup the first time. If you ever want to stop the fsc daemon, you can do so with fsc -shutdown.

Running either of these scalac or fsc commands will produce Java class files that you can then run via the scala command, the same command you used to invoke the interpreter in previous examples. However, instead of giving it a filename with a .scala extension containing Scala code to interpret6 as you did in every previous example, in this case you'll give it the name of a class containing a main method. Similar to Java, any Scala class with a main method that takes a single parameter of type Array[String] and returns Unit7 can serve as the entry point to an application. In this example, WorldlyApp has a main method with the proper signature, so you can run this example by typing:

scala WorldlyApp

At which point you should see:

Hello, world!

You may recall seeing this output previously, but this time it was generated in this interesting manner:

  • The scala program fires up a JVM with the WorldlyApp's main method as the entry point.
  • WordlyApp's main method creates a new WordlyGreeter instance via new, passing in the string "Hello" as a parameter.
  • Class WorldlyGreeter's primary constructor essentially initializes a final field named greeting with the passed value, "Hello" (this initialization code is automatically generated by the Scala compiler).
  • WordlyApp's main method initializes a local \@val@ named wg with the new WorldlyGreeter instance.
  • WordlyApp's main method then invokes greet on the WorldlyGreeter instance to which wg refers.
  • Class WordlyGreeter's greet method invokes worldify on singleton object WorldlyGreeter, passing along the value of the final field greeting, "Hello".
  • Companion object WorldlyGreeter's worldify method returns a String consisting of the value of a concatenation of the s parameter, which is "Hello", and the literal String ", world!".
  • Class WorldlyGreeter's greet method then initializes a \@val@ named worldlyGreetingwithplaces the "Hello, world!" String returned from the worldify method.
  • Class WorldlyGreeter's greet method passes the "Hello, world!" String to which worldlyGreeting refers to println, which sends the cheerful greeting, via the standard output stream, to you.