Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Common Design Patterns in Java: Creational Patterns

Header Image

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:

  1. Singleton
  2. Factory Method
  3. Abstract Factory
  4. Builder
  5. 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:

  1. Adapter
  2. Bridge
  3. Composite
  4. Decorator
  5. Facade
  6. Flyweight
  7. 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:

  1. Chain of Responsibility
  2. Command
  3. Interpreter
  4. Iterator
  5. Mediator
  6. Memento
  7. Observer
  8. State
  9. Strategy
  10. Template Method
  11. 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!