Frameworks consist of a number of classes working together, containing some reusable logic, be it presentation-, business- or persistence-logic. In this Blog I will present an example framework that builds homes of any kind, in other words, the framework's logic is "building houses".
As you may guess, the classes working together here will be walls, roofs, doors and so on.
To be able to build homes of any type, any new
operator in the framework
must be encapsulated into a protected
factory method.
Only that way the framework can be customized and reused entirely.
(Exception is the allocation of standard data types like Integer, String, Boolean
etc.)
How to Build a Home
The encapsulated home-building business logic is
- creating 4 walls
- erecting them
- making a door into one of them
- covering them with a roof.
Homes can be houses, tents, wooden huts, ... all of them will be built the same way.
So here is that logic in an abstract
class.
To make it short, I left out the imports
and JavaDoc
(mind that you always should write documentation for public
and protected
classes, methods and fields!).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public abstract class Home { public final void build() { System.out.println("==== Starting to build "+this+" ===="); final List<Wall> walls = new ArrayList<>(); for (int i = 1; i <= 4; i++) walls.add(newWall(i)); erectWalls(walls); walls.get(0).setDoor(); final Roof roof = newRoof(); coverWithRoof(walls, roof); System.out.println("==== Finished building "+this+" ===="); } protected abstract Wall newWall(int number); protected abstract Roof newRoof(); private void erectWalls(List<Wall> walls) { for (Wall wall : walls) System.out.println("Erecting wall "+wall); } private void coverWithRoof(List<Wall> walls, Roof roof) { System.out.println("Covering with "+roof+": "+walls); } @Override public String toString() { return getClass().getSimpleName(); } } |
These are the factory methods, creating walls and roof:
protected abstract Wall newWall(int number); protected abstract Roof newRoof();
Here comes an abstract wall for that home.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public abstract class Wall { public interface Door { } protected final int number; protected Wall(int number) { this.number = number; } protected void setDoor() { final Door door = newDoor(); System.out.println("Building "+door+" into "+this); } protected abstract Door newDoor(); @Override public String toString() { return getClass().getSimpleName()+" "+number; } } |
Any wall could have a door, modelled as inner interface.
Again a factory method is responsible for creating it, in case setDoor()
gets called:
protected abstract Door newDoor();
Here comes the roof, as interface, so that walls also could implement roofs, useful for tents.
1 2 3 | public interface Roof { } |
We could set a material
property into this, to show the roof's reliability.
Frameworking a House
Until now we just got abstract classes and interfaces. Not a very good shelter, time to get concrete :-)
1 2 3 4 5 6 7 8 9 10 11 12 | public class House extends Home { @Override protected Wall newWall(int number) { return new BrickWall(number); } @Override protected Roof newRoof() { return new TiledRoof(); } } |
Just the factory methods are implemented, and the according classes. Very short and concise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class BrickWall extends Wall { private static class WoodenDoor implements Door { @Override public String toString() { return getClass().getSimpleName(); } } public BrickWall(int number) { super(number); } @Override protected Door newDoor() { return new WoodenDoor(); } } |
This wall is made of bricks and got a wooden door. The following roof is covered with tiles.
1 2 3 4 5 6 7 | public class TiledRoof implements Roof { @Override public String toString() { return getClass().getSimpleName(); } } |
Test code:
1 2 3 4 5 6 | public class Demo { public static void main(String[] args) { new House().build(); } } |
Output is:
==== Starting to build House ==== Erecting wall BrickWall 1 Erecting wall BrickWall 2 Erecting wall BrickWall 3 Erecting wall BrickWall 4 Building WoodenDoor into BrickWall 1 Covering with TiledRoof: [BrickWall 1, BrickWall 2, BrickWall 3, BrickWall 4] ==== Finished building House ====
With this house we are ready for the rainy season :-)
Frameworking a Tent
In summer we prefer to enjoy fresh air by living in a tent.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class Tent extends Home { @Override protected Wall newWall(int number) { return new TextileWall(number); } @Override protected Roof newRoof() { return new TextileWall(0); } } |
A wall is used also as roof of the tent.
There is no separate door, it is cut into the wall, thus the setDoor()
override.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class TextileWall extends Wall implements Roof { public TextileWall(int number) { super(number); } @Override protected void setDoor() { System.out.println("Cutting a zipper door into "+this); } @Override protected Door newDoor() { return null; } } |
Test code:
1 2 3 4 5 6 | public class Demo { public static void main(String[] args) { new Tent().build(); } } |
This outputs:
==== Starting to build Tent ==== Erecting wall TextileWall 1 Erecting wall TextileWall 2 Erecting wall TextileWall 3 Erecting wall TextileWall 4 Cutting a zipper door into TextileWall 1 Covering with TextileWall 0: [TextileWall 1, TextileWall 2, TextileWall 3, TextileWall 4] ==== Finished building Tent ====
Frameworking a Phantasy Home
What can be done with named classes can also be done using anonymous classes. Following example shows the real power of overriding.
Thanks to factory-methods you can customize the framework down to any level, here down to the wall's door. Mind how overrides nest into other overrides. Method overrides contain anonymous class overrides, that again contain method overrides.
Might be a little hard to read, but this is the framework style. Summit of OO in my opinion, because you can reuse not just one class but a whole set of them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class Demo { public static void main(String[] args) { new Home() { @Override protected Roof newRoof() { return new Roof() { public String toString() { return "Phantasy Roof"; } }; } @Override protected Wall newWall(int number) { return new Wall(number) { @Override protected Door newDoor() { return new Door() { public String toString() { return "Phantasy Door"; } }; } public String toString() { return "Phantasy Wall "+number; } }; } public String toString() { return "Phantasy Home"; } }.build(); } } |
Output is:
==== Starting to build Phantasy Home ==== Erecting wall Phantasy Wall 1 Erecting wall Phantasy Wall 2 Erecting wall Phantasy Wall 3 Erecting wall Phantasy Wall 4 Building Phantasy Door into Phantasy Wall 1 Covering with Phantasy Roof: [Phantasy Wall 1, Phantasy Wall 2, Phantasy Wall 3, Phantasy Wall 4] ==== Finished building Phantasy Home ====
So, isn't frameworking an alternative to networking :-?
You just need to state what you want, the framework will call you and make something of whatever you return.