4 From Business Process to Class: A New Way of Thinking
In the first unit, you learned the grammar of Scala—the individual words and sentence structures like val
, if
, and def
. Now, it is time to learn how to write essays. It’s time to organize your code around big ideas, not just small instructions.
As a Business Analyst, your mind is already trained for this. You excel at looking at a complex, messy real-world process and identifying the core concepts: “There is a Customer who places an Order which contains multiple Products…”
What if your code could be structured around those exact same nouns? What if you could create a digital version of a Customer
that is self-contained and intelligent?
This is the central promise and the paradigm shift of Object-Oriented Programming (OOP). It’s a style of programming that moves away from writing long, procedural lists of instructions and towards creating a virtual world of smart objects that interact with each other. For an analyst, this is a superpower: it allows you to model the business domain directly in your code.
4.1 The Big Idea: Bundling Data and Behavior
Before OOP, a programmer trying to represent a customer might have a collection of loose variables:
val customer1_name = "Alice"
val customer1_id = 101
val customer1_email = "alice@example.com"
val customer2_name = "Bob"
// ...and so on.
This is chaotic. The data is disconnected from the actions you can perform. The logic to update a customer’s email would be in some other function, completely separate from the data it operates on.
OOP solves this by bundling data and the actions that work on that data into a single, neat package. We call this package an Object. The blueprint for creating these packages is called a Class.
- The Class: The detailed architectural blueprint for a concept, like “House.” It defines what all houses know (their attributes, like
address
andsquareFootage
) and what all houses can do (their behaviors, likeopenFrontDoor()
orturnOnLights()
). - The Object: An actual instance built from the blueprint, like “the house at 123 Main Street.” It has its own specific state (its address is “123 Main Street,” its lights might be on or off) and can perform all the actions defined in the blueprint.
Every object in OOP has three core aspects: 1. State: What an object knows. This is its internal data, its attributes. 2. Behavior: What an object can do. These are its actions, its functions, which we call methods. 3. Identity: The simple fact that it is a unique entity. Even if two houses are built from the same blueprint and painted the same color, they are still two different houses.
4.2 Hands-On: Modeling a Richer Product
Let’s model a Product
for our e-commerce store, “Sparkly Goods.” This time, we’ll give it both state and behavior right from the start.
4.2.1 Step 1: The Blueprint - Now with State and Behavior!
A product has data (ID, name, price), but it can also perform actions, like displaying itself or determining if it’s on sale.
class Product(val id: Int, val name: String, var price: Double, val category: String) {
// BEHAVIOR 1: A method to display product information.
// It uses the object's own state (its name, category, and price).
def displayInfo(): Unit = {
println("--- Product Information ---")
println(s"ID: $id")
println(s"Name: $name")
println(s"Category: $category")
println(s"Price: $$${price}")
println("-------------------------")
}
// BEHAVIOR 2: A method that asks a question about the object's state.
def isExpensive(): Boolean = {
// 'this' refers to "my own". My own price.
this.price > 50.00
}
// BEHAVIOR 3: A method that MODIFIES the object's state.
def applyDiscount(percentage: Double): Unit = {
if (percentage > 0 && percentage < 1) {
val discountAmount = this.price * percentage
this.price = this.price - discountAmount
println(s"Applied a ${percentage * 100}% discount. New price is $$${this.price}")
} else {
println("Invalid discount percentage. It must be between 0 and 1.")
}
}
}
Our Product
blueprint is now much smarter. It doesn’t just hold data; it knows how to perform operations related to that data.
4.2.2 Step 2: The Factory - Creating and Understanding Objects
Now, let’s use our blueprint to create distinct objects. The new
keyword is like saying “run the factory and produce one new item from this blueprint.”
// Create two different objects from the Product class
val luxuryWatch = new Product(401, "Scala Chronograph", 199.99, "Watches")
val coffeeMug = new Product(205, "Spark Mug", 12.50, "Kitchenware")
// Let's interact with them by calling their methods
.displayInfo()
luxuryWatch.displayInfo()
coffeeMug
println(s"Is the watch expensive? ${luxuryWatch.isExpensive()}") // true
println(s"Is the mug expensive? ${coffeeMug.isExpensive()}") // false
// Let's modify the state of ONE object
.applyDiscount(0.20) // Apply a 20% discount
luxuryWatch
// Display the info again to see the changed state
println("After discount:")
.displayInfo()
luxuryWatch
// The coffee mug's state remains unchanged, because it's a separate object!
.displayInfo() coffeeMug
This demonstrates the concepts of State and Identity. Each object maintains its own internal state (price
), and changing one object has no effect on another.
4.3 A Better Blueprint for Data: Introducing the case class
For classes whose main purpose is to hold data—like the models you’ll frequently use in data analysis—Scala provides a powerful and convenient shorthand: the case class
.
Think of a case class
as a regular class
that comes with a set of useful, pre-built features, saving you a lot of boilerplate code.
Let’s refactor our Product
into a case class
. Notice how little changes, but how much we get for free.
case class Product(id: Int, name: String, price: Double, category: String)
// 1. The 'new' keyword is now optional!
val book = Product(101, "The Pragmatic Programmer", 29.95, "Books")
// 2. You get a beautiful, readable printout for free! No more memory addresses.
println(book)
// Output: Product(101,The Pragmatic Programmer,29.95,Books)
// 3. You get sensible equality for free. Two objects with the same state are equal.
val book1 = Product(101, "The Pragmatic Programmer", 29.95, "Books")
val book2 = Product(101, "The Pragmatic Programmer", 29.95, "Books")
println(s"Are book1 and book2 equal? ${book1 == book2}") // Output: true
Tip: For data modeling in Spark and general data analysis in Scala, you will almost always want to use a
case class
. It is the idiomatic tool for creating data blueprints.
4.4 Design Tips for Your First Classes
As you start modeling your own concepts, keep these principles in mind.
- A Class Should Represent a Single, Cohesive Concept. A
Customer
class should be responsible for customer-related data and behavior (likeupdateAddress
orverifyEmail
). It should not also be responsible for handlingOrder
logic. This is known as the Single Responsibility Principle. - Start by Identifying the Nouns. When you look at a business problem, the nouns are your candidate classes: “Customer,” “Product,” “Invoice,” “Shipment.” The verbs associated with them are your candidate methods: “a customer places an order,” “an invoice calculates its total.”
- Favor Immutability. Notice in our first
class
example, we usedvar price
so we could change it. In many cases, it’s better to design your objects to be immutable (all attributes areval
). Instead of changing an object’s state, you create a new object with the updated state.case class
es are particularly well-suited for this style of programming, which you’ll see in more advanced Scala. For now, just remember that immutable objects are simpler and safer to reason about.
4.5 Summary: From Chaos to Clarity
You’ve just taken your first, most important step into Object-Oriented Programming. * We’ve seen that OOP is a paradigm for managing complexity by bundling state (data) and behavior (methods) into single units called objects. * A class
is the blueprint, and an object is a concrete instance with its own unique identity and state. * A case class
is Scala’s powerful shorthand for creating classes meant to hold data, giving us many conveniences for free.
Our objects can now know things about themselves and perform actions. But this power comes with a risk. Right now, anyone could call luxuryWatch.price = -500.00
. How do we protect our objects from being corrupted? That is the crucial concept of Encapsulation, and it’s what we will master in the next chapter.