Memra

Abstraction, architecture, patterns, and separation of concerns

◈ 7 cards

Levels of abstraction, procedural vs data abstraction, architecture and patterns as design concepts, and the superadditive-complexity argument behind separation of concerns.

Abstraction is a choice of level, not a hiding place

When you look for a modular solution to a problem, many levels of abstraction are available to you, and they form a ladder. At the top the solution is stated in the language of the problem environment — the words a member of the tool library would use. Lower down, problem vocabulary is mixed with implementation vocabulary. At the bottom the solution is stated so precisely that it can be typed in. Every rung is a complete statement of the solution; what changes is how much detail is suppressed. That is the whole idea, and it is why abstraction is a design concept rather than a coding trick: it lets you commit to a decision at one level and defer everything below it without leaving the decision vague.

Two kinds of abstraction do the work. A procedural abstraction is a named sequence of instructions with a specific and limited function — the name implies the function and suppresses the steps. A data abstraction is a named collection of data describing a data object, with the attributes that belong to it. The two travel together: a procedural abstraction almost always operates on a data abstraction, and naming both is what turns a vague intention into something you can place on a diagram.

Architecture and patterns are design concepts too

Software architecture is the overall structure of the software: the components, the way they interact, and the structure of the data they use — plus, in the broader sense, the properties that give a system conceptual integrity. It is listed among the design concepts because it is the first abstraction that lets you reason about the whole system from its parts, before any part exists. A design pattern is the other reuse mechanism: a named description that binds a context, a problem with its competing forces, and a solution — so that when the same forces recur you can apply a proven answer instead of reinventing one, and know in advance what it will cost you. Both get a full treatment later in the course; here they matter only as members of the catalogue, because both are ways of not solving the same problem twice.

Separation of concerns, and why divide-and-conquer really works

Separation of concerns says that a hard problem becomes tractable the moment you carve it into parts that no longer have to be reasoned about together — each part can then be worked on, or tuned, on its own terms. The word concern is precise: a concern is a feature or a behaviour specified as part of the requirements model. So separating concerns is not arbitrary chopping — it is cutting along lines the requirements already drew.

What makes this a principle rather than a platitude is the observation underneath it, which is not obvious. If complexity were additive, subdividing a problem would merely move the work around and gain you nothing. Complexity is not additive: the perceived complexity of two problems taken together is reliably greater than the sum of their complexities taken separately, because you also have to hold their interactions in your head. Subdivision therefore removes effort rather than relocating it — which is why divide and conquer is a strategy and not just a habit. Separation of concerns is also the parent of three other concepts in this chapter: modularity is its most common manifestation, functional independence is the criterion for having done it well, and stepwise refinement is the procedure for doing it in stages.

Worked example — "collect a tool" at three levels

Take one BorrowBox operation and write it at three rungs of the ladder. At the top: collect a tool. That is a procedural abstraction stated in the member's language; it implies a long sequence — open the app, show the QR token, hold it to the locker bank, take the drill, close the door — without committing to any of it. One level down: verify the token, open the assigned door, record the handover. Now problem vocabulary is mixed with solution vocabulary, and the data abstraction has a name too — a CollectionToken with a reservation reference, a locker identifier, an issue time and a validity window. At the bottom: check the token's HMAC against the locker's key, reject it if the issue time is more than sixty seconds old, pulse the door relay, and insert an audit row.

Now apply separation of concerns to the same operation. "Decide whether this token is valid" and "open a physical door" are two concerns, and they came from two different requirements — one about security, one about hardware behaviour. Fuse them into one LockerService method and the day the vendor changes its relay protocol you are editing the same code that holds the token-validation rule, and the tests you must re-run are the security tests. Keep them apart — a TokenVerifier that answers "is this legitimate?" and a LockerGateway that answers "did the door open?" — and each change lands in one small place with one small test suite. That is the payoff, and notice it is not aesthetic: it is measured in what a future change costs.

collect a toolthe member's wordsverify token, open door, record handoverdesign languagecheck HMAC, pulse relay, insert audit rowimplementationproblem languagecodeOne operation, three rungs.
Each band is a complete statement of the same operation — only the suppressed detail changes. The named data abstraction (CollectionToken) travels down the ladder alongside the procedural one.
NORMAL ~/memra/learn/comp-410/abstraction-and-separation-of-concerns utf-8 LF