OO Interview - Classes Protect Invariants
I interviewed a recent college graduate, today. He attended the Texas A&M. That’s a good school, from what I understand. The inventor of C++ teaches there. The inventor of C++, the language that popularized object-oriented programming. That sounds like a pretty good place to earn one’s Bachelor’s degree in Computer Science. As a student there, maybe you’ll learn more than data structures and algorithms. Maybe, you’ll learn a little object-oriented design.
Seems not so much.
Now, I don’t run my interviews like most people. I like to think that when someone interviews with me, they walk away knowing more about programming than when they walked into the room with the white board. Since I work in a shop that uses C#, C++, and Python, I want to write code with other programmers that can express the code they write in too little time in nice class definitions that promote maintenance. I don’t want to work with programmers that think procedurally. I don’t want to work with programmers that interrogate rather than command.
Since the kid had only the most rudimentary OO skills, I crafted a forum that would walk us through basic OOD. He listed Python, C, C++, Java, and Lisp as the languages that he knew. I asked him to write on the whiteboard compilable Hello World programs in all five of the languages. He succeeded by writing correct programs in all of the languages but LISP.
Then, I wrote on the board addToTwo(num:int):int
and asked him to write
compilable programs in the four languages with which he succeeded to answer
the previous question. He succeeded with C (and, therefore C++). I helped him
get through the Java and Python implementations.
We moved on to classes and, with a little help, he wrote some declarations on
the board for the AddToTwo
class. I completed this exercise with a little
speech about OO.
When higher-level languages came about, like C, data was king. You declared your data structure and passed it around to methods that could change that data, make new data, save the data, do stuff with the data. Eventually, in large software systems, the code that changed the data spread out over numerous modules. Figuring out the side effects of those methods became to much to maintain.
Object-oriented programming turned that around and specified that behavior is the most important. Classes define the behavior of the system and hide the data. They entangle the two so we can better maintain our software. It provides us with better code organization. A class protects its data, its state, through the methods that you provide. A class will prevent outside code from changing its state incorrectly.
Lots of OO programmers forget this and treat their objects as structures with little or no behavior. This leads to where we were back in the C days.
This interview will test your ability to use the OO training you received in school and that you’ll learn during this interview. Remember, in OO, behavior is king.
“So, interview candidate,” I said, “you now work at a zoo. You build software to manage the animals’ information at the zoo. What’s your favorite animal?”
“Um, the giraffe.” On the white board, I wrote:
1 | class Giraffe(object): |
1 | for animal in [Giraffe()]: |
I asked him to write a class definition for his least favorite animal and add to the loop. He responded with the following code.
1 | class Rat(object): |
1 | for animal in [Giraffe(), Rat()]: |
After complimenting him, I wrote class definitions for Whale
and Spider
.
1 | class Whale(object): |
1 | class Spider(object): |
I also extended the loop.
1 | for animal in [Giraffe(), Rat(), Whale(), Spider()]: |
“Now,” I asked, “what do you think of all of these classes?”
“I don’t like them,” he replied.
“What should we do about it?”
He thought for the briefest moment. “I think we should have one animal class!”
“Okay. Show me.”
He erased what we had and replaced it with something similar to this.
1 | class Animal(object): |
1 | giraffe = Animal('mrmmph', True) |
“Great” I said, though I didn’t mean it. “The thing is, they hired another programmer and he started today. He’s the zoo’s owner’s wife’s nephew. At the same time, the zoo acquired a meerkat and your boos told Jerry (that’s the kid’s name, Jerry) to add it to the system. Jerry went ahead and included the new meerkat in the system.”
1 | giraffe = Animal('mrmmph', True) |
“Candidate, what’s wrong with his code?”
“Well, a meerkat has a spine.”
“Yep, that’s right. How do we fix it?”
“That’s easy. Just make that ‘false’ a ‘true’.”
“I agree, that fixes the program. But, it does not fix the design. Jerry can still make the same mistake. We may miss it, the code could go into production! Then, some poor second grade girl will think that a meerkat and a crab have something more in common than starring in Disney movies.”
“Yeah, I see that….” Together, we pondered the board, he and I. Ostensibly, he thought about a better design. I thought about getting some falafel. Finally, he said, “Well, we can make two classes, then.” He set about writing.
1 | class Vertebrate(object): |
1 | class Invertebrate(object): |
1 | giraffe = Vertebrate('mrmmph') |
“Now, he can’t make a mistake.”
“Good,” I encouraged him. “Unfortunately, after a night of partying, Jerry comes in and finds a note that reads, ‘Jerry, we just bought a cat. Can you put it in the software for us?’ And, so, he does. Somewhat worse for the wear, he copies the rat implementation and pastes it at the end like this.”
1 | giraffe = Vertebrate('mrmmph') |
“Then, he packs up and goes home to take some aspirin.” The candidate looked a little put out, at this point. “I know that you wouldn’t abide such a co-worker. However, Jerry enjoys the security of nepitism. So, how can you fix this?”
(Here’s where it all fell apart for me.)
“Well, we could somehow track each sound that’s been used to create an animal. Then, we can … prevent other animals from getting created that way.”
“What happens when the zoo wants to buy another giraffe so that it can have a renewable supply of giraffes, a family of giraffes with little giraffe babies? We’ll need more than one giraffe in the system, then.”
“Right, but … oh. Ok, we can …” He went on to explain in ever-confusing
terms about how we could only assign instances of Vertebrate
that make the
‘mrmmph’ sound to variables named “giraffe.”
I stopped him. “What you’re proposing goes way beyond object-oriented design. Let’s just stand back for a moment. What sound do all rats make?”
“‘Squeak.’”
“Right. And, what sound do all cats make, assuming they don’t have laryngitis?”
“‘Meow.’”
“Right. So, doesn’t it make sense that we have classes that control that? That we have a ‘cat’ class that knows that it meows because it controls that piece of information? Remember, classes are about behavior.”
“Yeah, I can see that.”
“So, let’s do that. Does a cat have a spine?”
“Yes.”
“Ok. We can make a ‘cat’ class, then, that uses the ‘vertebrate’ class. Go ahead and write that on the board. And, while you’re at it, write a new ‘spider’ class, too.”
1 | class Cat(Vertebrate): |
1 | class Spider(Invertebrate): |
“Good! Now, modify the printing code that we had for these two animals.”
1 | cat = Cat() |
“Excellent! Now, let’s evaluate our code. First, can we understand it?”
“Yes.”
“I agree. Can Jerry mess it up through his inattention?”
“No.”
“Then, we’ve come up with something good, then. Our classes protect the data that they need to maintain their identity. In other words, each class protects its invariants. Well done, Interview Candidate.”
Our Final Code
1 | class Vertebrate(object): |
Of course, the interview wasn’t over. Because, we then talked about
refactoring static data