Sunday, 27 September 2015

Class vs. Trait in Scala


1. Singleton objects are declared with the object keyword. Scala doesn't have static members. Instead, an object is used to hold members that span instances, such as constants.

An object and a class have the same name and they are defined in the same file, they are companions.
For case classes, the compiler automatically generates a companion object.

2. Inheritance Rules:

When inheritance is used in Scala, the following rules are recommended:
1.  An abstract base class or trait is subclassed one level by concrete classes, including case classes.
2. Concrete classes are never subclassed. Except two cases:
    a. Classes that mix in other behaviors defined in traits.
    b. Test-only versions to promote automated unit testing.
3. When subclassing seems like the right approach, consider partitioning behaviours into traits and mix in those traits instead.
4. Never split logical state across parent-child type boundaries.

The relationship between parent and child types is a contract and care is required to ensure that a child class does not break the implemented behaviors specified by the parent type. Therefore, almost never overriding concrete members.

In Scala, any declaration without a visibility keyword is "public". There is no public keyword in Scala. This is contrast to Java, which defaults to public only within the package.

3. Class Constructors

class Person(var name: String, var age: Int)

Prefixing a constructor argument with a var makes it a mutable field of the class.
Using the case keyword infers the val keyword and also adds additional methods. In other words, val is assumed for case classes.

The primary constructor is the entire body of the class. Any parameters that the constructor requires are listed after the class name.
The auxiliary constructor is named this and it must call the primary constructor or another auxiliary constructor as its first expression.
By forcing all construction go through the primary constructor, code duplication is minimized and initialization logic for new instances is always uniformly applied.

The compiler does not automatically generate apply methods for secondary constructors in case classes. However, if we overload the apply in the companion object, we can have our convenient "constructors" and avoid the requirement to use new.

case class Person(
  name: String,
  age: Option[Int] = None,
  address: Option[Address] = None)

Object Person {
  
  def apply(name: String): Person = new Person(name)

  def apply(name: String, address: Address): Person =
      new Person(name, address = Some(address))
}

Person("Buck")
Person("Buck", address = Some(a))

In fact, it's not all that common to define auxiliary constructors in Scala. Instead, make judicious use of Scala's support for named and optional parameters, and use overloaded apply "factory" methods in objects.

The term method refers to a function that is tied to an instance. Scala will lift
an application method into a function when a function argument is needed for another method or function.

The term member refers to a field, method, or type in a generic way. A field and method can have the same name, but only if the method has an argument list.

4. Trait Constructors

While the body of a trait functions as its primary constructor, traits don't support an argument list for the primary constructor, nor can you define auxiliary constructors.

Traits can extend other traits. They can only extend classes that have no-argument primary or auxiliary constructor, since a trait can't pass arguments to the parent class constructor

Traits with abstract members don't have to be declared abstract by adding the abstract keyword before the trait keyword. However, classes that are abstract, where one or more member is undefined, must be declared abstract.


5. Class or Trait?

Traits as mixins make the most sense for "adjunct" behavior.
If you find that a particular trait is used most often as a parent of other classes, so that the child classes behave as the parent trait, then consider defining the trait as a class instead.

Think of Traits as interfaces that also give you the option of defining the methods you declare.
Traits can also declare and optionally define instance fields (not just static fields, as in Java interfaces)

class LoggedService(name: String) extends Service(name) with Logging{...}

Note how we pass the name argument to the parent class Service.
Scala requires the override keyword when you override a concrete method in a parent class.

Note: Avoid concrete fields in traits that can't be initialized to suitable default values. Use abstract fields instead, or convert the trait to a class with a constructor.

No comments:

Post a Comment