Skip to content

Instantly share code, notes, and snippets.

@ashleylester
Created April 24, 2016 23:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ashleylester/5f0bc1fce57020b7b22f34d26ad2ccec to your computer and use it in GitHub Desktop.
Save ashleylester/5f0bc1fce57020b7b22f34d26ad2ccec to your computer and use it in GitHub Desktop.

Java Interface Notes

Interfaces are meeting points

Interfaces exist in many places and have different implications depending on their context. Interfaces exist between lots of different entities:

  • Organisations
  • Components in a system
  • Objects in an object-oriented application
  • Drivers and operating systems
  • Clients and servers

Objects have a private and public interface

It is obvious, but worth stating, that when a class declares private instance variables and private methods, this gives a class its private interface.

When a class declares methods as public, this defines its public interface. Therefore, access modifiers don't just define what we want to protect from others. Importantly, access modifiers define what methods we expect to be used elsewhere, and what methods we don't expect to be used elsewhere. The consequences are:

  • Public methods should not be changed without reason
  • Private methods might change on a whim
  • Public methods define what the class is and what it is for
  • Private methods do not count towards the public image of the class
  • Expect your public methods to be used elsewhere (outwardly-facing)
  • Private methods are not going to be used elsewhere (inwardly-facing)

Dependencies between objects have a direction

When a class references another object by composition, a relationship is created from the referencing class to the referenced class. The referencing class depends on the referenced class, and hopes that it will not change its implementation, otherwise it too will have to change.

This dependency is one-way. Often this dependency flows downhill. Objects depend on behaviour and make references to other objects that are finer-grained. For example: a canvas object depends on a triangle object by making reference to it. The canvas can "consist of" many triangles, and each triangle "is drawn on" a canvas, but makes no direct reference to it.

Unfortunately, this downhill flow of information has to be controlled, or an application becomes brittle and hard to change. This is because a finer-grained class has a higher chance of changing its implementation than a class that further up the chain of dependencies - and when a class changes, all of the dependent classes might have to change as well.

Interfaces help change the direction of dependencies

Interfaces help us to invert the direction of a dependency. this is called dependency inversion.

The concept is quite trivial to explain using a dynamic language like Ruby. In Ruby, and all dynamic languages, methods will try to run without caring what type of object they have been passed. As long as the method argument contains the behaviour they are looking for, they will deal with it. This a basic concept of duck typing.

Going back to our canvas and triangle, a first-cut way to draw some shapes would be to create dependencies from canvas to triangles, and invoke the triangles' draw methods. The dependency inversion way to do it would be different. We can define behaviour in such a way that the canvas does not know about what it has to draw.

class Canvas
  def drawObject(myObject)
    # The canvas draws the object onto the canvas
    myObject.draw
  end
end

class Triangle
  def draw
    # triangle drawing behaviour
  end
end

class Square
  def draw
    # square drawing behaviour
  end
end

class Picture
  def draw
    # picture drawing behaviour
  end
end

class Drawing
  def initialize
    canvas = Canvas.new
    canvas.drawObject(Triangle.new)
    canvas.drawObject(Square.new)
    canvas.drawObject(Picture.new)
  end
end

The Canvas class contains no references to any type of shapes, and does not need to know about them. A drawable object knows how it wants to be drawn but is not aware of what it will end up being drawn on. Because the canvas class does not depend on any of its drawable shapes, it does not crash and burn if the shapes were to change.

Change can come in many ways.

  • Changing the method name or argument list
  • Changing the type of arguments in the argument list
  • Changing the return type of the method
  • Changing what happens to the arguments in the body of the method

Interfaces in Java

If you do not already know what an interface is, you have not read the above sections, and maybe you should do so first.

I used the italicised drawable in the last section because in Java, interfaces are often given -able as a suffix. For example, Serializable, Comparable, and Iterable.

The reason why Java needs these interfaces is because it is statically typed. Statically typed languages check type safety at compile time - think of it like this: the compiler does all the unit tests for type safety on your behalf.

Therefore, the static equivalent of the Ruby code in the last section would be this:

public class Canvas {
  void drawObject(Drawable myObject) {
    myObject.draw();
  }
}

public class Triangle implements Drawable {
  public void draw() {
    // triangle drawing behaviour
  }
}

public class Square implements Drawable {
  public void draw() {
    // square drawing behaviour
  }
}

public class Picture implements Drawable {
  public void draw() {
    // picture drawing behaviour
  }
}

public interface Drawable {
  void draw();
}

public class Drawing {
  public Drawing() {
    Canvas canvas = new Canvas();
    canvas.drawObject(new Triangle());
    canvas.drawObject(new Square());
    canvas.drawObject(new Picture());
  }
}

Summary

An interface is a meeting point between objects or components in an application or system. We use interfaces in Java to help us write loosely-coupled code by reversing the direction of dependencies. This means that our classes are not tied down to implementations, and so our application becomes flexible and easy to change in the future.

If your objects have some common behaviour, and that behaviour feels poorly related to behaviour that defines their type, use an interface. Examples are schedulable, persistable, runnable, skinnable, drawable, and so on.

It is important to design software so that it is easy to change in the future. This involves using the correct building blocks so that it isn't a burden to gradually broaden our applications in size and scope. I have found this a helpful reminder of how I should be thinking when deciding on how an object relates to another object:

  • If the relationship is is-a, consider using an inheritance relationship
  • If the relationship is has-a, consider using a composition relationship
  • If the relationship is has the behaviour of a, consider using an interface
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment