1 Object-Oriented Programming Foundations
A progressive guide to the core concepts of object-oriented programming and the design choices that connect classes, objects, abstractions, and collaborating components.
The -oriented paradigm
models software as interacting objects. Each combines information with operations related to that information, so a program can represent both physical entities such as bank accounts and abstract concepts such as shopping carts, network connections, or calendar events.
The main goals are:
Modularity: Related data and behavior stay together.
: Important features are exposed while unnecessary details are hidden.
Reuse: Existing types and components can be reused or specialized.
Maintainability: Changes can often remain inside one .
Flexibility: Different types can respond to the same operation in type-specific ways.
A useful way to understand the paradigm is to distinguish what an knows from what it can do. Its stored information is its state, and its available operations are its behavior.
Takeaway: OOP organizes responsibilities around objects so that software structure reflects the concepts and collaborations in the problem domain.
Classes, objects, state, and behavior
A is a blueprint for a kind of . It specifies the data an may hold and the operations it may perform. An is a concrete instance created from that . A single can produce many objects, and those objects can have different state values while sharing the same general structure and behavior.
A commonly includes:
Fields or properties: Data associated with an .
Methods: Operations that an can perform.
Constructors: Operations used to initialize a new .
Access modifiers: Rules that control which code may use a member.
For example, a BankAccount could describe an account number, an owner, and a balance. A checking account and a savings account can therefore be separate objects with different balances, even though both come from the same . In languages such as Java and C#, a variable holding a generally refers to that , so more than one variable can refer to the same .
Takeaway: A defines a kind of thing; an is one particular thing created from that definition.
State, behavior, and
An 's state is the information stored in it at a particular time. For a bank account, state might include an account identifier and current balance. Behavior is the work the can perform, such as depositing money, withdrawing money, or reporting a balance.
combines state and behavior while controlling access to internal details. A well-encapsulated exposes a small, understandable public and protects invariants, which are conditions that must remain true. For example, if a bank account must never have a negative initial balance, its constructor can reject invalid input, while a deposit method can reject nonpositive amounts.
Keeping a balance private means that client code cannot assign an invalid value directly. Instead, it must use operations that validate changes. This provides two benefits:
Protection: Outside code cannot arbitrarily corrupt the 's state.
Implementation independence: The can change how its data is represented without requiring changes to callers.
is more than simply making fields private. It also requires meaningful operations and boundaries that prevent callers from depending on unnecessary implementation details.
Takeaway: Good lets an control its own state and preserve the rules that make that state valid.
and interfaces
creates a relationship in which a subclass reuses and specializes a more general superclass. For example, a SavingsAccount can extend a BankAccount by inheriting account behavior and adding an interest-related operation. This is an “is-a” relationship: a savings account is a kind of bank account.
can reduce duplicated code, but it also creates a strong dependency between the base and derived classes. It should be used only when the specialized type truly conforms to the promises of the general type. A subclass that violates those promises can make the hierarchy confusing or unsafe.
An defines a contract rather than a complete implementation. A PaymentMethod might require a pay operation. A credit card and a gift card can implement that contract differently, even if they are otherwise unrelated classes. This encourages programming to an : client code can depend on the capability represented by PaymentMethod instead of depending on one concrete payment .
Interfaces are particularly useful when unrelated classes share a capability, such as being printable, comparable, or encryptable. Many languages also allow a to implement multiple interfaces, providing a flexible alternative to inheriting from multiple classes.
Takeaway: Use for a genuine substitutable “is-a” relationship, and use interfaces when clients need a shared capability or contract.
and
builds a by giving it objects of other classes as parts. An Order has a PaymentMethod; it is not a kind of payment method. The order can delegate payment work to the contained , allowing the payment method to be replaced without changing the order's basic logic.
often reduces because a depends on the behavior of a component rather than on the implementation details of a base . It also makes behavior easier to vary at run time. A useful guideline is to prefer over when the relationship is not naturally substitutable or when behavior should be assembled from interchangeable parts.
and work especially well together. An order can hold any that satisfies the PaymentMethod , and a call to pay can use the implementation belonging to the actual payment . The order therefore remains independent of whether payment is handled by a credit card, gift card, or another compatible type.
Takeaway: expresses “has-a” relationships and supports flexible designs built from replaceable collaborators.
Applying the ideas in -oriented design
-oriented design decides which objects a system needs, what responsibilities they have, and how they collaborate. A practical process is:
Identify domain concepts. Look for important nouns and capabilities, such as
Order,Customer,Invoice, andPaymentMethod.Assign responsibilities. Decide which should own each piece of data or perform each operation.
Define clear boundaries. Keep internal state private and expose only the operations clients need.
Choose relationships carefully. Use for genuine “is-a” relationships and for “has-a” relationships or replaceable parts.
Depend on abstractions. Use interfaces or abstract types when callers should not depend on a particular implementation.
Keep classes cohesive. Give each a focused purpose instead of combining unrelated responsibilities.
Minimize . Limit how much classes know about one another's internal details.
In an online store, an Order might contain order lines, a customer reference, and a payment method. Its behavior could include calculating a total and submitting payment. Private fields and validated methods encapsulate the order's state; a PaymentMethod defines a shared contract; lets the order contain a payment component; and lets different payment objects respond to pay in different ways.
A strong design is not the one with the most classes or the deepest hierarchy. It is the one whose structure makes responsibilities, allowed changes, and collaboration easy to understand.
Final takeaway: Select OOP tools according to the relationship and responsibility they express. Favor clear abstractions, high cohesion, low , protected state, and components that can change without unnecessary effects elsewhere.