Common Design Patterns in Java: Creational Patterns
Ahoy, mateys! Welcome aboard the SS Java, where we’re about to embark on a thrilling adventure through the treacherous waters of common design patterns in Java. Today, we’ll focus on creational patterns, which be the treasure maps that help us navigate the creation of objects in our code. So grab your trusty cutlass and let’s set sail!
Creational Patterns
In the world of software design, creational patterns be like the compass guiding us through the vast ocean of object creation. They provide a way to create objects while hiding the details of their instantiation, allowing us to sail smoothly through the seas of our code.
There be five main creational patterns in the pirate’s chest:
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
Singleton Pattern
The Singleton pattern be like a one-eyed pirate: there’s only one of ‘em, and he’s always the same no matter where ye find him. In other words, the Singleton pattern ensures that a class has only one instance and provides a global point of access to it.
Here’s an example of a Singleton in Java:
public class Captain {
private static Captain uniqueInstance;
private Captain() {
}
public static synchronized Captain getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Captain();
}
return uniqueInstance;
}
public void sail() {
System.out.println("The captain is steering the ship!");
}
}
Now, wherever ye need the captain in your code, just call Captain.getInstance()
to summon him.
Factory Method Pattern
The Factory Method pattern be like a skilled shipwright, providing a way to create objects without specifying the exact class of the object that will be created. It allows a class to delegate the instantiation to subclasses.
Here’s an example of the Factory Method pattern in Java:
public abstract class Ship {
public abstract void sail();
}
public class PirateShip extends Ship {
@Override
public void sail() {
System.out.println("The pirate ship is sailing!");
}
}
public class GhostShip extends Ship {
@Override
public void sail() {
System.out.println("The ghost ship is sailing!");
}
}
public abstract class ShipFactory {
public abstract Ship createShip();
}
public class PirateShipFactory extends ShipFactory {
@Override
public Ship createShip() {
return new PirateShip();
}
}
public class GhostShipFactory extends ShipFactory {
@Override
public Ship createShip() {
return new GhostShip();
}
}
Now ye can create different types of ships without knowing the exact class beforehand!
Abstract Factory Pattern
The Abstract Factory pattern be like a collection of shipwrights, each specializing in building a different type of ship. It provides an interface for creating families of related or dependent objects without specifying their concrete classes.
Here’s an example of the Abstract Factory pattern in Java:
interface Ship {
void sail();
}
class PirateShip implements Ship {
@Override
public void sail() {
System.out.println("The pirate ship is sailing!");
}
}
class GhostShip implements Ship {
@Override
public void sail() {
System.out.println("The ghost ship is sailing!");
}
}
interface ShipFactory {
Ship createShip();
}
class PirateShipFactory implements ShipFactory {
@Override
public Ship createShip() {
return new PirateShip();
}
}
class GhostShipFactory implements ShipFactory {
@Override
public Ship createShip() {
return new GhostShip();
}
}
Nowye can create various types of ships using the corresponding factory, making it easy to extend the code with new ship types in the future.
Builder Pattern
The Builder pattern be like a master shipwright who knows how to construct different parts of a ship separately, and then assemble them into a complete vessel. It separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
Here’s an example of the Builder pattern in Java:
class Ship {
private String hull;
private String mast;
private String sail;
public void setHull(String hull) {
this.hull = hull;
}
public void setMast(String mast) {
this.mast = mast;
}
public void setSail(String sail) {
this.sail = sail;
}
public void sail() {
System.out.println("The ship with " + hull + " hull, " + mast + " mast, and " + sail + " sail is sailing!");
}
}
interface ShipBuilder {
void buildHull();
void buildMast();
void buildSail();
Ship getShip();
}
class PirateShipBuilder implements ShipBuilder {
private Ship ship;
public PirateShipBuilder() {
ship = new Ship();
}
@Override
public void buildHull() {
ship.setHull("wooden");
}
@Override
public void buildMast() {
ship.setMast("tall");
}
@Override
public void buildSail() {
ship.setSail("black");
}
@Override
public Ship getShip() {
return ship;
}
}
class GhostShipBuilder implements ShipBuilder {
private Ship ship;
public GhostShipBuilder() {
ship = new Ship();
}
@Override
public void buildHull() {
ship.setHull("ethereal");
}
@Override
public void buildMast() {
ship.setMast("invisible");
}
@Override
public void buildSail() {
ship.setSail("ghostly");
}
@Override
public Ship getShip() {
return ship;
}
}
class ShipDirector {
public Ship constructShip(ShipBuilder shipBuilder) {
shipBuilder.buildHull();
shipBuilder.buildMast();
shipBuilder.buildSail();
return shipBuilder.getShip();
}
}
Now ye can create different ships by using the ShipDirector
and various ShipBuilder
implementations, making it easy to create new ship types by just creating new builders.
Prototype Pattern
The Prototype pattern be like a magical cloning machine that can create exact copies of any ship you put into it. This pattern involves creating new objects by copying an existing object, known as the prototype.
Here’s an example of the Prototype pattern in Java:
abstract class Ship implements Cloneable {
public abstract void sail();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class PirateShip extends Ship {
@Override
public void sail() {
System.out.println("The pirate ship is sailing!");
}
}
class GhostShip extends Ship {
@Override
public void sail() {
System.out.println("The ghost ship is sailing!");
}
}
Now, if ye need a new ship identical to an existing one, simply clone the existing ship:
PirateShip pirateShip = new PirateShip();
GhostShip ghostShip = new GhostShip();
// Clone the pirate ship
PirateShip clonedPirateShip = (PirateShip)pirateShip.clone();
// Clone the ghost ship
GhostShip clonedGhostShip = (GhostShip) ghostShip.clone();
// Test cloned ships
clonedPirateShip.sail();
clonedGhostShip.sail();
With the Prototype pattern, ye can clone the existing ships without having to create new instances through constructors, making it more efficient when creating many identical instances.
Singleton Pattern
The Singleton pattern be like a legendary ship that only one can exist in the entire world. It ensures that a class has only one instance and provides a global point of access to it.
Here’s an example of the Singleton pattern in Java:
class TheBlackPearl {
private static TheBlackPearl instance;
private TheBlackPearl() {
// Private constructor to prevent creating more than one instance
}
public static TheBlackPearl getInstance() {
if (instance == null) {
instance = new TheBlackPearl();
}
return instance;
}
public void sail() {
System.out.println("The Black Pearl is sailing!");
}
}
Now, ye can only have one instance of TheBlackPearl
:
TheBlackPearl blackPearl1 = TheBlackPearl.getInstance();
TheBlackPearl blackPearl2 = TheBlackPearl.getInstance();
blackPearl1.sail();
blackPearl2.sail();
System.out.println(blackPearl1 == blackPearl2); // true
As ye can see, blackPearl1
and blackPearl2
be referring to the same instance, ensuring there be only one TheBlackPearl
in the entire world.
In conclusion, me hearties, creational design patterns be an essential part of any pirate’s toolbox when it comes to plundering the seas of Java. By mastering these patterns, ye’ll be well on your way to becoming a legendary pirate programmer!
Structural Patterns
As we continue our voyage through the waters of design patterns, let’s turn our spyglass towards structural patterns. These patterns be like the ship’s hull, focusing on the composition of classes and objects to form larger structures.
There be seven main structural patterns ye should be familiar with:
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
Adapter Pattern
The Adapter pattern be like a trusty parrot translator, allowing two incompatible interfaces to communicate with each other. It converts the interface of a class into another interface that clients expect, enabling classes to work together that otherwise couldn’t.
Here’s an example of the Adapter pattern in Java:
interface OldSailor {
void hoistTheColors();
}
class OldSailorImpl implements OldSailor {
@Override
public void hoistTheColors() {
System.out.println("The old sailor is hoisting the colors!");
}
}
interface NewSailor {
void raiseTheFlag();
}
class SailorAdapter implements NewSailor {
private OldSailor oldSailor;
public SailorAdapter(OldSailor oldSailor) {
this.oldSailor = oldSailor;
}
@Override
public void raiseTheFlag() {
oldSailor.hoistTheColors();
}
}
Now ye can make old sailors understand the new lingo by using the adapter!
Bridge Pattern
The Bridge pattern be like a sturdy gangplank that connects two ships. It decouples an abstraction from its implementation, allowing the two to vary independently.
Here’s an example of the Bridge pattern in Java:
interface Weapon {
void swing();
}
class Cutlass implements Weapon {
@Override
public void swing() {
System.out.println("Swinging the cutlass!");
}
}
class Blunderbuss implements Weapon {
@Override
public void swing() {
System.out.println("Swinging the blunderbuss!");
}
}
abstract class Pirate {
protected Weapon weapon;
public Pirate(Weapon weapon) {
this.weapon = weapon;
}
public abstract void attack();
}
class Captain extends Pirate {
public Captain(Weapon weapon) {
super(weapon);
}
@Override
public void attack() {
System.out.print("The captain is ");
weapon.swing();
}
}
class FirstMate extends Pirate {
public FirstMate(Weapon weapon) {
super(weapon);
}
@Override
public void attack() {
System.out.print("The first mate is ");
weapon.swing();
}
}
Now ye can switch weapons without changing the pirate classes, and add new pirate classes without modifying the weapon classes!
Composite Pattern
The Composite pattern be like a barrel full of monkeys, allowing you to build structures of objects in the form of trees that contain both compositions of objects and individual objects as nodes. It lets clients treat individual objects and compositions of objects uniformly.
Here’s an example of the Composite pattern in Java:
interface CrewMember {
void work();
}
class Sailor implements CrewMember {
@Override
public void work() {
System.out.println("The sailor is working!");
}
}
class Cook implements CrewMember {
@Override
public void work() {
System.out.println("The cook is preparing a meal!");
}
}
class Crew implements CrewMember {
private List<CrewMember> crewMembers = new ArrayList<>();
public void add(CrewMember crewMember) {
crewMembers.add(crewMember);
}
public void remove(CrewMember crewMember) {
crewMembers.remove(crewMember);
}
@Override
public void work() {
for (CrewMember crewMember : crewMembers) {
crewMember.work();
}
}
}
Now ye can treat the entire crew as a single unit, and make 'em all work together!
### Decorator Pattern
The Decorator pattern be like adding fancy decorations to yer captain's quarters. It allows ye to attach new behaviors to objects by placing these objects inside special wrapper objects that contain the new behaviors.
Here's an example of the Decorator pattern in Java:
```java
interface Treasure {
int getValue();
}
class GoldCoin implements Treasure {
@Override
public int getValue() {
return 10;
}
}
abstract class TreasureDecorator implements Treasure {
protected Treasure treasure;
public TreasureDecorator(Treasure treasure) {
this.treasure = treasure;
}
@Override
public int getValue() {
return treasure.getValue();
}
}
class JeweledTreasure extends TreasureDecorator {
public JeweledTreasure(Treasure treasure) {
super(treasure);
}
@Override
public int getValue() {
return treasure.getValue() + 100;
}
}
Now ye can add jewels to yer treasure and increase its value without modifying the original treasure classes!
Facade Pattern
The Facade pattern be like a map of a hidden treasure island, providing a simplified interface to a complex subsystem. It hides the complexities of the subsystem from the client and allows them to interact with the subsystem through a simple interface.
Here’s an example of the Facade pattern in Java:
class NavigationSystem {
void planRoute() {
System.out.println("Planning the route...");
}
}
class ShipMaintenance {
void checkSystems() {
System.out.println("Checking ship systems...");
}
}
class ShipFacade {
private NavigationSystem navigationSystem;
private ShipMaintenance shipMaintenance;
public ShipFacade() {
this.navigationSystem = new NavigationSystem();
this.shipMaintenance = new ShipMaintenance();
}
public void prepareForVoyage() {
shipMaintenance.checkSystems();
navigationSystem.planRoute();
}
}
Now ye can prepare for a voyage by simply calling the prepareForVoyage
method on the ShipFacade
!
Flyweight Pattern
The Flyweight pattern be like a ship’s cargo hold, storing a large number of objects that share common properties in an efficient manner. It minimizes memory usage by sharing as much data as possible with similar objects.
Here’s an example of the Flyweight pattern in Java:
class CannonBall {
private String size;
public CannonBall(String size) {
this.size = size;
}
public void fire() {
System.out.println("Firing a " + size + " cannonball!");
}
}
class CannonBallFactory {
private Map<String, CannonBall> cannonBalls = new HashMap<>();
public CannonBall getCannonBall(String size) {
if (!cannonBalls.containsKey(size)) {
cannonBalls.put(size, new CannonBall(size));
}
return cannonBalls.get(size);
}
}
Now ye can fire many cannonballs of the same size without creating multiple objects for each one!
Proxy Pattern
The Proxy pattern be like a lookout in the crow’s nest, acting as a stand-in for another object. It controls access to the real object, allowing you to perform actions before or after the request reaches the real object.
Here’s an example of the Proxy pattern in Java:
interface TreasureChest {
void open();
}
class RealTreasureChest implements TreasureChest {
@Override
public void open() {
System.out.println("Opening the treasure chest...");
}
}
class TreasureChestProxy implements TreasureChest {
private RealTreasureChest realTreasureChest;
public TreasureChestProxy() {
this.realTreasureChest = new RealTreasureChest();
}
@Override
public void open() {
System.out.println("Checking for traps...");
realTreasureChest.open();
}
}
Now, when ye try to open the treasure chest, the proxy will first check for traps before actually opening the real treasure chest!
That be all, matey, for the structural design patterns! These patterns will help ye sail smoothly through the treacherous waters of Java application development. Remember, a good pirate knows when to use the right pattern for the right situation. Fair winds and following seas to ye!
Behavioral Patterns
As we sail into the territory of behavioral patterns, let’s remember that these patterns be focused on communication and assignment of responsibilities between objects. They help ye keep a well-organized crew on your Java vessel.
There be eleven main behavioral patterns ye should be aware of:
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Chain of Responsibility Pattern
The Chain of Responsibility pattern be like a line of sailors passing treasure from one to another. It creates a chain of objects that can handle a request, and the request is passed along the chain until an object handles it.
Here’s an example of the Chain of Responsibility pattern in Java:
abstract class CrewHandler {
protected CrewHandler nextHandler;
public void setNextHandler(CrewHandler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
class CookHandler extends CrewHandler {
@Override
public void handleRequest(String request) {
if (request.equalsIgnoreCase("Cook")) {
System.out.println("The cook is handling the request!");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
class SailorHandler extends CrewHandler {
@Override
public void handleRequest(String request) {
if (request.equalsIgnoreCase("Sailor")) {
System.out.println("The sailor is handling the request!");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
Now ye can create a chain of handlers and pass requests along it, allowing each handler to decide if it can handle the request or pass it to the next handler in the chain!
Command Pattern
The Command pattern be like a treasure map, turning a request into a stand-alone object that contains all information about the request. It allows ye to pass requests as method arguments, delay or queue a request’s execution, and support undoable operations.
Here’s an example of the Command pattern in Java:
interface Command {
void execute();
}
class RaiseSailsCommand implements Command {
@Override
public void execute() {
System.out.println("Raising the sails!");
}
}
class FireCannonsCommand implements Command {
@Override
public void execute() {
System.out.println("Firing the cannons!");
}
}
class Captain {
public void giveOrder(Command command) {
command.execute();
}
}
Now ye can give commands to the captain, and he’ll execute them without knowing the details of each command!
Interpreter Pattern
The Interpreter pattern be like a parrot that can speak different languages, providing a way to evaluate language grammar or expressions. It’s often used in compilers, parsers, and calculators.
Here’s an example of the Interpreter pattern in Java:
interface Expression {
boolean interpret(String context);
}
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
return context.contains(data);
}
}
class OrExpression implements Expression {
private Expression expr1;
private Expression expr2;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
class AndExpression implements Expression {
private Expressionexpr1;
private Expression expr2;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
Now ye can combine different expressions and interpret them based on the context!
Iterator Pattern
The Iterator pattern be like a sailor navigating through a ship, providing a way to access elements of an aggregate object sequentially without exposing its underlying representation.
Here’s an example of the Iterator pattern in Java:
interface Iterator {
boolean hasNext();
Object next();
}
interface Container {
Iterator getIterator();
}
class TreasureChest implements Container {
private String[] treasures = {"gold", "silver", "pearls"};
@Override
public Iterator getIterator() {
return new TreasureIterator();
}
private class TreasureIterator implements Iterator {
int index;
@Override
public boolean hasNext() {
return index < treasures.length;
}
@Override
public Object next() {
if (hasNext()) {
return treasures[index++];
}
return null;
}
}
}
Now ye can create a treasure chest and iterate through its treasures without knowing the details of how they be stored!
Mediator Pattern
The Mediator pattern be like a first mate, helping to reduce coupling between classes by having them communicate through a central object called a mediator.
Here’s an example of the Mediator pattern in Java:
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
class PirateMediator implements Mediator {
private List<Colleague> colleagues = new ArrayList<>();
public void addColleague(Colleague colleague) {
colleagues.add(colleague);
}
@Override
public void sendMessage(String message, Colleague originator) {
for (Colleague colleague : colleagues) {
if (colleague != originator) {
colleague.receiveMessage(message);
}
}
}
}
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
public abstract void receiveMessage(String message);
}
class PirateColleague extends Colleague {
public PirateColleague(Mediator mediator) {
super(mediator);
}
@Override
public void receiveMessage(String message) {
System.out.println("Pirate received: " + message);
}
}
Now ye can have colleagues communicate with each other through the mediator without being directly coupled!
Memento Pattern
The Memento pattern be like a treasure chest full of memories, capturing and storing an object’s internal state so that it can be restored to that state later.
Here’s an example of the Memento pattern in Java:
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento) {
mementoList.add(memento);
}
public Memento get(int index) {
return mementoList.get(index);
}
}
Now ye can save and restore the state of an object like a true time-traveling pirate!
Observer Pattern
The Observer pattern be like a lookout on the crow’s nest, where an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes.
Here’s an example of the Observer pattern in Java:
interface Observer {
void update(String message);
}
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
class PirateCaptain implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
class PirateCrewMember implements Observer {
@Override
public void update(String message) {
System.out.println("Crew member received: " + message);
}
}
Now ye can have the pirate captain send orders to the crew members without direct communication!
Strategy Pattern
The Strategy pattern be like a pirate choosing different tactics in battle, allowing the behavior of an object to be changed at runtime by selecting from a set of interchangeable algorithms.
Here’s an example of the Strategy pattern in Java:
interface AttackStrategy {
void attack();
}
class CannonStrategy implements AttackStrategy {
@Override
public void attack() {
System.out.println("Fire the cannons!");
}
}
class BoardingStrategy implements AttackStrategy {
@Override
public void attack() {
System.out.println("Prepare to board!");
}
}
class PirateShip {
private AttackStrategy attackStrategy;
public void setAttackStrategy(AttackStrategy attackStrategy) {
this.attackStrategy = attackStrategy;
}
public void executeAttack() {
attackStrategy.attack();
}
}
Now ye can switch between attack strategies at runtime and adapt to the situation like a cunning pirate!
Arrr! With these here patterns in yer treasure chest, ye be prepared to face any coding challenge on the high seas of Java development! Remember, these be just a few of the many design patterns ye can use to make yer code more flexible and maintainable. Now hoist the Jolly Roger and set sail for more adventures in the world of Java!
Expression expr1; private Expression expr2;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
} } ```
Now ye can create complex expressions with different interpreters and evaluate them based on the given context!
Model-View-Controller (MVC) Pattern
As we reach the MVC pattern, let’s think of it as a well-organized pirate crew. The Model-View-Controller pattern separates the concerns of an application into three interconnected parts, making it easier to maintain and modify.
- Model: Represents the data and business logic of the application. It be like the treasure of your pirate ship.
- View: Represents the user interface and display of the data. It be like the spyglass ye use to view the treasure.
- Controller: Acts as an intermediary between the Model and View, handling user input and updating the Model and View accordingly. It be like the captain who decides how the treasure is managed.
Here’s an example of the MVC pattern in Java:
// Model
class Pirate {
private String name;
private int gold;
public Pirate(String name, int gold) {
this.name = name;
this.gold = gold;
}
// Getters and Setters ...
}
// View
class PirateView {
public void displayPirateDetails(String pirateName, int gold) {
System.out.println("Pirate: " + pirateName + ", Gold: " + gold);
}
}
// Controller
class PirateController {
private Pirate model;
private PirateView view;
public PirateController(Pirate model, PirateView view) {
this.model = model;
this.view = view;
}
// Getters and Setters for model ...
public void updateView() {
view.displayPirateDetails(model.getName(), model.getGold());
}
}
public class MVCPatternDemo {
public static void main(String[] args) {
// Create a pirate and view
Pirate pirate = new Pirate("Blackbeard", 1000);
PirateView view = new PirateView();
// Create a controller and update the view
PirateController controller = new PirateController(pirate, view);
controller.updateView();
// Update the model and view again
controller.setPirateName("Captain Kidd");
controller.setPirateGold(2000);
controller.updateView();
}
}
Now ye have a well-organized crew, separating the concerns of data, display, and control in your Java application!
Conclusion
Congratulations, matey! Ye’ve learned about common design patterns in Java, like the creational, structural, and behavioral patterns, as well as the Model-View-Controller (MVC) pattern. With these patterns in your treasure chest, ye be well-equipped to write more maintainable, flexible, and organized Java code on your swashbuckling programming adventures!