One of the reoccurring topics that will be covered on everythingdev
will be design patterns. Having a solid understanding of design pattern
methodologies will not only lead to becoming more proficient in the OOP
paradigm, it can also just make your life much easier when implementing certain
modules within a software project.
Right now we’ll be covering the Factory Design Pattern.
Let’s say
we’re writing a paint program which allows the user to draw some shapes. Right
now we can draw rectangles and circles. We’ll create a ‘Shape’ class that can handle rectangles and circles:
public class Shape {
private int width = 0;
private int height = 0;
private double radius = 0.0D;
private Boolean isRectangle = false;
private Point[] hexagonPoints = null;
public Shape(int width, int height) {
this.isRectangle = true;
this.width = width;
this.height = height;
}
public Shape(double radius) {
this.radius = radius;
}
public void draw() {
private int height = 0;
private double radius = 0.0D;
private Boolean isRectangle = false;
private Point[] hexagonPoints = null;
public Shape(int width, int height) {
this.isRectangle = true;
this.width = width;
this.height = height;
}
public Shape(double radius) {
this.radius = radius;
}
public void draw() {
if(isRectangle)
//
handle drawing the rectangle on screen...
} else {
//
otherwise it’s a circle, and handle drawing the circle on screen….
}
}
}
}
}
The shape
class has two constructors: one that accepts width and height parameters,
and another which accepts a radius parameter. If the first constructor is called,
the object will represent a rectangle, if the second is called it will be a
circle. Easy enough.
Soon our
software has become a huge hit and we receive a request to add support for another
shape type: hexagons. Hexagons will have width and height properties like
rectangles. We decide to recycle an existing constructor we have for
rectangles, and simply add a Boolean ‘isHexagon’ parameter to distinguish
between a rectangle or hexagon, and update our draw() method accordingly.
Now our
shape class looks like this:
public class Shape {
private int width = 0;
private int height = 0;
private double radius = 0.0D;
private Boolean isRectangle = false;
private Boolean isHexagon = false;
private Point[] hexagonPoints = null;
public Shape(int wfaceidth, int height, Boolean isHexagon) {
if(isHexagon) {
this.isHexagon = true;
}
else {
this.isRectangle = true;
}
this.width = width;
this.height = height;
}
public Shape(double radius) {
this.radius = radius;
}
public void draw() {
private int height = 0;
private double radius = 0.0D;
private Boolean isRectangle = false;
private Boolean isHexagon = false;
private Point[] hexagonPoints = null;
public Shape(int wfaceidth, int height, Boolean isHexagon) {
if(isHexagon) {
this.isHexagon = true;
}
else {
this.isRectangle = true;
}
this.width = width;
this.height = height;
}
public Shape(double radius) {
this.radius = radius;
}
public void draw() {
if(isRectangle)
//
handle drawing the rectangle on screen...
}
else if(isHexagon) {
// draw hexagon
}
else {
else if(isHexagon) {
// draw hexagon
}
else {
//
otherwise it’s a circle, and handle drawing the circle on screen….
}
}
}
}
}
Couple of
emerging problems with our Shape class:
- It’s rapidly becoming messier (imagine what it’s going to look like as we add support for more and more distinct shapes).
- The more functionality we have, the harder debugging specific shape functionality will get.
- The onus is on the developer to juggle all the constructors and initialization parameters in their head to remember how to build each shape.
Here’s
another issue: when we changed the first constructor to accommodate hexagons, it
forced us to update references to that constructor. So if we have 17 different
classes within our codebase that initializes
a shape using that constructor, that’s 17 classes that need to be updated along
with the change to our Shape class. In other words, those 17 classes are overly
reliant on being aware of the underlying implementation details of the shape
class. We can also say that these classes are tightly coupled. We’ll solve
these problems by implementing a factory class to handle creation of the shape
objects instead.
We’ll start
by creating a ‘Shape’ interface containing some properties and methods that are
common to all Shapes regardless of their specific type:
public interface Shape {
public void draw();
public void getShapeType();
public void setColor();
public void getShapeType();
public void setColor();
}
And we’ll refractor the Shape classes so they all implement
our Shape interface:
class Rectangle implements Shape
public void draw() {…….}
public void getShapeType(){…….}
public void setColor(){…….}
public void getShapeType(){…….}
public void setColor(){…….}
}
class Circle implements Shape
public void draw() {…….}
public void getShapeType(){…….}
public void setColor(){…….}
public void draw() {…….}
public void getShapeType(){…….}
public void setColor(){…….}
}
class Hexagon implements Shape
public void draw() {…….}
public void getShapeType(){…….}
public void setColor(){…….}
public void getShapeType(){…….}
public void setColor(){…….}
}
Then we’ll build a Factory class that will handle the creation of the various shapes:
public class ShapeFactory {
final int TYPE_CIRCLE = 0;
final int TYPE_OVAL = 2;
final int TYPE_RECTANGLE = 3;
final int TYPE_HEXAGON = 4;
public Shape buildShape(int
shapeType) {
switch(shapeType):
case TYPE_CIRCLE: return new Circle(double
radius)
// do stuff to make circles…
// do stuff to make circles…
case TYPE_RECTANGLE: return new Rectangle(int
width, int height)
// do stuff to make rectangles…
// do stuff to make rectangles…
case TYPE_HEXAGON: return new Hexagon(int
width, int height)
// do stuff to make hexagons…
// do stuff to make hexagons…
default: return null;
}
}
}
return null;
}
Our Factory allows the developer (as well as other
components within the system) to take a more hands-off approach to requesting
certain shapes. Instead of getting caught up in the intricate under the
hood details of a class (“It’s a
rectangle not a hexagon… so I need to pass this parameter instead…”, or "Which constructor do I use for hexagons again?", etc) we
simply make a request to our factory, “We want X type of shape”, pass the
relevant information, and get back that type of shape. Then our other
components can draw it, drag it, resize it, and do whatever else our program
allows the user to do with shapes
The design
of a Factory class could get much fancier. Having an enumerator that lists the
shape types rather than those hardcoded integers is a good idea, for instance. Even
in its current state though, our Factory class has already pushed us in the
right direction to having a much cleaner design and loosely coupled system
involving classes and objects that aren’t so heavily reliant on knowing each
other’s implementation details.