Double click to toggle Read Mode.

Design Patterns Course

Github Link

Table of Contents

  1. UML Notation for Class Diagram
  2. Behavioral Design Patterns
  3. Memento Pattern
  4. Observer Pattern
  5. State Pattern
  6. Iterator Pattern
  7. Strategy Pattern
  8. Command Pattern


UML Notation for Class Diagram

Association

classDiagram
  direction LR
  class Class1 {
  }
  class Class2 {
  }
  Class1 -->  Class2
class Class1 { void method(Class2 c2) { } } // Example class Student { void enrollIn(Course course) { } // Student uses Course }

Inheritance

classDiagram
  class Base {
  }
  class Derived {
  }
  Base <|--  Derived
class Base { } class Derived extends Base { } // Example class Animal { } class Dog extends Animal { }

Aggregation

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
  direction LR
  class Class1 {
  }
  class Class2 {
  }
  Class1 "1" o-- "*" Class2
class Class1 { List<Class2> c2s; // Class1 has class2s } // Example class Department { List<Employee> employees; // Department has employees }

Composition

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
  direction LR
  class Class1 {
  }
  class Class2 {
  }
  Class1 "1" *-- "*" Class2
class Class1 { private Class2 c2 = new Class2(); // Class2 cannot exist without Class1 } // Example class House { private Room room = new Room(); // Room cannot exist without House }

Dependency

classDiagram
  direction LR
  class Class1 {
  }
  class Class2 {
  }
  Class1 ..>  Class2
class Class1 { void method() { Class2 c3 = new Class2(); // Temporary dependency with Class2 c2.methodFromC2(); } } // Example class OrderProcessor { void process(Order order) { // association with Order PaymentGateway gateway = new PaymentGateway(); // Temporary dependency with PaymentGateway gateway.charge(order.getAmount()); } }



Design Patterns

Behavioral Design Patterns

Memento Pattern

Applicability

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Editor {
    - content: String
    + createState(): EditorState
    + restore(EditorState): void
    + getContent(): String
    + setContent(String): void
}

class EditorState {
    - content: String
    + getContent(): String
}

class History {
    - states: Stack
    + push(EditorState): void
    + pop(): EditorState
}

Editor  -->  EditorState
History o-- EditorState

General Vocabulary

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Originator {
    - state
    + createMemento(): Memento
    + restore(Memento): void
    + getMemento()
    + setMemento(Memento): void
}

class Memento {
    - state
    + getState()
}

class Caretaker {
    - states: List
    + push(Memento): void
    + pop(): Memento
}

Originator  -->  Memento
Caretaker o-- Memento

Create EditorState class

public class EditorState { private final String content; public EditorState(String content) { this.content = content; } public String getContent() { return content; } }

Create Editor class

public class Editor { private String content; public EditorState createState() { return new EditorState(content); } public void restore(EditorState state) { content = state.getContent(); } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }

Create History class

import java.util.Stack; public class History { private Stack<EditorState> history = new Stack<>(); public void push(EditorState state) { history.push(state); } public EditorState pop() { history.pop(); return history.peek(); } }

Create Main class

public class Main { public static void main(String[] args) { var editor = new Editor(); var history = new History(); editor.setContent("a"); history.push(editor.createState()); editor.setContent("b"); history.push(editor.createState()); editor.setContent("c"); history.push(editor.createState()); System.out.println(editor.getContent()); editor.restore(history.pop()); System.out.println(editor.getContent()); editor.restore(history.pop()); System.out.println(editor.getContent()); } }

Without Memento Pattern

import java.util.Stack; public class Editor { private String content; private Stack<String> history = new Stack<>(); public void setContent(String content) { history.push(content); this.content = content; } public String getContent() { return content; } public void undo() { history.pop(); content = history.peek(); } } public class Main { public static void main(String[] args) { Editor editor = new Editor(); editor.setContent("a"); editor.setContent("b"); editor.setContent("c"); System.out.println(editor.getContent()); editor.undo(); System.out.println(editor.getContent()); editor.undo(); System.out.println(editor.getContent()); } }

For supporting Redo as well

Update History class

import java.util.Stack; public class History { private Stack<EditorState> undoStack = new Stack<>(); private Stack<EditorState> redoStack = new Stack<>(); public void push(EditorState state) { undoStack.push(state); redoStack.clear(); } public EditorState popFromUndo() { var lastState = undoStack.pop(); redoStack.push(lastState); return undoStack.peek(); } public EditorState popFromRedo() { var lastState = redoStack.pop(); undoStack.push(lastState); return lastState; } }

Update Main class

public class Main { public static void main(String[] args) { var editor = new Editor(); var history = new History(); editor.setContent("a"); history.push(editor.createState()); editor.setContent("b"); history.push(editor.createState()); editor.setContent("c"); history.push(editor.createState()); System.out.println(editor.getContent()); // c // Undo operations editor.restore(history.popFromUndo()); System.out.println(editor.getContent()); // b editor.restore(history.popFromUndo()); System.out.println(editor.getContent()); // a // Redo operations editor.restore(history.popFromRedo()); System.out.println(editor.getContent()); // b editor.restore(history.popFromRedo()); System.out.println(editor.getContent()); // c } }



Observer Pattern

Applicability

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class DataSource {
    - observers: List
    - value: int
    + getValue(): int
    + setValue(int): void
    + addObserver(Observer): void
    + removeObserver(Observer): void
    + notifyObservers(): void
}

class Observer {
    <<interface>>
    + update(): void
}

class BarChart {
    + update(): void
}
class PieChart {
    + update(): void
}

DataSource "1" o-- "*" Observer
Observer <|-- BarChart
Observer <|-- PieChart

General Vocabulary

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Subject {
    - observers: List
    - value: int
    + getValue(): int
    + setValue(int): void
    + addObserver(Observer): void
    + removeObserver(Observer): void
    + notifyObservers(): void
}

class Observer {
    <<interface>>
    + update(): void
}

class ConcreteObserver {
    + update(): void
}

Subject "1" o-- "*" Observer
Observer <|-- ConcreteObserver

Create DataSource class

import java.util.ArrayList; import java.util.List; public class DataSource { private List<Observer> observers = new ArrayList<>(); private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; notifyObservers(); } public void addObserver(Observer observer) { observers.add(observer); } public void removeObserver(Observer observer) { observers.remove(observer); } public void notifyObservers() { for (var observer : observers) observer.update(); } }

Create Observer interface

public interface Observer { void update(); }

Implement Concrete Observer: BarChart

public class BarChart implements Observer { private DataSource dataSource; public BarChart(DataSource dataSource) { this.dataSource = dataSource; } @Override public void update() { System.out.println("BarChart got updated: " + dataSource.getValue()); } }

Implement Concrete Observer: PieChart

public class PieChart implements Observer { private DataSource dataSource; public PieChart(DataSource dataSource) { this.dataSource = dataSource; } @Override public void update() { System.out.println("PieChart got updated: " + dataSource.getValue()); } }

Add Main class

public class Main { public static void main(String[] args) { DataSource dataSource = new DataSource(); BarChart barChart = new BarChart(dataSource); PieChart pieChart = new PieChart(dataSource); dataSource.addObserver(barChart); dataSource.addObserver(pieChart); dataSource.setValue(5); dataSource.setValue(10); dataSource.removeObserver(barChart); dataSource.setValue(15); } }

Without using Observer Pattern

public class DataSource { private int value; private BarChart barChart; private PieChart pieChart; public int getValue() { return value; } public void setValue(int value) { this.value = value; updateCharts(); } public void setBarChart(BarChart barChart) { this.barChart = barChart; } public void setPieChart(PieChart pieChart) { this.pieChart = pieChart; } public void removeBarChart() { barChart = null; } public void removePieChart() { pieChart = null; } private void updateCharts() { if (barChart != null) { barChart.update(value); } if (pieChart != null) { pieChart.update(value); } } } public class BarChart { public void update(int value) { System.out.println("BarChart got updated: " + value); } } public class PieChart { public void update(int value) { System.out.println("PieChart got updated: " + value); } } public class Main { public static void main(String[] args) { DataSource dataSource = new DataSource(); BarChart barChart = new BarChart(); PieChart pieChart = new PieChart(); dataSource.setBarChart(barChart); dataSource.setPieChart(pieChart); dataSource.setValue(5); dataSource.setValue(10); dataSource.removeBarChart(); dataSource.setValue(15); } }



State Pattern

Applicability

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Canvas {
    - currentTool: Tool
    + mouseDown(): void
    + mouseUp(): void
    + getCurrentTool(): Tool
    + setCurrentTool(Tool): void
}

class Tool {
    <<interface>>
    + mouseDown(): void
    + mouseUp(): void
}

class Selection {
    + mouseDown(): void
    + mouseUp(): void
}
class Brush {
    + mouseDown(): void
    + mouseUp(): void
}

Canvas o--Tool
Tool <|-- Selection
Tool <|-- Brush

General Vocabulary

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Context {
    - currentState: State
    + getCurrentState(): State
    + setCurrentState(State): void
    + requests(): void  // inputHandlers
}

class State {
    <<interface>>
    + behaviorHandlers(): void
}

class ConcreteState {
    + behaviorHandlers(): void
}

Context o--State
State <|-- ConcreteState

Create Tool interface

public interface Tool { void mouseDown(); void mouseUp(); }

Create Canvas class

public class Canvas { private Tool currentTool; public void mouseDown() { currentTool.mouseDown(); } public void mouseUp() { currentTool.mouseUp(); } public Tool getCurrentTool() { return currentTool; } public void setCurrentTool(Tool currentTool) { this.currentTool = currentTool; } }

Implement Concrete Tool: Pen Tool

public class PenTool implements Tool { @Override public void mouseDown() { System.out.println("Pen icon"); } @Override public void mouseUp() { System.out.println("Draw a line"); } }

Implement Concrete Tool: Selection Tool

public class SelectionTool implements Tool { @Override public void mouseDown() { System.out.println("Selection icon"); } @Override public void mouseUp() { System.out.println("Draw a dashed rectangle"); } }

Add Main class

public class Main { public static void main(String[] args) { var canvas = new Canvas(); canvas.setCurrentTool(new PenTool()); canvas.mouseDown(); canvas.mouseUp(); canvas.setCurrentTool(new SelectionTool()); canvas.mouseDown(); canvas.mouseUp(); } }

Without using State Pattern

class Canvas { private String currentTool; public void mouseDown() { if (currentTool == "pen") { System.out.println("Pen icon"); } else if (currentTool == "selection") { System.out.println("Selection icon"); } } public void mouseUp() { if (currentTool == "pen") { System.out.println("Draw a line"); } else if (currentTool == "selection") { System.out.println("Draw a dashed rectangle"); } } public String getCurrentTool() { return currentTool; } public void setCurrentTool(String currentTool) { this.currentTool = currentTool; } } public class Main { public static void main(String[] args) { var canvas = new Canvas(); canvas.setCurrentTool("pen"); canvas.mouseDown(); canvas.mouseUp(); canvas.setCurrentTool("selection"); canvas.mouseDown(); canvas.mouseUp(); } }



Iterator Pattern

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class BrowseHistory {
    - urls: Array
    - size: int
    + addUrl(String): void
    + removeUrl(): String
    + createIterator(): Iterator
}

class Iterator {
    <<interface>>
    + hasNext(): boolean
    + next(): String
}

class ArrayIterator {
    - browserHistory: BrowserHistory
    - index: int
    + hasNext(): boolean
    + next(): String
}

class Main {
    + main()
}


BrowseHistory ..> ArrayIterator
ArrayIterator "1" o-- "1" BrowseHistory
Iterator <|-- ArrayIterator
Main ..> BrowseHistory
Main ..> Iterator

General Vocabulary

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Aggregate {
    - datas
    + addData()
    + removeData()
    + createIterator(): Iterator
}

class Iterator {
    <<interface>>
    + hasNext(): boolean
    + next()
}

class ConcreteIterator {
    - aggregate: Aggregate
    + hasNext(): boolean
    + next()
}

class Client


Aggregate ..> ConcreteIterator
ConcreteIterator "1" o-- "1" Aggregate
Iterator <|-- ConcreteIterator
Client ..> Aggregate
Client ..> Iterator

Create Iterator interface

public interface Iterator { boolean hasNext(); String next(); }

Create BrowseHistory class and nested ArrayIterator class

public class BrowseHistory { private String[] urls = new String[10]; private int size = 0; public void addUrl(String url) { urls[size++] = url; } public String removeUrl() { return urls[--size]; } public Iterator createIterator() { return new ArrayIterator(this); } public class ArrayIterator implements Iterator { private BrowseHistory url; private int index = 0; public ArrayIterator(BrowseHistory url) { this.url = url; } @Override public boolean hasNext() { return index < url.size; } @Override public String next() { return url.urls[index++]; } } }

Add Main class

public class Main { public static void main(String[] args) { var history = new BrowseHistory(); history.addUrl("apple.com"); history.addUrl("fb.com"); history.addUrl("instagram.com"); Iterator iterator = history.createIterator(); while (iterator.hasNext()) { String url = iterator.next(); System.out.println(url); } } }

Change BrowseHistory urls' Data Structure to List

import java.util.ArrayList; import java.util.List; public class BrowseHistory { private List<String> urls = new ArrayList<>(); public void addUrl(String url) { urls.add(url); } public String removeUrl() { return urls.remove(urls.size() - 1); } public Iterator createIterator() { return new ListIterator(this); } public class ListIterator implements Iterator { private BrowseHistory browseHistory; private int index = 0; public ListIterator(BrowseHistory browseHistory) { this.browseHistory = browseHistory; } @Override public boolean hasNext() { return index < browseHistory.urls.size(); } @Override public String next() { return browseHistory.urls.get(index++); } } }
Why interace instead of implementing hasnext(), next() methods directly inside BrowseHistory?
// BAD: Direct implementation in aggregate var history = new BrowseHistory(); // Only ONE position tracker in the class history.next(); // moves position to 1 history.next(); // moves position to 2 // Can't have multiple iterations simultaneously
// GOOD: Iterator pattern var history = new BrowseHistory(); Iterator iterator1 = history.createIterator(); Iterator iterator2 = history.createIterator(); iterator1.next(); // iterator1 at position 1, iterator2 still at 0 iterator2.next(); // iterator2 at position 1, iterator1 still at 1
Why nested Concrete Iterator?

Without using Iterator Pattern

Using List

import java.util.ArrayList; import java.util.List; public class BrowseHistory { private List<String> urls = new ArrayList<>(); public void addUrl(String url) { urls.add(url); } public String removeUrl() { return urls.remove(urls.size() - 1); } public List<String> getUrls(){ return urls; } } public class Main { public static void main(String[] args) { var history = new BrowseHistory(); history.addUrl("apple.com"); history.addUrl("fb.com"); history.addUrl("instagram.com"); for(int i=0; i<history.getUrls().size(); i++){ System.out.println(history.getUrls().get(i)); } } }

Using Array

public class BrowseHistory { private String[] urls = new String[10]; private int size = 0; public void addUrl(String url) { urls[size++] = url; } public String removeUrl() { return urls[--size]; } public String[] getUrls(){ return urls; } public int getSize(){ return size; } } public class Main { public static void main(String[] args) { var history = new BrowseHistory(); history.addUrl("apple.com"); history.addUrl("fb.com"); history.addUrl("instagram.com"); for(int i=0; i<history.getSize(); i++){ System.out.println(history.getUrls()[i]); } } }

Disadvantage




Strategy Pattern

Applicability

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class PaymentService {
    - strategy: PaymentStrategy
    + setPaymentStrategy(PaymentStrategy): void
    + pay(): void
}

class PaymentStrategy {
    <<interface>>
    + processPayment(): void
}

class DebitCardPayment {
    + processPayment(): void
}
class EsewaPayment {
    + processPayment(): void
}

PaymentService o-- PaymentStrategy
PaymentStrategy <|-- DebitCardPayment
PaymentStrategy <|-- EsewaPayment

General Vocabulary

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Context {
    - strategy: Strategy
    + setStrategy(Strategy): void
    + process(): void
}

class Strategy {
    <<interface>>
    + process(): void
}

class ConcreteStrategy {
    + process(): void
}

Context o-- Strategy
Strategy <|-- ConcreteStrategy

Create PaymentStrategy interface

public interface PaymentStrategy { void processPayment(); }

Create PaymentService class

public class PaymentService { private PaymentStrategy strategy; public void setPaymentStrategy(PaymentStrategy strategy){ this.strategy = strategy; } public void pay(){ strategy.processPayment(); //Polymorphic Behavior } }

Implement Concrete PaymentStrategy: DebitCardPayment

public class DebitCardPayment implements PaymentStrategy { @Override public void processPayment() { System.out.println("Making payment via Debit Card"); } }

Implement Concrete PaymentStrategy: EsewaPayment

public class EsewaPayment implements PaymentStrategy { @Override public void processPayment() { System.out.println("Making payment via Esewa"); } }

Add Main class

public class Main { public static void main(String[] args) { PaymentService paymentService = new PaymentService(); paymentService.setPaymentStrategy(new EsewaPayment()); paymentService.pay(); } }

Without using Strategy Pattern

public class PaymentService{ public void processPayment(String paymentMethod){ if(paymentMethod.equals("Credit Card")){ System.out.println("Making payment via credit card"); } else if(paymentMethod.equals("Debit Card")){ System.out.println("Making payment via debit card"); } else if(paymentMethod.equals("Esewa")){ //huge algorithm System.out.println("Making payment via Esewa"); } else{ System.out.println("Unsupported Payment method"); } } } public class Main { public static void main(String[] args) { PaymentService paymentService = new PaymentService(); paymentService.processPayment("Esewa"); } }



Command Pattern

Applicability

Command Pattern Class Diagram

General Vocabulary

Command Pattern Class Diagram

Create Command interface

public interface Command { void execute(); }

Create Button class

public class Button { private Command command; public Button(Command command) { this.command = command; } public void click() { command.execute(); } }

Create TextEditor class

public class TextEditor { public void boldText() { System.out.println("Text has been bolded."); } }

Create BoldCommand class

class BoldCommand implements Command { private TextEditor editor; public BoldCommand(TextEditor editor){ this.editor = editor; } @Override public void execute() { editor.boldText(); } }

Create Main class

public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor(); var boldCommand = new BoldCommand(editor); Button button = new Button(boldCommand); button.click(); } }

Without Command Pattern

// Direct use on click public class Button { private TextEditor editor; public Button(TextEditor editor) { this.editor = editor; } public void click() { editor.boldText(); } } // Same public class TextEditor { public void boldText() { System.out.println("Text has been bolded."); } } // Main class - without Command pattern public class Main { public static void main(String[] args) { TextEditor textEditor = new TextEditor(); Button button = new Button(textEditor); button.click(); } }

Composite Command

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Command {
    <<interface>>
    + execute()
}

class ConcreteCommand {
    - service
    + execute()
}

class CompositeCommand {
    - commands: List
    + addCommand(Command)
    + execute()
}

Command <|-- ConcreteCommand
Command <|-- CompositeCommand
ConcreteCommand "*" --o "1" CompositeCommand

Add italicizeText functionality in CustomerService

public void italicizeText() { System.out.println("Text has been italicized."); }

Create ItalicCommand Command

class ItalicCommand implements Command { private TextEditor editor; public ItalicCommand(TextEditor editor){ this.editor = editor; } @Override public void execute() { editor.italicizeText(); } }

Create ComplexCommand Command

import java.util.ArrayList; import java.util.List; public class ComplexCommand implements Command { private List<Command> commands = new ArrayList<>(); public void addCommand(Command command) { commands.add(command); } @Override public void execute() { for (var command: commands) { command.execute(); } } }

Update main

public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor(); var boldCommand = new BoldCommand(editor); var italicCommand = new ItalicCommand(editor); var complexCommand = new ComplexCommand(); complexCommand.addCommand(boldCommand); complexCommand.addCommand(italicCommand); Button button = new Button(complexCommand); button.click(); } }

Undoable Command

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Command {
    <<interface>>
    + execute()
}

class UndoableCommand {
    <<interface>>
    + unexecute()
}

class ConcreteUndoableCommand {
    + execute()
    + unexecute()
}

class ConcreteCommand {
    + execute()
}


Command <|-- UndoableCommand: extends
UndoableCommand <|-- ConcreteUndoableCommand: implements
Command <|-- ConcreteCommand: implements

History

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class History {
    - undoableCommands: Stack
    + push(UndoableCommand)
    + pop(): UndoableCommand
}

class TextEditor {
    - content: String
    + boldText()
    + setContent(String)
    + getContent(): String
}

class BoldCommand {
    - prevContent: String
    - editor: TextEditor
    - history: History
    + execute()
    + unexecute()
}

class UndoCommand {
    - history: History
    + execute()
}


UndoCommand o-- History
BoldCommand o-- TextEditor
BoldCommand o-- History

Complete Command Pattern with History

%%{init: { "flowchart": { "rankSpacing": 100, "nodeSpacing": 100 }}}%%
classDiagram
direction LR
class Command {
    <<interface>>
    + execute()
}

class UndoableCommand {
    <<interface>>
    + unexecute()
}

class Button {
    - command: Command
    + click(): void
}

class History {
    - undoableCommands: Stack
    + push(UndoableCommand)
    + pop(): UndoableCommand
}

class TextEditor {
    - content: String
    + boldText()
    + setContent(String)
    + getContent(): String
}

class BoldCommand {
    - prevContent: String
    - editor: TextEditor
    - history: History
    + execute()
    + unexecute()
}

class UndoCommand {
    - history: History
    + execute()
}


Command <|-- UndoableCommand
UndoableCommand <|-- BoldCommand
Command <|-- UndoCommand
History o-- "*" UndoableCommand
UndoCommand o-- History
BoldCommand o-- TextEditor
BoldCommand o-- History
Button  o--  Command

Create UndoableCommand interface

public interface UndoableCommand extends Command { void unexecute(); }

Create History class

import java.util.Stack; public class History { private Stack<UndoableCommand> commands = new Stack<>(); public void push(UndoableCommand command) { commands.push(command); } public UndoableCommand pop() { return commands.pop(); } }

Create UndoCommand

public class UndoCommand implements Command { private History history; public UndoCommand(History history) { this.history = history; } @Override public void execute() { history.pop().unexecute(); } }

Update TextEditor

public class TextEditor { private String content; public void boldText() { content = "<b>" + content + "</b>"; } public void italicizeText() { content = "<i>" + content + "</i>"; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }

Update BoldCommand

class BoldCommand implements UndoableCommand { private String prevContent; private TextEditor editor; private History history; public BoldCommand(TextEditor editor, History history){ this.editor = editor; this.history = history; } @Override public void unexecute() { editor.setContent(prevContent); } @Override public void execute() { prevContent = editor.getContent(); editor.boldText(); history.push(this); } }

Update Main

public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor(); History history = new History(); editor.setContent("Hello World"); System.out.println(editor.getContent()); var boldCommand = new BoldCommand(editor, history); Button button1 = new Button(boldCommand); button1.click(); System.out.println(editor.getContent()); var undoCommand = new UndoCommand(history); Button button2 = new Button(undoCommand); button2.click(); System.out.println(editor.getContent()); } }