Complete Guide | Lesson 3: First Principles → Lesson 4: Blueprints & Instances → Lesson 5: Inheritance (Apr 23)
A blueprint is not a house. A class is not an object. Confuse the two and you'll build on sand.
This seems obvious when I say it that way, but this confusion is behind more bad code than any other single misunderstanding in object-oriented programming. Beginners treat classes and objects as the same thing. They put data that belongs to individual instances into the class itself. They create one object when they need a hundred. They hardcode values into blueprints that should be passed in at construction time.
This is Lesson 4, and it maps to Chokmah on the Tree of Life. Chokmah means "Wisdom." It's the first active force of creation: the initial flash of insight that produces a design. In Lesson 2, you learned to see the world as objects. In Lesson 3, you learned the atomic types those objects are made from. Now, in Chokmah, you learn to create the blueprints from which objects are built. This is where programming stops being philosophy and starts being engineering.
The Blueprint Analogy
An architect draws a blueprint for a house. The blueprint specifies three bedrooms, two bathrooms, a kitchen, and a garage. It defines dimensions, materials, and where the plumbing goes.
The blueprint is not a house. Nobody lives in a blueprint. But every house built from that blueprint has three bedrooms, two bathrooms, a kitchen, and a garage. They might have different paint colors, different furniture, different families inside, but the structure is the same.
In programming:
- Class = the blueprint (defines what a thing IS and what it CAN DO)
- Object = a house built from that blueprint (a specific instance with specific data)
- Instantiation = the act of building a house from a blueprint (
new Customer("Alice"))
You can build one object from a class or a million. Each one is independent. Alice's account balance doesn't affect Bob's. But both Alice and Bob have an account balance, because the blueprint says every Customer has one.
Let's revisit the coffee shop from Lesson 2, but now with classes:
1CLASS Customer
2 name: String
3 email: String
4 loyaltyPoints: Integer = 0
5
6 CONSTRUCTOR(name, email)
7 this.name = name
8 this.email = email
9 END CONSTRUCTOR
10
11 METHOD placeOrder(items)
12 order = new Order(this, items)
13 RETURN order
14 END METHOD
15
16 METHOD earnPoints(amount)
17 this.loyaltyPoints = this.loyaltyPoints + amount
18 END METHOD
19END CLASSNow we can create actual customers:
1alice = new Customer("Alice Chen", "[email protected]")
2bob = new Customer("Bob Smith", "[email protected]")
3
4// Alice and Bob are separate objects built from the same blueprint
5alice.earnPoints(50)
6// alice.loyaltyPoints = 50
7// bob.loyaltyPoints = 0 (unaffected, separate instance)Properties, Methods, and Constructors
Properties: What an Object Knows
Properties (also called fields, attributes, or member variables) are the data an object carries. A BankAccount knows its balance, ownerName, and accountNumber. A Song knows its title, artist, duration, and playCount.
Properties define the state of an object. Alice's bank account has a balance of $500. Bob's has $12,000. Same class, different state.
Methods: What an Object Does
Methods (also called functions or behaviors) are the actions an object can perform. A BankAccount can deposit(), withdraw(), getBalance(), and transfer(). A Song can play(), pause(), skip(), and addToPlaylist().
Methods define the behavior of an object. They can read properties, modify properties, call other methods, and interact with other objects.
Constructors: How Objects Are Born
A constructor is a special method that runs automatically when an object is created. It's the birth certificate. When you write new BankAccount("Alice", 500), the constructor receives "Alice" and 500 and uses them to set the object's initial state.
1CLASS BankAccount
2 ownerName: String
3 balance: Decimal
4 accountNumber: String
5
6 CONSTRUCTOR(ownerName, initialDeposit)
7 this.ownerName = ownerName
8 this.balance = initialDeposit
9 this.accountNumber = generateUniqueId()
10 END CONSTRUCTOR
11
12 METHOD deposit(amount)
13 IF amount <= 0 THEN
14 THROW Error("Deposit must be positive")
15 END IF
16 this.balance = this.balance + amount
17 END METHOD
18
19 METHOD withdraw(amount)
20 IF amount > this.balance THEN
21 THROW Error("Insufficient funds")
22 END IF
23 this.balance = this.balance - amount
24 END METHOD
25
26 METHOD getBalance()
27 RETURN this.balance
28 END METHOD
29END CLASSNotice the this keyword. It means "the specific object I'm inside of right now." When Alice calls myAccount.deposit(100), this.balance refers to Alice's balance. When Bob calls his deposit(200), this.balance refers to Bob's balance. Same method, different object, different data.
Anatomy of a Class
Name: What kind of thing is this? (BankAccount, Customer, Order) Properties: What does it know? (balance, ownerName, accountNumber) Constructor: How is it born? What does it need at creation time? Methods: What can it do? (deposit, withdraw, transfer) Every class you'll ever write has these four components. The art is deciding what goes where.
Encapsulation: Hiding the Guts
Here's the principle that separates good class design from bad: encapsulation. It means hiding the internal details of how a class works and exposing only what the outside world needs to interact with.
Think about an ATM. You see a screen, a keypad, a card slot, and a cash dispenser. That's the public interface. Behind the panel? There's a safe, a computer, a network connection, a mechanical arm that counts bills, sensors, a printer. You never touch any of that. You don't need to. The ATM hides its complexity behind a simple interface.
Classes work the same way:
Public: Visible to everyone. The methods and properties that other code interacts with. deposit(), withdraw(), getBalance().
Private: Hidden from everyone outside the class. Internal state and helper methods that shouldn't be accessed directly. balance (the raw number), validateTransaction(), logActivity().
Protected: Visible to the class itself and its descendants (subclasses, which we'll cover in Lesson 5), but not to outsiders. Think of it as family access: your kids can see the family budget, but the neighbors can't.
Why does this matter? Because if balance is public, any code anywhere in your program can write account.balance = -1000. That's a bug that's nearly impossible to track down. If balance is private and only modifiable through deposit() and withdraw(), both of which validate the amount, then a negative balance is structurally impossible. The design prevents the bug.
Getters and Setters: Controlled Access
Sometimes you need to let the outside world read a private property without letting them change it. That's what getters are for:
1CLASS Temperature
2 PRIVATE _celsius: Decimal
3
4 CONSTRUCTOR(celsius)
5 this._celsius = celsius
6 END CONSTRUCTOR
7
8 GETTER celsius()
9 RETURN this._celsius
10 END GETTER
11
12 GETTER fahrenheit()
13 RETURN (this._celsius * 9/5) + 32
14 END GETTER
15
16 SETTER celsius(value)
17 IF value < -273.15 THEN
18 THROW Error("Below absolute zero")
19 END IF
20 this._celsius = value
21 END SETTER
22END CLASSThe temperature object stores Celsius internally. You can read it in Fahrenheit (computed on the fly). You can set it, but only to physically possible values. The setter acts as a bouncer: valid data gets in, invalid data gets rejected.
Static vs Instance: The Blueprint's Own Properties
Everything we've discussed so far belongs to instances, individual objects. But sometimes the blueprint itself needs to hold data or behavior.
Static members belong to the class, not to any particular object. There is one copy, shared by everyone.
1CLASS Customer
2 STATIC count: Integer = 0
3 STATIC CONSTANT MAX_LOYALTY_POINTS = 100000
4
5 name: String
6 loyaltyPoints: Integer
7
8 CONSTRUCTOR(name)
9 this.name = name
10 this.loyaltyPoints = 0
11 Customer.count = Customer.count + 1
12 END CONSTRUCTOR
13
14 STATIC METHOD getCustomerCount()
15 RETURN Customer.count
16 END STATIC METHOD
17END CLASSCustomer.count doesn't belong to Alice or Bob. It belongs to the concept of Customer itself. Every time a new Customer is created, the count goes up. You access it through the class, not through an instance: Customer.getCustomerCount(), not alice.getCustomerCount().
When to use static: Constants (Math.PI), utility functions (Math.round()), counters, factory methods, configuration.
When NOT to use static: Anything that varies between objects. If Alice's name isn't the same as Bob's name, it's not static.
Static vs Instance Decision
Ask yourself: "Does this belong to the concept or to a specific thing?" PI is the same for all math operations → static. Customer name is different for each customer → instance. Total customer count describes the collection, not an individual → static. Loyalty points vary per customer → instance. If in doubt, make it an instance member. You can always promote to static later. The reverse is harder.
"The goal of software architecture is to minimize the human resources required to build and maintain the required system." Robert C. Martin wrote that in Clean Architecture, and encapsulation is one of the primary tools for achieving it. The less exposed complexity your classes have, the fewer things can go wrong.
Putting It All Together
Let's design a complete class for a real scenario. You're building a music playlist:
1CLASS Playlist
2 PRIVATE _name: String
3 PRIVATE _songs: List<Song> = []
4 PRIVATE _createdBy: String
5 PRIVATE _createdAt: DateTime
6 STATIC playlistCount: Integer = 0
7
8 CONSTRUCTOR(name, createdBy)
9 this._name = name
10 this._createdBy = createdBy
11 this._createdAt = DateTime.now()
12 Playlist.playlistCount = Playlist.playlistCount + 1
13 END CONSTRUCTOR
14
15 GETTER name() RETURN this._name
16 GETTER songCount() RETURN this._songs.length
17 GETTER totalDuration()
18 total = 0
19 FOR EACH song IN this._songs
20 total = total + song.duration
21 END FOR
22 RETURN total
23 END GETTER
24
25 METHOD addSong(song)
26 IF this._songs.contains(song) THEN
27 THROW Error("Song already in playlist")
28 END IF
29 this._songs.add(song)
30 END METHOD
31
32 METHOD removeSong(song)
33 this._songs.remove(song)
34 END METHOD
35
36 METHOD shuffle()
37 this._songs = randomize(this._songs)
38 END METHOD
39
40 METHOD getNextSong()
41 IF this._songs.isEmpty() THEN
42 RETURN null
43 END IF
44 RETURN this._songs[0]
45 END METHOD
46END CLASSThis class has: private properties (encapsulated state), a constructor (birth), getters (controlled read access), methods (behavior), a static counter (class-level data), and validation logic (duplicate song prevention). It's a complete, self-contained unit of functionality.