"What", you may ask, "is good design?" That's a big question, and a nuanced question. For the moment, what you need to buy into, to not just accept but feel in your bones, to print out and hang above your monitor back in Bancroft, are the following two tenets of program design:
|
😇 Separation of Interface from Implementation ... is the path to enlightenment! |
👿 Duplication of code ... is a mortal sin! |
In procedural programming, which is what we did in IC210, the language mechanism that supported separating interface from implementation was the function: a function's prototype (plus some documentation, if we're lucky) is the interface, its definition is the implementation. Scoping rules actually prohibit us from accessing the local variables and parameters inside the function's body, so this separation of interface from implementation isn't just conceptual — it's actually enforced by the compiler! However, the function mechanism on its own is by no means enough to achieve the nirvana of full separation of Interface from Implementation. Why?
You're limited to a single implementation for each interface (a single
function definition for each prototype), which is a real problem.
As a simple analogy, that would be like saying each kind of light-bulb
has to have a different socket, which would be ... difficult. Instead,
we live in a world in which the same socket works for
40watt or 60watt bulbs, for incandescent
bulbs or for fluorescent bulbs or LED bulbs or — more interestingly
— black-light bulbs. For many kinds of programming
problems we want the same thing: many different
implementations of the same interface.
Object-oriented programming is a programming paradigm — that means not merely a language but a whole philosophy on what a program is — with the goal of complete separation of interface from implementation. It's basic ideas are
To sum up: The Procedural Programming paradigm sees a program as functions calling functions, with data being passed around as arguments to or return-values from function calls. The Object-Oriented Programming paradigm sees a program as a bunch of data+function bundles (the objects) that communicate by calling each other's member functions.
| ↦ |
compile:
g++ -o hello helloworld.cpp |
↦ | ↦ |
execute:
$ ./hello |
Compilation creates the executable file hello
that contains machine code, which is the physical
computer's native language, and which it can run directly.
| ↦ |
compile:
javac HelloWorld.java |
↦ | ↦ |
execute:
$ java HelloWorld |
Compilation produces the file HelloWorld.class
that contains java bytecode, which the Java Virtual
Machine (i.e. the program java we run in
the final command) interprets. So binary code that
executes directly on the physical computer is never
produced. Instead, the program java, which
is itself an executable running on the physical computer,
executes the code.
If there was time in class, your instructor probably ran these programs while showing the "top" command.
What you will have noticed is that the C++ program eats up more and more memory until it crashes. However, the Java program does not. Why? Garbage collection!
Some programming languages support object-oriented programming more than others. C++ supports OOP well (it was designed to), as were other major languages, such as Java, C#, and Python.
In this course, we will be using Java. Why Java? Knowing multiple languages will help you better understand programming. Java is in many respects easier than C++, and it comes with many built-in features that we can explore. You should expect to learn or at least become familiar with 8-10 languages while you are an undergrad. Once you have learned the first 2-3, you will find it very easy to pick up new ones.
Why do we need more than one language? Different languages are good for solving different problems. Each has its strengths and weaknesses. The cost model of software development has driven the direction of language development for the last few decades:
High-level languages are fast to develop because they hide the messy and time-consuming bits of programming, like tracking registers and allocating memory. The trade-off is that these languages take more clock-cycles to run each command. Thanks to Moore's Law, we get faster machines each year, which makes high-level programming feasible. The graph below shows an artist's interpretation of the relative distribution of various languages between time to develop and time to run. (NOTE - this is just to give you a rough idea of the relative speeds, there is no data to back up the exact position of any language on the graph):

Video games are often written in multiple languages. The graphics code in a first-person shooter must render a million polygons onscreen, at 30 frames per second. That code needs to be extremely efficient. It is probably written in C and/or Assembler. The high-level code that controls a character's actions are often written in a high-level scripting language such as Lua. In general - code that runs many times per second needs to be efficient; code than runs once can take as many clock cycles as it needs.
There is one other reason why we see multiple programming languages: personal preference. The computer industry is full of highly-opinionated people who insist that their favorite language/OS/editor/browser/etc. is better than all the others. We call these arguments "religious wars". If you show the above graph to any software professional, it will almost certainly spawn an argument as to where each language should appear on the chart.
Java is a programing language developed by Jim Gosling and Bill Joy at Sun Microsystems in the 1990s. Sun wanted to be the leader in "intelligent" consumer devices. They wanted every bit of electronics you have to be controlled by downloadable programs that would do things like model your behavior and do things for you (e.g. to have your refrigerator tell you that you are out beer.) They began to design a programming language that could be used for these goals. It had to:
The smart appliance market did not work out as a business model for Sun. There were too many technical challenges and faulty assumptions about the market.
Then along came the world wide web. These same requirements were needed for the Internet. Java finally found its market.
How is it robust?
How is it portable? And safe?
Unlike C++, Java runs on a virtual machine called the JVM (Java Virtual Machine).
Remember, C++ is compiled and linked, which results in an executable file only appropriate for that computer's architecture. Java is compiled into bytecode, which is identical no matter which machine it is compiled on. This bytecode is then interpreted by the Java Virtual Machine and run. So, anybody with a JVM (like the one your computer is always telling you to update) can run the bytecode.
But what is a virtual machine? A virtual machine is software capable of running code that appears to that code to be a specific physical computing environment. The diagrams below illustrate how this works:

The diagram above shows two different operating systems represented as puzzle pieces. A program will only run if its lower edge fits perfectly into the OS piece. The shape of the edge represents the libraries that must be linked at compile-time. For example, the "stdio" library that handles basic print and keyboard functions is very different between Windows and Linux.

The diagram above show the same program (Firefox) compiled for the two different Operating Systems. This program is written in C++. The program seems the same to the user (each one serves web pages) but are very different under the hood. The version compiled on Windows will not run on linux, and vice-versa.

The diagram above shows how the Java Virtual Machine fits between the application and the Operating System. OpenOffice is written in Java. You can download one copy of this program that will run on either Windows or Linux. The Java Virtual Machine is a program written in C++. It must be compiled specifically for either Windows or Linux, the same as Firefox. Once it is running, however, it can run any Java program, regardless of the underlying OS. That makes Java code more "portable" than C/C++.
Life is full of tradeoffs... this portability makes Java code generally slower than C/C++.