The Fundamental Difference
Objects hide their data and expose behavior (methods). Data structures expose their data and have no meaningful behavior. This isn't aesthetic — it determines how your code handles change. Adding a new operation to a data-structure world is easy (add a function). Adding a new type to an object world is easy (add a subclass). Each is hard for the other case.
// Data Structure — expose data, no behavior
struct Point:
x
y
// Operates on data structures (procedural)
function distance(p):
return sqrt(p.x * p.x + p.y * p.y)
// ─────────────────────────────────────────────
// Object — hides data, exposes behavior
class Point:
_x = 0
_y = 0
function distanceToOrigin():
return sqrt(_x * _x + _y * _y)
function translate(dx, dy):
return new Point(_x + dx, _y + dy)
The Law of Demeter
A method should only call methods on: itself, its arguments, objects it created, or its direct components. Never reach into an object to get another object and call that. Train wrecks — a.getB().getC().doSomething() — expose implementation details across the entire chain and make refactoring a nightmare.
Train wreck — violates Law of Demeter
amount = order.getCustomer()
.getWallet().getBalance()
if order.getCustomer()
.getWallet().getBalance()
> order.total:
order.getCustomer()
.getWallet().deduct(order.total)
Tell, don't ask — delegate to the right owner
order.chargeCustomer()
// Inside Order:
function chargeCustomer():
customer.charge(total)
// Inside Customer:
function charge(amount):
wallet.deduct(amount)
DTOs and Active Records
Data Transfer Objects (DTOs) are pure data structures — no business logic. Use them to move data between layers: HTTP response → service, database row → domain. Active Records add save()/find() to DTOs. The trap: never add business logic to an Active Record — create a separate domain object for that.
// ✓ DTO — pure data, no behavior
struct UserDTO:
id
name
email
createdAt
// ✓ Active Record — data + persistence only
class UserRecord:
id
name
email
function save(): ...
function find(id): ...
// ✗ Hybrid — Active Record with business logic (don't do this)
class UserRecord:
...
function canAccessAdmin(): ... // ← business logic here
function calculateSubscriptionPrice(): ... // ← wrong layer
Code Challenge
Break the Train Wreck — the chained calls expose three internal objects. Delegate to the right owner at each level.
Key takeaway
Objects are not better than data structures — they're different tools. Pick one and commit. Hybrids give you the worst of both.