Beautiful sunny day in the Twin Cities of Minneapolis and St. Paul, not that matters because I am working indoors in my home office, and it does not have an outside window.
Last week I was talking with a software engineers and the topic of interfaces came up. An interface is a contract stating that we define a list of methods in the interface and any class that uses the interface must define and implement the methods listed in the interface definition at a minimum. Additional methods may be included if necessary.
During my professional career I have been interfacing with hardware and / or have worked on the software for custom hardware. I am quite familiar with the concepts and usage of interfaces in different programming languages. In addition, I have defined and implemented interfaces in the C language for many different products. As mentioned above, an interface is a contract between a provider and an implementer / vendor.
As an example, for most (if not all) the products we developed at a software company which I owned and acted as the CTO, the distribution library encapsulated thousands of functions that our products, which were developed by different programmers, would call. The contract was to use and if not found, request functionality that could then be used by other products (e.g., queue, maps, directories, SNMP, SMTP, etc.)
You can read about interfaces in the Java documentation and in Wikipedia.
OK, that is enough of a preamble. Let’s define a test program for a cutter. The cutter would be something similar to a pen plotter in which the pen carrier has been replaced a device that holds a stylus. The stylus has a sharp edge which can cut material. In other implementations, the stylus may be replaced by a laser capable to cut harder and thicker materials.
Let’s take a look at a Java program that I wrote using the Eclipse IDE.
package com.canessa.cutter; /* * */ public class Solution { /* * */ public static void main(String[] args) throws InterruptedException { // **** cannot instantiate the type Cutter (it is an interface) **** // Cutter cutter = new Cutter(); // **** instantiate the JCCutter (this is an implementation) **** JCCutter jcCutter = new JCCutter(); // **** get and display about info from the cutter **** System.out.println("about: " + jcCutter.about()); // **** initialize the cutter **** jcCutter.init(); // **** load media **** jcCutter.load(); // **** get current position **** System.out.println("cutter position: " + jcCutter.getPosition().toString()); // **** stylus down **** jcCutter.stylusDown(); // **** cut square [0,0] with sides of 100 units **** jcCutter.rectangle(new Point(100, 0), new Point(100, 100), new Point(0, 100)); // **** stylus up **** jcCutter.stylusUp(); // **** stylus down **** jcCutter.stylusDown(); // **** cut circle inside square **** jcCutter.circle(new Point(50, 50), 100); // **** stylus up **** jcCutter.stylusUp(); // **** eject media **** jcCutter.eject(); } }
The console output for a pass of the software follows:
about: Cutter designed and implemented by John Canessa Industries initializing ... initialized!!! loading media ... media loaded !!! cutter position: [0,0] stylus down from [0,0] -> [0,100] ... from [0,100] -> [100,100] ... from [100,100] -> [100,0] ... from [100,0] -> [0,0] ... done stylus up stylus down cutting circle at [50,50] with radius: 100 ... done !!! stylus up ejecting media ... media ejected !!!
The first line which is commented out makes a direct call to a Cutter class. I commented out because it would generate an error. The Cutter class is defined as an interface. An interface cannot be instantiated. In our example, the interface follows:
package com.canessa.cutter; /* * */ public interface Cutter { public void init(); public void home(); public void load(); public void eject(); public void stylusUp(); public void stylusDown(); public void setPosition(Point pt); public Point getPosition(); public void line(Point to); public void circle(Point center, int radius); public void rectangle(Point pt1, Point pt2, Point pt3); }
The Cutter interface specifies a set of methods that a vendor / provider of the cutter should implement at a minimum. In our case, we have very few calls to initialize and home the device. We can load and eject material from the cutter. We can lift or drop the stylus on the material. This holds true if the stylus has one or two sharp edges used to cut the material. If our cutter is implemented with a laser, these calls would turn off and turn on respectively the laser. We could also have a cutter with some type of spinning abrasive device (like the ones used to polish metals) which would cut some materials. Or perhaps we have a single cutter which can be fitted with different types of cutting devices based on the material we wish to cut. Note that the Interface would not change and probably our program would remain the same.
I will show the output for the Solution when the initialize cutter call has been disabled. In general, when using hardware, you must first initialize it to a known state before sending other commands. This avoids the system to exhibit strange behaviors or produce unexpected results.
We will now comment out the initialization and observe what happens with the program:
about: Cutter designed and implemented by John Canessa Industries initializing ... initialized!!! loading media ... media loaded !!! cutter position: [0,0] stylus down from [0,0] -> [0,100] ... from [0,100] -> [100,100] ... from [100,100] -> [100,0] ... from [100,0] -> [0,0] ... done stylus up stylus down cutting circle at [50,50] with radius: 100 ... done !!! stylus up ejecting media ... media ejected !!!
It seems that the program behaved correctly without the cutter being explicitly initialized. The reason for this will became obvious when we look at the implementation code of the Cutter interface in the JCCutter class.
package com.canessa.cutter; /* * Class implemented by the cutter manufacturer JC */ public class JCCutter implements Cutter { // **** **** private boolean initialized = false; private Point current = null; private boolean stylusUp = true; // **** **** public void init() { System.out.println("initializing ..."); try { Thread.sleep(1000 * 3); } catch (InterruptedException e) { e.printStackTrace(); } this.stylusUp = true; this.current = new Point(0, 0); this.initialized = true; System.out.println("initialized!!!"); } // **** **** public void home() { if (!initialized) init(); } // **** **** public void load() { if (!initialized) init(); // **** load media **** System.out.println("loading media ..."); try { Thread.sleep(1000 * 4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("media loaded !!!"); } // **** **** public void eject() { if (!initialized) init(); // **** check if the stylus needs to be moved up **** if (this.stylusUp == false) stylusUp(); // **** eject media **** System.out.println("ejecting media ..."); try { Thread.sleep(1000 * 4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("media ejected !!!"); } // **** **** public void stylusUp() { if (!initialized) init(); this.stylusUp = true; System.out.println("stylus up"); } // **** **** public void stylusDown() { if (!initialized) init(); this.stylusUp = false; System.out.println("stylus down"); } // **** **** public void setPosition(Point pt) { if (!initialized) init(); // **** get to the desired position **** System.out.println("moving to [" + pt.getX() + "," + pt.getY() + "] ..."); try { Thread.sleep(1000 * 1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("moving to [" + pt.getX() + "," + pt.getY() + "] !!!"); // **** update current position **** this.current = pt; } // **** **** public Point getPosition() { if (!initialized) init(); // **** get current position **** return this.current; } // **** **** public void line(Point to) { if (!initialized) init(); // **** draw the line **** } // **** **** public void circle(Point center, int radius) { if (!initialized) init(); // **** cut circle (stylus may be up) **** System.out.println("cutting circle at [" + center.getX() + "," + center.getY() + "] with radius: " + radius + " ..."); try { Thread.sleep(1000 * 3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("done !!!"); } // **** **** public void rectangle(Point pt1, Point pt2, Point pt3) { if (!initialized) init(); // **** save starting position to complete shape **** Point start = new Point(current.getX(), current.getY()); // **** cut rectangle (stylus may or may not be down) **** System.out.println("from [" + this.current.getX() + "," + this.current.getY() + "] -> [" + pt1.getX() + "," + pt1.getY() + "] ..."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } this.current = pt1; System.out.println("from [" + this.current.getX() + "," + this.current.getY() + "] -> [" + pt2.getX() + "," + pt2.getY() + "] ..."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } this.current = pt2; System.out.println("from [" + this.current.getX() + "," + this.current.getY() + "] -> [" + pt3.getX() + "," + pt3.getY() + "] ..."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } this.current = pt3; System.out.println("from [" + this.current.getX() + "," + this.current.getY() + "] -> [" + start.getX() + "," + start.getY() + "] ..."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("done"); this.current = start; } // **** returns an about string for the cutter **** public String about() { return "Cutter designed and implemented by John Canessa Industries"; } }
The code illustrates that the JCCutter class implements the Cutter interface. The JCCutter class defines some members that it requires for its operation. It then goes on implementing each of the methods called in the Cutter interface. Please note that I did not completely implemented the line() method. In an actual implementation that and many other methods (e.g., spline, ellipse, drawLetter, drawText, etc.) would have been defined in the interface and implemented by the provider / vendor of the cutter.
The JCCutter class also implemented an about() method that was not included in the interface. We called it to get some information about our cutter.
Since I had to run the software I also implemented a simple class for Point. The class follows:
package com.canessa.cutter; /* * */ public class Point { // **** members **** private int x; private int y; // **** constructor **** public Point() { } // **** constructor **** public Point(int x, int y) { this.x = x; this.y = y; } // **** get x coordinate **** public int getX() { return this.y; } // **** get y coordinate **** public int getY() { return this.x; } // **** set x coordinate **** public void setX(int x) { this.x = x; } // **** set y coordinate **** public void setY(int y) { this.y = y; } // **** to string *** public String toString() { return "[" + this.x + "," + this.y + "]"; } }
In actuality, while I was working for a fortune 500 company, I architected and implemented a cutter that was able to use a stylus to cut a film that could be attached to most flat surfaces. The software supported a few fonts which we implemented using a CAS system. We could have gone with COTS fonts but they demanded a royalty for each font. The fonts were implemented using splines so they could generate any size characters.
The cutter implemented a stylus. During design and implementation we ran into an issue. If the direction of cut was aligned with the edge of the stylus, all went well. As the direction of motion differed from the cutting edge, the stylus would plow into the material instead of cutting it. It just would not work. To address such behavior, we mounted a servo motor which would rotate the stylus and align it with the tangent of the direction of motion. We could cut also cut logos with the system.
At the time of the project, my wife and I had purchased a boat which we use to take to the St. Croix River which separates Minnesota and Wisconsin. I made and applied the name of the boat. We named the boat “Megabyte”.
In conclusion, interfaces are very useful to establish a contract between users and providers of hardware or services.
If you are interested in this code, you can find all the files in my GitHub repository.
Hope you enjoyed this post. Always keep in mind that learning requires reading and experimentation. I have been using interfaces for a very long time, and when I read and experiment with them, it seems that I always learn something new.
If you have a comment or questions regarding this or any other post in this blog, or if you would like for me to help is a software development product / service for internal or external use, please leave me a note bellow. Such request will not be displayed in the blog.
Keep on reading and experimenting. The more you learn the more you will be able to come up with simple and elegant software implementations.
John
Follow me on Twitter: @john_canessa