Why Encapsulation Strengthens Design | C++ Programming

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 →

Earn 3 college credits for C++ ProgrammingRecommended for credit by NCCRS · Computer Science 242 · self-paced · one payment, lifetime access

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.

Why take it here

Why students take C++ Programming with UPI Study

Your first real programming languageC++ teaches how programs really work: variables, control flow, functions, memory and objects. It is the base for data structures, algorithms, systems and game programming, and for most computer-science degrees.
Credit at a fraction of the priceThe same 3 credits can cost $750 or more at a US college. Here it is one payment, self-paced, with lifetime access.
Finish your degree soonerTransfer credits reduce the courses you still owe, so you can graduate earlier or lighten a heavy semester.
Strengthen an applicationA completed college-level course on an official transcript shows admissions and scholarship committees you can do the work.
Study on your own scheduleNo start dates, no deadlines. Watch the lessons, practice, and take the final when you are ready.
130,000+students helped with college creditsACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.

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.

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?
An invariant is a condition that must hold for an object from creation until it is destroyed. A thermostat's set point must stay between its low and high limits, for example. With public members, one assignment can set the point to 95 and another can set the low limit above the high limit. Nothing warns you, so the invariant breaks silently.
Why do a getter and setter for every private member not give good encapsulation?
Because every value can still be read and changed directly, so the invariant is no better protected than with public data. Values constrained together make this worse. Moving a range from 16–28 up to 30–40 with separate limit setters either fails on the first call or leaves 16–40, a state nobody wanted. Offer one operation that checks and changes the pair together.
How can a class with all private members still leak its internals?
A class leaks when an operation hands out a pointer or a modifiable reference to its private data. The caller can then write through it and skip every validation check. A getter that returns an internal vector by reference lets a caller push back past a size limit. Fix it by returning a copy or a const reference, or by offering checked element operations.
What is the difference between abstraction and encapsulation in C++?
Abstraction is deciding what a type should offer, and in what terms. Encapsulation is enforcing that decision so no one can bypass it. For a thermostat, abstraction offers a target and a range instead of raw voltages. Encapsulation ensures nobody can set a target outside that range. Abstraction comes first, because you can't enforce an interface you haven't chosen.

About the C++ Programming course

How many college credits is C++ Programming?
C++ Programming (Computer Science 242) carries a NCCRS credit recommendation for 3 semester credits. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities.
How much does C++ Programming cost and what is included?
$250, paid once, with lifetime access: all 84 video lessons, a practice quiz after each, the graded work and final exam, and an official transcript when you finish. Students who want several courses choose UPI Study PRO instead.
Which universities accept UPI Study credits?
UPI Study has signed partner agreements with universities such as Thomas Edison State University, University of the People and Charter Oak State College, and works with universities such as Penn State, SNHU and SUNY. ACE and NCCRS have over 2,100 cooperating colleges that will consider accepting credits; the final decision is with the universities. Use "Find my college" on this page to check yours.
How long does C++ Programming take?
It is self-paced with no deadlines. The 84 lessons total about 0 hours of video; most students add reading and practice time and finish in a few weeks to a few months, on their own schedule.
How are the exams done?
Quizzes and assignments are online in the course. The final exam is taken online with proctoring; a person on the UPI Study team checks assignments, usually within two days.
How do my credits reach my college?
When the course shows 100% complete, request the official transcript from your dashboard. It is sent to the college you name, and you can send it to more than one. Check your college first with "Find my college" on this page, or ask Athena.

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.

C++ Programming
3 credits · NCCRS
Enroll →