Control Abstractions and Machine Independence
A black box is a component that can be used without knowing its implementation, provided its interface is known.
Using a Television Without Knowing Its Circuits
Think about how you use a television. You press a button, choose a channel, and adjust the volume. You never need to know how the picture is produced, what components sit on the circuit boards, or how the signal is processed on its way to the screen. You only need to know what the controls do. That everyday experience is the starting point for one of the most useful habits in computer science: treating a part of a system as a black box.
A black box is a component that can be used without knowing its implementation, provided its interface is known. Keep this definition in mind as you read. The question to carry through the whole article is this: when you use a part of a program, what do you actually need to know, and what can you safely ignore?
The Two Faces of a Black Box
A black box is a component that can be used without knowing its implementation, provided its interface is known. The interface is the set of things a user can do with the component and the results they can expect. The implementation is the hidden machinery that actually carries out the work.
Every black box has two faces. The face the outside world sees is its interface. The hidden part, the machinery that does the work, is its implementation. Some implementations are short and simple, while others involve many steps and many parts working together. The key point is that the two faces are separate. A component can be understood from its interface alone, and its implementation can be read, rewritten, or replaced separately.
Read the diagram as a statement about responsibility. The caller reaches the component only through its interface. The caller is responsible for supplying correct inputs and for using the result. The body is responsible for producing that result. Because the caller never touches the body directly, the body is free to change, and that freedom is exactly what the black box principle is designed to protect.
To use a black box, you should not need to know anything about its implementation. What you need to know is its interface. If you know the inputs a box accepts and the behavior it promises in return, you can use it correctly even if you have never seen how it works inside.
Television Tubes Replaced by Transistors
When the insides of television sets changed from vacuum tubes to transistors, the people who watched them did not need to learn anything new. They did not even need to know what the change meant. The controls and the picture stayed the same, and that sameness is what allowed the inside to be replaced. The television is a black box whose interface, the controls and the picture, held steady while its implementation was swapped out.
Tracing a Call to multiply
Suppose you have a subroutine that multiplies two numbers. A caller only needs to know that the subroutine takes two values and returns their product. The body could use a loop of repeated additions, or it could use a direct multiplication. Both versions below share the same interface, multiply with two parameters, and both produce the same results for every caller when b is a whole, non-negative value.
12- The parameters a and b are the interface the caller relies on.
- The loop of additions is the implementation hidden behind that interface.
- The call
multiply(3, 4)returns the product 12.
Following one call from start to finish
A program calls multiply(3, 4). Trace what the caller sees and what the body does.
- 1Caller writes the call
The caller uses only the name multiply and the two values 3 and 4, based on the interface.
- 2Values enter the body
Inside the subroutine, a takes the value 3 and b takes the value 4.
- 3Body does the work
The hidden steps produce the product, 12.
- 4Control returns to the caller
The subroutine hands back 12, and the caller continues from the place where it made the call.
The caller receives 12 and never needed to know whether the body used a loop or a direct multiplication.
The sequence diagram shows the control transfer a subroutine creates. Execution jumps from the call into the body, the inputs travel with it, and when the body finishes, execution resumes in the caller right after the call. The caller's code never has to point into the middle of the body.
Changing the Body Without Changing the Caller
The body of multiply can be rewritten in a different way while the interface stays fixed. The caller wrote multiply(3, 4) once, based only on the interface, and it keeps working after the body is rewritten. The inside changed, and the outside did not need to know.
def multiply(a, b):
total = 0
for _ in range(b):
total = total + a
return totalBody uses a loop of repeated additions.
def multiply(a, b):
return a * bBody uses a direct multiplication. The interface and the result for multiply(3, 4), which is 12, are unchanged.
Subroutines as Procedural Abstractions
The black box idea appears in programming under another name when we talk about subroutines. A subroutine is a procedural abstraction. Its interface is an abstraction that lets you carry out some procedure without worrying about how that procedure is implemented. Calling a subroutine is therefore a way of using a black box.
Calling the idea a procedural abstraction emphasizes something useful. A subroutine packages an action, and the name and parameters of the subroutine are what you rely on. The steps inside the subroutine are a private matter. Subroutines are one of the main tools for building abstractions out of smaller pieces of code.
Information Hiding and Machine Independence
Abstraction, in general, hides information. An abstraction keeps quiet about what lies behind it, in its implementation. This hiding is called information hiding, and it is the reason a black box can be trusted: the details that are not part of the interface are kept out of view.
The source describes two forms of this hiding. Control abstractions hide details of the machine. Because of this, the same Java control structures can be used on different types of CPU, even though each CPU has its own underlying machine language. Procedural abstractions hide the body of a subroutine, so the inside can be changed without affecting the programs that use the subroutine.
Information hiding keeps implementation details behind an interface. That is what lets the inside of a subroutine change without affecting the programs that use it.
Mistakes When Reading a Black Box
Common mistake Treating the body as part of the contract
What it looks likeA caller relies on multiply using a loop, so it breaks when the body is rewritten to use a direct multiplication.
Why it failsThe contract is the interface and the promised behavior. The loop was an implementation detail the caller was never supposed to depend on.
Do this insteadDepend only on the name, the parameters, and the promised results.
Common mistake Assuming the same signature guarantees the same behavior
What it looks likeA rewrite of multiply keeps the two parameters but returns a different value for some inputs.
Why it failsA black box stays a black box only when the behavior seen from outside stays the same, not merely the shape of the call.
Do this insteadCheck that the promised results still hold for the inputs callers actually use.
Common mistake Needing to read the implementation before using a subroutine
Predicting the Effect of Changing average
Apply the principle to a new situation. Suppose a subroutine named average takes a list of numbers and returns their mean. A teammate rewrites the body to use a different sequence of steps but still returns the same mean for every list. Decide first whether any program that calls average needs to change.
What do you think happens?
After the teammate rewrites the body of average, must any program that calls average be changed?
Reveal answer
No. Because the interface is unchanged and average still returns the same mean for every list, the callers are unaffected.
The callers depend only on the interface, which is a name, a list parameter, and a promised mean. The new sequence of steps is a change inside the box, and the outside behavior has not changed.
A subroutine called total takes a list of numbers and returns their sum. Its body first uses a loop to add the numbers, and you later replace the body with a built-in summing operation that returns the same sum for every list. Which programs must change, and what must stay true for that answer to hold?
Hints
- Ask whether the name, parameters, and returned value changed.
- Consider what the caller can observe, and whether the new body promises the same results.
Key Takeaways
- A black box is a component that can be used without knowing its implementation, provided its interface is known.
- The interface is the face the outside world sees, consisting of inputs and promised results. The implementation is the hidden machinery that does the work, and it can change as long as outside behavior stays the same.
- A subroutine is a procedural abstraction, so calling it is a way of using a black box, and information hiding keeps its body out of the caller's view.
- A change to a subroutine's body does not affect its callers when the interface and the behavior seen from outside remain the same.