UPI Study › Courses › C++ Programming › Free lessons › Lesson 11.4 Free with a free account
Why Encapsulation Strengthens Design
C++ Programming · Chapter 11: Inheritance and PolymorphismSection 11.4 · lesson 81 of 84video coming soon
← Overriding Inherited BehaviorNext lesson: How Polymorphism Enables Flexible Code →
In this lesson
Shielding internal details.
- State a type's invariant; see why public data can't protect it
- Validate changes, including values constrained together
- Spot a type that leaks its internals, and refactor it
- Explain coupling, cohesion, and abstraction versus encapsulation
Transcript
Everything said in the video, section by section (1,743 words). Read along, or search it.
Opening Question
What Does Private Data Really Protect
Encapsulation is usually taught as a rule about keywords: make data private, and add accessors. That version is easy to follow, and easy to follow badly. A program can obey it exactly, and gain nothing. So here's the question. If a type has private data and a getter and setter for every member, what exactly has it protected? The idea underneath is worth more. A well-encapsulated type makes a promise about itself that no outside code can break. This section is about identifying that promise, and arranging for it to hold.
Example Setup
Three Rules for a Thermostat
Consider a thermostat. It holds a low limit, a high limit, and a set point. Three statements must be true at all times. The low limit isn't above the high limit. The set point is between the two. And both limits are within what the hardware can deliver.
Definition
The Invariant Is the Promise
Those statements are the type's invariant: conditions that hold from the moment an object is created, until it ceases to exist. An object with a low limit of forty and a high limit of twenty-eight isn't a thermostat in any useful sense. It's three numbers sitting together in memory. The invariant is the promise. Encapsulation is the machinery that makes the promise keepable.
Problem Demo
Public Members Let Invariants Break
Written with public members, the type can state its invariant only in a comment, which means it can't really state it at all. Here it starts as intended: a range of sixteen to twenty-eight, set to twenty-two. One assignment sets the point to ninety-five. Another sets the low limit to forty. Two ordinary assignments, and the object now violates both of its stated conditions. Nothing warned, nothing failed, and the comment is still sitting there, being wrong.
First Fix
Compiler Enforces Private Members
Making the members private closes the direct route, and the compiler enforces it, rather than asking politely. Assigning to the set point from outside is now a build error: the message calls it a private member of Thermostat.
Mechanism
Checked Operations Guard Every Change
What replaces the direct route is a set of operations that check before they change anything. Set set point refuses any value outside the current limits, and returns false. Otherwise, it makes the change and returns true. Here's the object passing through four attempted changes. Setting the point to ninety-five is refused. Twenty-four is accepted. A range of forty to twenty-eight is refused. And twenty to thirty is accepted. It refused two of them, and remains a valid thermostat. That's the entire mechanism. The constructor is the other half of it: the object starts valid. An object created valid, and changeable only through checks, is valid at every moment of its life. Encapsulation doesn't shrink the space of possible values. It removes every route into the invalid part of it.
Common Mistake
Getters and Setters Protect Nothing
The common way to satisfy the rule, without gaining anything, is a getter and a setter for every member. Every value can still be reached and changed, so the invariant is no better protected. The type has simply become longer. Worse, some invariants can't be protected one member at a time. Suppose each limit has its own validating setter. Now move the range from sixteen to twenty-eight, up to thirty to forty. Setting the low limit first fails, because thirty is above the current high limit. Setting the high limit first succeeds, leaving sixteen to forty, a state nobody wanted. Each setter did exactly what it promised. The rule that follows: values constrained together must be changeable together, through one operation that checks the pair. Where a per-member setter can't keep the invariant, its existence is the fault.
Decision Guide
What a Type Should Offer
For each proposed operation, the question isn't whether a member needs reaching. It's whether the type needs to offer this. Six situations cover most cases. If callers genuinely need a value, offer an accessor. Reading can't break an invariant. If they need to change it, and the change can be validated alone, offer a validating mutator. If it's constrained with another value, offer one operation for the pair. Implementation details, like a count or a capacity, get nothing. And if nobody outside has needed something yet, offer nothing until they do. If callers only read a value to perform one calculation, offer the calculation itself. Is within range, rather than the two limits. A type asked for its limits has exported a decision. A type asked whether a temperature is in range has answered a question, and kept the decision.
Hidden Failure
Private Members Can Still Leak
A type can make every member private, and still be wide open, if one of its operations hands out a route to the data. This reading log validates every addition, checking both the range and the capacity. But raw data returns a pointer to the internal array, and the front door is bypassed. Adding two hundred is correctly refused. Then the caller writes nine thousand nine hundred ninety-nine over the first reading, and minus nine thousand nine hundred ninety-nine into a position that was never added. The size still reports three. Every guarantee has evaporated, and the private label had nothing to do with it. This is the failure that survives a review, because the type looks encapsulated.
Warning Signs
Spotting and Fixing Leaky Operations
Here are the signs that a type leaks. Raw data hands out write access to the whole array. Replace it with element operations that check the position. A value at function returning a modifiable reference allows an unchecked write. Replace it with a reader, plus a validating writer. A getter returning the internal container by reference hands out everything, even resizing. Return a copy, or offer the specific queries callers make. And a getter returning an implementation type hands out the choice itself: give the information, not the structure. The test for a leak: using only this operation, can a caller put the object into a state the invariant forbids? For any operation returning a plain pointer, or a modifiable reference to internal data, the answer is almost always yes.
Second Argument
Coupling and the Cost of Change
So far the argument has been about correctness. The second argument is about change, and it matters most on a program of any size. Coupling is how much one piece of code depends on the details of another. With public members, every caller is coupled to your choice of member: its name, its type, and its meaning. Rename one, and four callers break. Through operations, callers depend only on the operations. The members underneath can be renamed, retyped, or replaced by a calculation, and nothing outside changes.
Related Concept
Cohesion and Design Goals
Cohesion is the other half of the pair: how well the contents of one type belong together. High coupling freezes internal decisions, so any change ripples outward. Low coupling lets internals be reworked freely. Low cohesion looks like a thermostat that also formats reports and manages a file. High cohesion means every member serves one stated purpose. Encapsulation improves cohesion almost as a side effect. Deciding what to expose forces you to say what the type is for. Aim for low coupling and high cohesion. They tend to move together: a type that does one thing exposes less, and is harder to become entangled with.
Distinction
Abstraction Versus Encapsulation
Abstraction and encapsulation are constantly confused. The distinction is sharp once stated. Abstraction is a decision. It asks what a type should offer, in what terms, and concerns the interface. Encapsulation is enforcement, and concerns the boundary. It asks how that decision is made unbreakable. Abstraction fails when the interface is wrong. Encapsulation fails when the interface can be bypassed, however good it is. For the thermostat, abstraction offers a target and a range, not raw voltages. Encapsulation means nobody sets a target outside the range. Without encapsulation, abstraction is good intentions with no guarantees. Without abstraction, encapsulation is unbreakable, and useless. And abstraction comes first. You can't enforce an interface you haven't chosen. An interface of one getter and one setter per member was never chosen. It was derived mechanically from the data, and enforcing it protects nothing.
Inheritance Problem
Protected Members Break the Invariant
Across an inheritance boundary, there is a third audience. A derived type isn't the base, and it isn't an unrelated caller. Here the base validates its set point, but the members are protected, so every derived type can see them. Holiday thermostat's go away sets the point to five directly, and no check happens. Read the two results together. The validating operation correctly refused five. Then the derived type set it to five anyway. The invariant is broken, by code from the same family, written in good faith.
Consequences
Three Costs of Exposing Members
Exposing members to derived types commits you to three things. You can't enforce the invariant any more. Every derived type, including ones written years later, can change those members without passing any check. You can't change them either. Exposed members are part of the interface. Renaming or removing one breaks every derived type in existence. And the audience only grows. Outside callers can be found and counted. Derived types can be written by anyone, at any time. The default worth holding to: keep members private. Where a derived type genuinely needs privileged access, expose an operation that performs the check, not the data that skips it.
Key Terms
Vocabulary of Encapsulation
Key terms. An invariant is a condition that must hold for an object throughout its life. Encapsulation keeps data and its permitted operations together, and closes every route that would bypass them. An accessor returns information without changing the object. A mutator changes it, and earns its place by validating. Leaking internals means offering an operation that hands out unrestricted access to internal data. Coupling is dependence on another's details, and lower is better. Cohesion is how well a type's contents belong together, and higher is better. And abstraction is choosing what a type should offer, and in what terms.
Quick Check
Which Playlist Operation Breaks the Limit
Quick check. A Playlist allows at most a hundred songs, kept in a private vector. Add checks the limit, count returns the size, and songs returns the vector itself, by reference. Which one lets a caller break the limit? Pause and think. Songs. Through that reference, a caller can push back as many songs as it likes, and add's check never runs. Return a copy, or a const reference, and the limit holds again.
Key Takeaways
One-Sentence Summary of Encapsulation
To summarize, in one sentence. A type's invariant is the promise it makes about itself. Encapsulation is the arrangement of private data, validating operations, and a narrow interface that makes the promise impossible to break. Change constrained values together, and expose what the type needs to offer, not what its members happen to be. A pointer or reference to internals defeats every check. And keep data private from derived types too.
About C++ Programming at UPI Study
C++ Programming (Computer Science 242) is a self-paced online course of 84 video lessons in 11 chapters, from your first program to decisions, loops, arrays, functions, classes, files, pointers and inheritance. It carries a NCCRS credit recommendation for 3 semester credits.
Every lesson transcript is free on pages like this one, and the videos are free with a free UPI Study account. Enrolling adds a practice quiz after each lesson, the graded work and final exam that earn the credit, lifetime access, and an official transcript on completion. UPI Study has helped over 130,000 students with college credits. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.
Enroll now → Course details Download the syllabus Every course with PRO →
Why take it here
Why students take C++ Programming with UPI Study
Where the credit goes
Universities that work with NCCRS
C++ Programming carries a NCCRS credit recommendation. These universities work with NCCRS; some are UPI Study's signed partners. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.
Thomas Edison State UniversitySigned partner
University of the PeopleSigned partner
Charter Oak State CollegeSigned partner
Strayer UniversitySigned partner
Columbia Southern UniversitySigned partner
Indiana TechSigned partner
Penn StateWorks with NCCRS
Southern New Hampshire UniversityWorks with NCCRS
SUNYWorks with NCCRS
+many more cooperating universitiesFind my college →Find my college → Not listed? Ask Athena in the corner of this page, or book a 15-minute call.
FAQ
Questions students ask us
About Why Encapsulation Strengthens Design
What is a class invariant in C++, and why can't public data members protect it?
Why do a getter and setter for every private member not give good encapsulation?
How can a class with all private members still leak its internals?
What is the difference between abstraction and encapsulation in C++?
About the C++ Programming course
How many college credits is C++ Programming?
How much does C++ Programming cost and what is included?
Which universities accept UPI Study credits?
How long does C++ Programming take?
How are the exams done?
How do my credits reach my college?
Ready for the full course? Earn 3 credits in C++ Programming.
84 video lessons, a quiz after each one, and the graded work that earns the credit. Recommended for credit by NCCRS.
3 credits · NCCRSEnroll →