Scala OOPs

Learn via video courses
Topics Covered

Overview

Object-Oriented Programming (OOP) in Scala is a programming paradigm that revolves around the organization of code into objects and classes. Scala seamlessly incorporates OOP principles while also embracing functional programming.

What are the concepts of Object Oriented Programming in Scala?

object oriented programming in scala

Classes

In Scala, classes are used to define blueprints for creating objects. They encapsulate data and behavior. We can define fields (data members) and methods (functions) within a class.

  • A class is declared by utilizing the class keyword, followed by the class name and a constructor parameter list. Fields and methods can be declared within the class body.
  • We create instances (objects) of a class using the new keyword, passing arguments to the constructor.

Objects

Objects in Scala are unique instances of a class. They are commonly used for holding utility methods, constants, or as entry points to applications.

  • Objects are declared using the object keyword and have a single instance.
  • We can access methods and fields of an object directly without the need for instantiation:

Constructors

  • Primary Constructors: Scala primary constructors are defined within the class declaration itself. They specify the parameters required to create an instance of the class.
  • Auxiliary Constructors: Auxiliary constructors are additional constructors defined using the def this() syntax. They allow us to provide alternative ways to construct objects of the class.

Traits

  • Traits in Scala are similar to interfaces in other languages but more powerful. They can contain both abstract and concrete methods and can be mixed into classes to provide reusable behavior.
  • A class can mix in multiple traits, allowing for a flexible and modular way to compose behavior.

Method Overriding

  • Subclasses in Scala can override methods defined in their parent classes or traits. The override keyword is used to indicate that a method in a subclass is meant to override a method with the same name in its superclass or trait.
  • Method overriding is a crucial mechanism for achieving polymorphism.

Inheritance:

  • Scala supports single inheritance, where a class can inherit from only one parent class. Inheritance is declared using the extends keyword.
  • Inherited members, including fields and methods, can be accessed or overridden in the subclass using the override keyword.

Polymorphism:

  • Scala supports polymorphism, allowing objects of different classes to respond to the same method call in a way that is specific to their individual implementations.
  • Polymorphism is achieved through method overriding, inheritance, and interfaces (traits).

Encapsulation:

  • Encapsulation is the principle of hiding the internal details of an object and exposing a controlled interface to interact with it.
  • Scala provides access modifiers (private, protected, and public) to control the visibility and accessibility of class members (fields and methods).

Singleton Objects:

  • Scala allows the creation of singleton objects, which are unique instances of a class that are automatically created when the class is loaded.
  • Singleton objects are often used to hold utility functions, constants, or serve as entry points to an application.

Case Classes:

  • Case classes are a specialized feature in Scala for defining immutable data structures.
  • They automatically generate useful methods such as equals, hashCode, and toString, making them convenient for working with data.

Pattern Matching:

  • Scala offers powerful pattern matching capabilities, which allow us to match data structures and expressions against patterns and take different actions based on the match.
  • Pattern matching is a versatile and expressive way to work with complex data and control flow.

Abstraction in Scala

Scala's abstraction in Object-Oriented Programming conceals implementation details, enabling the creation of objects representing real-world entities and simplifying complexity by emphasizing essential properties and behaviors while ignoring non-essential details.

Scala provides several mechanisms for abstraction:

1. Abstract Classes:

  • An abstract class is a class that cannot be instantiated directly and can have both abstract (unimplemented) and concrete (implemented) methods.
  • Abstract classes are defined with the use of the abstract keyword.
  • Subclasses of an abstract class must implement all its abstract methods, ensuring a contract between the superclass and its subclasses.

Example:

2. Abstract Methods:

  • An abstract method is a method declared in an abstract class or trait without providing a method body. Subclasses or mixing traits are responsible for providing implementations for these methods.
  • Abstract methods define the interface or contract that subclasses must adhere to.

Example:

3. Traits:

  • Traits in Scala share similarities with Java interfaces but offer greater versatility as they can encompass both abstract and concrete methods.
  • Traits allow us to define a set of behaviors that can be mixed into classes, providing a way to abstract and reuse behavior across multiple class hierarchies.

Example:

4. Type Abstraction:

  • Type abstraction in Scala involves abstracting over types rather than values. This is often used with generics (type parameters) and allows us to write more generic and reusable code by working with generic types.

Example:

5. High-Level Abstractions:

  • Beyond language-level abstractions, Scala encourages high-level abstractions through functional programming constructs. Concepts like function composition, monads, and combinators enable developers to create powerful abstractions for solving complex problems.

Encapsulation

Encapsulation refers to the practice of bundling data (attributes) and the methods (functions) that operate on that data into a single unit called a class. This unit is then hidden from the outside world, providing controlled access to the data through well-defined interfaces.

scala encapsulation

In Scala, encapsulation is achieved through various mechanisms:

1. Access Modifiers:

Scala provides three access modifiers—private, protected, and public—to control the visibility of class members (fields and methods). These modifiers help enforce encapsulation by limiting access to internal details:

  • private: Members marked as private can only be accessed within the defining class or object.
  • protected: protected members are accessible within the defining class, its subclasses, and same-package classes.
  • public (default): Members without an access modifier are accessible from anywhere.

Example:

  • In this example, balance is a private field, and its access is restricted to the BankAccount class.

2. Getters and Setters:

In Scala, we can define custom getters and setters for class fields. This allows us to control how data is accessed and modified, ensuring data consistency and validation.

Example:

  • Here, _name is private, and the name getter and name_= setter provide controlled access to the name attribute.

3. Case Classes:

Case classes in Scala automatically generate getters for their constructor parameters. They also provide an apply method for creating instances and other useful methods like equals, hashCode, and toString. These features promote encapsulation by simplifying the creation and access of immutable data.

Example:

  • In this case, x and y are encapsulated within the Point case class.

4. Singleton Objects:

Singleton objects in Scala can encapsulate shared state and behavior. Since they are singleton instances, they provide controlled access to shared resources or services.

Example:

  • The apiBaseUrl is encapsulated within the Configuration singleton object.

Polymorphism

Polymorphism enables objects from various classes to be treated as instances of a shared superclass. In Scala, polymorphism is supported through method overriding, interfaces (traits), and inheritance.

Here's how polymorphism works in Scala:

1. Method Overriding:

Polymorphism is primarily accomplished by means of method overriding, wherein a subclass furnishes a distinct implementation for a method that has already been declared in its superclass. The method in the subclass is often referred to as "overriding" the method in the superclass.

Example:

  • In this example, we have an Animal class with a speak() method. The Dog class extends Animal and provides its own implementation of speak(). Even though we declare animal as an Animal, at runtime, it's actually a Dog, and calling speak() invokes the Dog version of the method.

2. Interfaces (Traits):

Scala supports polymorphism through traits, which are similar to Java interfaces. A trait can define a set of methods (abstract or concrete), and multiple classes can mix in (implement) the trait, providing their own implementations of its methods.

Example:

  • Here, the Speaker trait defines the speak() method, and both Dog and Cat classes implement it. Instances of Dog and Cat can be treated as Speaker objects.

Inheritance

Inheritance allows us to create new classes (subclasses or child classes) based on existing classes (superclasses or parent classes). In Scala, inheritance is a fundamental mechanism for code reuse and creating class hierarchies.

scala inheritance

Here's how inheritance works in Scala:

1. Defining a Superclass (Parent Class):

Inheritance starts by defining a superclass, also known as a parent class. A superclass is a class that provides a blueprint for common characteristics and behaviors that can be shared by its subclasses.

  • In this example, Animal is the superclass, and it has a speak() method.

2. Defining a Subclass (Child Class):

Subclasses inherit attributes and behaviors from the superclass and can add their own attributes and behaviors or override those inherited from the superclass.

  • Here, Dog is a subclass of Animal. It inherits the name parameter from Animal and overrides the speak() method to provide its own implementation.

3. Creating Instances:

We can create instances of both the superclass and the subclass.

  • animal is an instance of Animal, and dog is an instance of Dog.

4. Method Overriding:

Subclasses can override methods from the superclass using the override keyword. This allows them to provide their own implementation of a method.

  • The Cat class also extends Animal and overrides the speak() method.

5. Polymorphism:

Inheritance enables polymorphism, which means that objects of the subclass can be treated as objects of the superclass. This allows for flexibility in using objects interchangeably.

  • In this example, even though animal2 is of type Animal, it's actually an instance of Cat, and calling speak() invokes the Cat version of the method.

6. Superclass Constructor:

Subclasses automatically call the constructor of the superclass. We can pass parameters to the superclass constructor in the subclass declaration.

  • Here, Fish extends Animal and passes the name parameter to the Animal constructor.

Conclusion

  • Object-Oriented Programming (OOP) organizes code into objects and classes, combining data and behavior, and supporting features like inheritance, polymorphism, and encapsulation.
  • Abstraction in Scala is the practice of hiding complex implementation details while exposing a simplified interface for data and behavior.
  • In Scala, encapsulation bundles data and methods within a class, controlling access through clear interfaces, promoting modularity and data security.
  • Polymorphism in Scala is the ability to treat objects of different classes uniformly through method overriding and interfaces, enabling flexibility and code reuse.
  • Inheritance in Scala is the mechanism that allows a class to inherit attributes and behaviors from another class, promoting code reuse and creating class hierarchies.