Menu
Home Articles About Work With Me
Architectural blueprints and technical drawings spread across a drafting table
Software Engineering Apr 16, 2026 • 15 min read

Blueprints and Instances: A Class Is Not an Object

A blueprint is not a house. A class is not an object. This lesson teaches the most important distinction in object-oriented programming: designing at the class level, building at the object level. Plus encapsulation, constructors, and why you should hide the guts.

Share:
Lee Foropoulos

Lee Foropoulos

15 min read

Continue where you left off?
Text size:

Contents

Complete Guide | Lesson 3: First PrinciplesLesson 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.

You design at the class level. You run at the object level. The class says "a customer has a name and can place orders." The object says "THIS customer is Alice and she just ordered a latte."

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.

Bank ATM machine with keypad and screen in a modern interior
An ATM is a perfect class example. Every ATM has the same interface (screen, keypad, card slot). Every ATM connects to different accounts with different balances. The class defines the interface. Each machine is an instance.

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 CLASS

Now 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)
1 blueprint
can produce unlimited objects. That's the entire power of classes: define once, instantiate everywhere, each instance carrying its own independent state.

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 CLASS

Notice 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.

Encapsulation is the ATM principle: expose a simple interface, hide the machinery. If nobody outside the class can directly set balance to negative one thousand, that bug literally cannot exist.

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.

70%
of bugs in enterprise software trace back to unexpected state modifications. Encapsulation prevents the entire category by controlling how state can be changed.

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 CLASS

The 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 CLASS

Customer.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.

Organized library shelves with labeled sections and systematic arrangement
A library is a class with static properties (totalBooks, location, hoursOfOperation) and instance properties unique to each book (title, author, isCheckedOut). The system works because the design separates what belongs to the whole from what belongs to each part.

"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 CLASS

This 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.

Factory production line producing identical items from a template design
A factory produces objects from a template. Each item has the same structure but may have different specific properties. That's a class in action: one design, unlimited instances.
A well-designed class is like a well-designed tool: you shouldn't need to read the instruction manual to use it. The public interface tells you everything. The private implementation is none of your business.
Lesson 4 Practice 0/6
How was this article?

Share

Link copied to clipboard!

You Might Also Like

Lee Foropoulos

Lee Foropoulos

Business Development Lead at Lookatmedia, fractional executive, and founder of gotHABITS.

🔔

Never Miss a Post

Get notified when new articles are published. No email required.

You will see a banner on the site when a new post is published, plus a browser notification if you allow it.

Browser notifications only. No spam, no email.

0 / 0