UPI Study › Courses › C++ Programming › Free lessons › Lesson 9.6 Free with a free account
Designing Clean Interfaces With Abstract Types
C++ Programming · Chapter 9: Structs, Classes, and File StreamsSection 9.6 · lesson 64 of 84video coming soon
← Defining Multiple Constructor VersionsNext lesson: Tracking Down Class Bugs →
In this lesson
Simplifying complexity.
- Separate what a type does from how it does it
- Design the interface before the representation
- Specify Stack and Queue by their operations
- Read a header as a contract; keep accessors minimal
Transcript
Everything said in the video, section by section (1,422 words). Read along, or search it.
Opening Question
Using std::string Without Knowing Its Storage
You use std::string constantly, and have no idea how it stores its characters. That isn't a gap in your education. It's the point. A well-designed type lets you work entirely in terms of what it does, and leaves how it does it as somebody else's business. This lesson is about designing types that behave that way.
Core Concept
Abstraction Draws a Line Between What and How
Abstraction is drawing a line between what something does and how it does it, and letting users work on one side of that line only. You already rely on several. With a string, you assign, compare, ask its length, and print it. You've never needed to know whether short text is kept inside the object, or how growth is arranged. With a vector, you add, index, and ask the size, never knowing the growth factor. With sort, you give a range and a comparator, never knowing which algorithm runs. In every case, the ignorance is deliberate and beneficial. You can't write a wrong assumption about the growth factor into your program, if you were never told it. And the library authors can change it without breaking a line of your code. Abstraction isn't about hiding things from you. It's about deciding what you need to depend on, and making everything else unavailable to depend on by accident.
Design Method
Interface First, Representation Last
The natural instinct when writing a new type is to start with its data: what fields does it need? That order produces working code, and poor designs. You pick the fields, add a getter for each, and callers work with the fields. The representation is now public, and changing it means changing every caller. Interface first means listing what callers do, naming the operations, and only then choosing a representation, which you can change freely later. It's four questions, in order. What will callers need to do? Written as verbs, in the problem's words, like add a reading, or report the average. What does each operation need, and what does it hand back? What's guaranteed, before and after? And only now: how will this be stored? Storage comes last on purpose: it's the part you most want to keep free to change.
Definition
The Stack as an Abstract Data Type
An abstract data type is a type defined entirely by the operations it supports, and the rules they obey, with no reference at all to how the data is held. The stack is a classic example. Push makes a value the most recently added item. Pop removes the most recent item, and requires the stack not to be empty. Top reports the most recent item without removing it, and also requires a non-empty stack. Empty reports whether anything is held, and size reports how many. The governing rule is last in, first out. Push 1, 2, 3, 4, and top reports 4, with size 4. Pop, and top is 3, with size 3.
Comparison
Queue Rules Versus Stack Rules
A queue is specified the same way. Enqueue adds a value to the back of the line. Dequeue removes the item that's waited longest. Front reports it. And empty and size, as before. Its rule is first in, first out. Push A, B, C and D onto a stack, and pop removes D, the newest. Items are added and removed at the same end. Enqueue the same four, and dequeue removes A, the oldest: here, 1 is at the front. After one dequeue, 2 is at the front, with 3 left. Items are added at one end, and removed at the other. And notice what's missing from both specifications: no mention of arrays, vectors, indexes, or memory.
Worked Example
Same Stack on a Fixed Array
A stack could be built on a vector, on a fixed array with a counter, or on structures you haven't met yet. Every one of those would satisfy the specification exactly. Here is the same stack on a fixed array and a counter. The private part is different. The caller, main, is untouched. A caller written against the specification works with all of them, unchanged. The output is identical: 4 and size 4, then 3 and size 3. That's the strongest possible statement that the representation was never part of the type's meaning.
Header Contract
What the Header Promises Callers
The header file is where an abstract data type stops being an idea, and becomes something a caller can read. Everything a caller is entitled to rely on appears there, and everything else stays out. The stack header lists push, pop, top, empty and size, each with a comment. The private vector is today's choice, and no caller depends on it. The operation names, what each takes and returns, what must be true before, and what's true afterward, are part of the contract. The bodies and the container are behind it. And those comments carry real weight. Pop requiring a non-empty stack isn't an implementation detail. It's part of what the caller is agreeing to. A contract that only lists names is half a contract.
Stability
Which Changes Break Callers and Which Don't
Implementations change constantly. Something's too slow, a better container appears, or a bug forces a rethink. Interfaces should change rarely, because a change there ripples out to every caller. Replace the stack's internal vector with a fixed array and a counter, and no caller is affected. None ever mentioned the vector. Rename top to peek, and every caller is affected. The name was part of the contract. Make push reserve space in advance, and nobody's affected: only timing differs. But change what top reports, say the oldest item instead of the newest, and every caller is affected. Existing calls still compile, which makes it more dangerous than an outright break. So here's the test. If I changed my mind about how this is stored, how much code outside the type would need editing? The answer should be none.
Common Mistake
Getters and Setters for Every Field
Here is the habit worth resisting: writing a getter and a setter for every field, mechanically, before knowing whether anyone needs them. It feels like good practice, and it isn't. It publishes the field list under slightly longer names, so callers end up depending on exactly what you meant to keep free to change. This reading offers get and set for celsius, hour, and sensor ID. It's already committed to three fields of specific types.
Better Design
A Reading Log With Meaningful Operations
Compare a reading log. It offers record, average, highest, and count above. It's smaller, says more, and is far harder to misuse. Run it: three readings, an average of 21.5, a highest of 24, and two above 20. Its readings could be held in one vector, in several, or summarized as they arrive, and no caller would notice.
Decision Rules
Four Questions Before Adding an Accessor
Ask four questions before an accessor earns its place. Does a caller genuinely need this value, for a purpose I can name? If not, leave it out. It can be added later. Removing one is far harder. Is it a value in the problem's terms, rather than a stored field? If not, offer the operation the caller actually wanted. Would I still offer it if the field were stored some other way? If not, it accesses the representation, not the type. And does the matching setter make sense on its own? If not, provide the getter only. Every element of an interface is something you've promised to keep working. So a smaller interface is a smaller promise.
Checklist
Six Marks of a Good Interface
Here's a checklist for an interface. Small: every operation has a caller who needs it. Complete: anything a caller reasonably needs can be done without reaching around the type. Hard to misuse: the obvious way to call it is the correct way, and requirements like not empty are stated where they'll be read. Spoken in the problem's words: operations named for what they mean, like record and average, not for how they work. Stable: a change of representation forces no edits outside the type. And honest: what's guaranteed is written down, and anything not written down isn't promised.
Quick Check
Redesigning the Thermostat Interface
Quick check. All over a program, callers of a thermostat class get its target, add one, and set it back. What should the interface offer instead, and why? Pause and think. An operation named for what callers mean, such as raise, by one degree. Each call then states its intent in the problem's words. And any rule, like a safe maximum, lives in one place inside the type, while the stored field stays free to change.
Key Takeaways
Operations First, Storage Last
To summarize. Abstraction lets callers work on the what side of the line only. Design the operations first, and choose the storage last. Specify an abstract data type by its operations and rules, and state requirements in the header, as part of the contract. And a few meaningful operations beat a getter and setter for every field.
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 Designing Clean Interfaces With Abstract Types
What is abstraction in C++ class design?
What does it mean to design the interface before the representation?
How do you specify a stack and a queue without saying how they store data?
Should I write a getter and setter for every private field?
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 →