Programming Basics – The Shape Example

Somebody I am working with is learning programming. Today we looked at interfaces and classes in Java. An example I like to use is the Shape Example. The Shape Example is quite nice and easy and has the advantage that it can be extended to cover basics such as this as well as more advanced topics such as Design Patterns.

The Shape

Let’s start with the idea of a Shape. I assume a two dimensional shape such as a square or a circle. Shape is an abstract concept. I could ask everyone in the office to draw a Shape and expect to receive a wide array of interesting drawings ranging from simple polygons to complex squiggles. All of the shapes have some things in common. We define Shape in this system as a Java Interface. Interfaces are abstract.

/**
 * Things that all Shapes have in common.
 */
public interface Shape {
	/**
	 * @return The area of the shape in square units.
	 */
	public double getArea();

	/** 
	 * @return the shape's centroid.
	 */
	public Point getCentroid();

	/** 
	 * @return a name to describe this shape.
	 */
	public String getName();
}

Point

Point is a simple class that describes a position in our two dimensional coordinate system. The design of Point follows a common pattern for simple data storage classes. It is immutable. Once a Point is created it is not possible to change any of its values. This is very useful and will be covered more later.

public class Point {

	private final double m_x, m_y;
		
	public Point(double x, double y){
		m_x = x;
		m_y = y;
	}
	
	public double getX()
	{
		return m_x;
	}
	
	public double getY()
	{
		return m_y;
	}
}

Point provides an implementation of toString so that I can print points out easily.

	@Override
	public String toString() {
		return "{" + m_x + "," + m_y + "}";
	}

A utility method lets me calculate the distance between two points.

	/**
	 * Return the distance between two points.
	 */
	public static double getDistance(Point p1, Point p2)
	{
		double a = p2.m_x - p1.m_x;
		double b = p2.m_y - p1.m_y;
		return Math.sqrt(a*a + b*b);
	}

Circle and Square

Now we have the basic Shape interface and Point class, we can define a Circle. A Circle is a concrete concept. If I ask everyone in the office to draw a 10cm circle I should receive a lot of very similar looking drawings. Circle is also immutable. It’s a good habit to get into for simple data classes.

public class Circle implements Shape {

	private final Point m_centre;
	private final double m_radius;
	
	public Circle(double x, double y, double radius)
	{
		this(new Point(x,y), radius);
	}
	
	public Circle(Point centre, double radius)
	{
		m_centre = centre;
		m_radius = radius;
	}
	
	@Override
	public double getArea() {
		return Math.PI * m_radius * m_radius;
	}

	@Override
	public Point getCentroid() {
		return m_centre;
	}

	@Override
	public String getName()
	{
		return "Circle";
	}
}

Square is defined by two opposite corners. This makes the maths slightly harder, but allows the square to be at any angle. A private method is used to help in the calculations. Methods such as this should be kept private until there is reason to make them otherwise. As the system grows this allows such methods to be changed easily without worrying that they may be used elsewhere.

public class Square implements Shape {
	private final Point m_p1, m_p2;

	public Square(Point p1, Point p2)
	{
		m_p1 = p1;
		m_p2 = p2;
	}
	
	@Override
	public double getArea() {
		double side = getSideLength();
		return side * side;
	}

	private double getSideLength()
	{
		// The side is 1/sqrt(2) of distance from a corner to opposite corner.
		double diagonal = Point.getDistance(m_p1, m_p2);
		return diagonal / Math.sqrt(2.0);
	}
	
	@Override
	public Point getCentroid() {
		return new Point(
				(m_p1.getX() + m_p2.getX()) / 2, 
				(m_p1.getY() + m_p2.getY()) / 2
		);
	}

	@Override
	public String toString() {
		return "Square with corners " + m_p1 + "," + m_p2;
	}
	
	@Override
	public String getName()
	{
		return "Square";
	}
}

Aside – What if Point was Mutable

Consider the implementation of getCentroid() in the Circle class. This returns the stored Point. If Point was mutable consider the following code:

Circle myCircle = new Circle(0,0,50);
Point centre = myCircle.getCentroid();
centre.setX(100);

The circle has now been changed. It is no longer at 0,0 but at 100,0. This is not harmful in such a simple program, but in a more complex program changing the internal knowledge of the Circle behind its back could be harmful.

Immutable pattern is not the only solution. The Circle could have made a copy of its centre when asked, so if the copy is changed no harm is done. Alternatively we may wish to allow programmers to move shapes by altering their centroids. We’d need to implement an Observer Pattern to allow the Shape to react to the change. It would be an over-complex solution.

Using Shapes

My colleague’s homework was to demonstrate the use of an interface. The important point in the homework was to realise that an interface cannot be instantiated. If I ask you do draw a Shape then you’d not know what to draw. I’d need to be specific

Shape myShape = new Circle(100, 100, 10);

Interfaces, or abstractions in general, allow us to write methods in terms of more abstract concepts and use them with the whole range of more concrete concepts. For example we could write a method to print out some information about a shape:

	private static void printInfo(Shape theShape) {
		String name = theShape.getName();
		double area = theShape.getArea();
		Point centroid = theShape.getCentroid();
		System.out.println("Shape " + name + " area " + area + " centroid " + centroid);
	}

We could construct an array of Shapes and print information about them.

	public static void main(String[] args) {
		Shape[] someShapes = new Shape[]{
				new Circle(25,30,100),
				new Square(new Point(-12,6), new Point(18,90))
		};

		for(Shape s : someShapes){
			printInfo(s);
		}
	}

The result:

Shape Circle area 31415.926535897932 centroid {25.0,30.0}
Shape Square area 3977.999999999999 centroid {3.0,48.0}

We could calculate the centre of mass based on the assumption of uniform density:

	private static Point findMassCentre(Shape[] shapes)
	{
		double sumX = 0;
		double sumY = 0;
		double totalMass = 0;
		
		for(Shape s : shapes){
			Point centre = s.getCentroid();
			double mass = s.getArea();
			sumX += centre.getX() * mass;
			sumY += centre.getY() * mass;
			totalMass += mass;
		}

		return new Point(
			sumX / totalMass,
			sumY / totalMass
		);
	}

The point of using the interface is that these methods do not need to know anything about the kind of shape, just that all shapes have a centroid and an area.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.