Structural and Behavioral Design Patterns with Java Coding Examples

Hanwen Zhang
7 min readAug 12, 2023

--

Key Takeaways of Structural and Behavior Design Patterns for Java Design Patterns, 2nd Edition by Vaskaran Sarcar

Summarize what I have learned about Design Patterns in Object Oriented Programming with code examples (Java).

Also elaborate more on frequently used design patterns including:

  • Creational PatternsAbstract Factory, Factory Method, Abstract Factory, Build => Read more
  • Structural PatternsDecorator, Composite, Adapter
  • Behavioral PatternsObserver, Strategy
  • For an overview of all 23 GOF Design Patterns, Read more

Talk is cheap, show me the code.

Structural Patterns

Adapter Pattern

Also known as — Wrapper, like a transmitter, connecting two things compatible, making unrelated classes work together, like having an in-house to implement a third-party interface

  • Client collaborates with objects conforming to the Target interface
  • Target defines the domain-specific interface that the Client uses
  • Adapter adapts the interface of the adaptee to the Target interface
  • Adaptee defines an existing interface that needs adapting
public class AdapterPattern {
public static void main(){
Verifier verifier = new Verifier();
verifier.verify(); //return true
Adapter adapter = new Adapter();
adapter.verify(); //"Adaptee" will print out and return true
}
}
public interface IVerify {
public boolean verify();
}
class Verifier implements IVerify {
public boolean verify() { return true; }
}
class Adaptee {
public static void print() {
System.out.println("Adaptee");
}
}
class Adapter implements IVerify {
public boolean verify() {
Adaptee.print();
return true;
}
}

Decorator Pattern

Also known as — Wrapper, describes how to add responsibilities to objects dynamically, wrap one object within another like a recursion by attaching additional responsibilities to existing objects, like adding a linked list to a certain procedure.

  • Component defines the interface for objects that can have responsibilities added to them dynamically
  • ConcreteComponent defines an object to which additional responsibilities can be attached
  • Decorator maintains a reference to a Component object and defines an interface that conforms to the Component’s interface
  • ConcreteDecorator adds responsibilities to the component
public class DecoratorPattern {
public static void main() {
Transaction deposit = new DepositPrint();
System.out.println(deposit.getDescription()); //Deposit
Transaction logging = new Logging(deposit);
System.out.println(logging.getDescription()); //Logging Deposit
Transaction security = new Security(deposit);
System.out.println(security.getDescription()); //Security Deposit
deposit = new Logging(deposit);
deposit = new Security(deposit);
System.out.println(deposit.getDescription()); //Logging Deposit Security Deposit
}
}
public abstract class Transaction {
public abstract String getDescription();
}
class DepositPrint extends Transaction {
public String getDescription() {return "Deposit"};
}
public abstract Decorator extends Transaction {
Transaction transaction;
public abstract String getDescription();
}
class Logging extends Decorators {
public Logging(Transaction t) {transaction = t}
public String getDescription() {return "Logging " + transaction.getDescription()}
}
class Security extends Decorators {
public Security(Transaction t) {transaction = t}
public String getDescription() {return "Security" + transaction.getDescription()}
}

Composite Pattern

Describes how to build a class hierarchy, need to think about the base case, like having a tree of activities delegated to the leaves, the same interface depends on hierarchy like tree structure

  • Component declares the interface for objects in the composition, implements default behavior for the interface common to all classes, declares an interface for managing and accessing its child components
  • Leaf represents leaf objects in the composition, defines behavior for primitive objects in the composition
  • Composite defines behavior for components having children, stores child components implement child-related operations in the Component interface
  • Client manipulates objects in the composition through the Component interface
public class CompositePattern {
public static void main(){
MonthlyStatement jan = new MonthlyStatement("January", 2022);
jan.print();

ComboStatement q1 = new ComboStatement("Q1", 2022);
MonthlyStatement feb = new MonthlyStatement("February", 2022);
MonthlyStatement mar = new MonthlyStatement("March", 2022);
q1.add(jan);
q1.add(feb);
q1.add(mar);
q1.print();

ComboStatement q2 = new ComboStatement("Q2", 2022);
MonthlyStatement apr = new MonthlyStatement("April", 2022);
MonthlyStatement may = new MonthlyStatement("May", 2022);
MonthlyStatement jun = new MonthlyStatement("June", 2022);
q2.add(apr);
q2.add(may);
q2.add(jun);
q2.print();

ComboStatement halfYear = new ComboStatement("First Half Year", 2022);
halfYear.add(q1);
halfYear.add(q2);
halfYear.print();
}
}

public abstract class Statement {
ArrayList<Statement> statements = new ArrayList<>();
public void add(Statement statement){
statements.add(statement);
};
public abstract void print();
}

class MonthlyStatement extends Statement { //leaf
String month;
int year;
public MonthlyStatement (String month, int year) {
this.month = month;
this.year = year;
}
public void print(){
System.out.println("** Monthly Statement for " + this.month + ", " + this.year);
}
}

class ComboStatement extends Statement { //Composite
String type;
int year;
public ComboStatement (String type, int year) {
this.type = type;
this.year = year;
}
public void add(Statement statement) {
super.statements.add(statement);
}
public void print(){
System.out.println("** Combo Statement of " + this.type + " Statement for " + this.year);
for (int i = 0; i<statements.size(); i++) {
super.statements.get(i).print();
}
}
}

Behavioral Patterns

Observer Pattern

Also known as Publish-Subscribe, defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This pattern is used when you are interested in the state of an object and want to get notified whenever there is any change. The known use case is the MVC framework.

public class ObserverPattern {
public static void main() {
SimpleSubject ads1 = new SimpleSubject();
SimpleSubject ads2 = new SimpleSubject();

SimpleObserver a = new SimpleObserver("A");
SimpleObserver b = new SimpleObserver("B");
SimpleObserver c = new SimpleObserver("C");
SimpleObserver d = new SimpleObserver("D");

ads1.registerObserver(a);
ads1.registerObserver(b);
ads1.setValue("Sending ads1 to A and B...");

ads1.registerObserver(c);
ads1.registerObserver(d);
ads1.setValue("Sending ads1 to C and D...");
}
}
public interface Observer {
public void update(String value);
}

public class SimpleObserver implements Observer {
private String value;
private String name;
public SimpleObserver(String name) {
this.name = name;
}
public void update(String value) {
this.value = value;
display();
}
public void display() {
System.out.println("Message received by " + name + " : " + value);
}
}

public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}

public class SimpleSubject implements Subject{
private List<Observer> observers;
private String value;
public SimpleSubject() {
observers = new ArrayList<Observer>();
}
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for(Observer observer: observers) {
observer.update(value);
}
}
public void setValue(String value) {
this.value = value;
notifyObservers();
}
}

Strategy Pattern

Encapsulates an algorithm in an object that switches between different implementations based on context, a way of doing something. Defines a family of algorithms, encapsulates each one, makes them interchangeable, and changes its implementations independently from clients that use it.

State Pattern and the Strategy Pattern

  • Both use composition to decouple the context and dynamic behaviors.
  • Both use specification inheritance to provide the same interface for different behavior.

Strategy pattern provides flexibility, extensibility, and choice.

  • Strategy declares an interface common to all supported algorithms. Context uses this interface to call the algorithm defined by a ConcreteStrategy, a context class in which a context object contains a reference to the strategy objects interface
  • ConcreteStrategy implements the algorithm using the Strategy interface and works as a context class, we delegate the task to the corresponding behavior class. Concrete classes contain the algorithms.
  • Context is configured with a ConcreteStrategy object and maintains a reference to a Strategy object and may define an interface that lets Strategy access its data (Client)
public class StrategyPattern {
public static void main(){
SavingsAccount account = new SavingsAccount();

System.out.println("Using Simple interest");
account.setCalculatedInterest(new SimpleInterest(1000000, 0.02));
account.showCalculatedInterest();

System.out.println("Using Compound interest");
account.setCalculatedInterest(new DailyCompoundInterest(1000000, 0.02));
account.showCalculatedInterest();
}
}

public class SavingsAccount { //context class contains reference variables for the strategy objects interface type
InterestType interestType;
public void setCalculatedInterest(InterestType interestType) { //setter to set the strategy
this.interestType = interestType;
}
public void showCalculatedInterest() { //delegate the task to the corresponding behavior class
interestType.calculateInterest();
}
}

public interface InterestType {
void calculateInterest();
}

public class SimpleInterest extends SavingsAccount implements InterestType {
int beginningBalance;
double interestRate;

public SimpleInterest(int balance, double rate) {
this.beginningBalance = balance;
this.interestRate = rate;
}
public void calculateInterest() {
System.out.println(beginningBalance * interestRate / 12.0);
}
}

public class DailyCompoundInterest extends SavingsAccount implements InterestType {
int beginningBalance;
double interestRate;

public DailyCompoundInterest(int balance, double rate) {
this.beginningBalance = balance;
this.interestRate = rate;
}
public void calculateInterest() {
System.out.println(beginningBalance * Math.pow((1 + interestRate / 365), 31) - beginningBalance);
}
}

--

--

Hanwen Zhang

Full-Stack Software Engineer at a Healthcare Tech Company | Document My Coding Journey | Improve My Knowledge | Share Coding Concepts in a Simple Way