Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

4 Commits

Repository files navigation

Java Design Patterns Examples 🚀

Welcome to the Java Design Patterns Examples repository! This project demonstrates key design patterns in Java through real-world examples. It is ideal for learning Object-Oriented Programming (OOP) design principles.

Table of Contents 📚

  1. Singleton Pattern: Ensures a class has only one instance and provides a global point of access to it.
  2. Decorator Pattern: Allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.
  3. Observer Pattern: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

The Singleton pattern is demonstrated through a database connection manager.

  • Class: DatabaseConnection
  • Example Usage:
    DatabaseConnectiondb1 = DatabaseConnection.getInstance();
    DatabaseConnectiondb2 = DatabaseConnection.getInstance();
    db1.query("SELECT * FROM users");
    db2.query("INSERT INTO users VALUES (1, 'John')");

The Decorator pattern is demonstrated through a customizable coffee shop.

  • Classes: SimpleCoffee, MilkDecorator, SugarDecorator
  • Example Usage:
    Coffeecoffee = newSimpleCoffee();
    coffee = newMilkDecorator(coffee);
    coffee = newSugarDecorator(coffee);
    System.out.println(coffee.getDescription() + " $" + coffee.getCost());

The Observer pattern is demonstrated through a weather monitoring system.

  • Classes: WeatherStation, CurrentConditionsDisplay, StatisticsDisplay
  • Example Usage:
    WeatherStationweatherStation = newWeatherStation();
    weatherStation.registerObserver(newCurrentConditionsDisplay());
    weatherStation.registerObserver(newStatisticsDisplay());
    weatherStation.setTemperature(80);
Singleton Pattern Overview 🔒🌐

Introduction 🌟🧩

The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful when exactly one object is needed to coordinate actions across the system.

Scenario: Database Connection 💻🔗

In many applications, you need to ensure that only one instance of a database connection is created to prevent resource conflicts and manage resources efficiently. The Singleton pattern ensures that there is only one instance of a class and provides a global point of access to it.

Code Explanation 📜💻

privatestaticDatabaseConnectioninstance;
  • This variable holds the single instance of the class.
  • Being static means it belongs to the class, not to any specific object of the class.
privateDatabaseConnection() {
System.out.println("Database Connection established");
}
  • The constructor is private, preventing other classes from instantiating the DatabaseConnection class directly.
  • It ensures that the only way to get an instance of this class is through the getInstance method.
publicstaticDatabaseConnectiongetInstance() {
if (instance == null) {
instance = newDatabaseConnection();
}
returninstance;
}
  • This method returns the single instance of the class.
  • If the instance is null (meaning it hasn't been created yet), it creates a new instance.
  • Subsequent calls to getInstance return the already created instance, ensuring there's only one instance.
publicvoidquery(Stringsql) {
System.out.println("Executing query: " + sql);
}
  • This method simulates executing a database query.
  • It prints out the SQL query string provided as an argument.
DatabaseConnectiondb1 = DatabaseConnection.getInstance();
DatabaseConnectiondb2 = DatabaseConnection.getInstance();
  • Both db1 and db2 are references to the same instance of DatabaseConnection.
  • The getInstance method ensures that the same instance is returned both times.
db1.query("SELECT * FROM users");
db2.query("INSERT INTO users VALUES (1, 'John')");
  • The query method is called on both db1 and db2.
  • Since db1 and db2 refer to the same instance, these calls operate on the same object.
System.out.println(db1 == db2);
  • This prints true because db1 and db2 are references to the same instance.

Output Explanation 🖥️🔍

When running the Main class, the final output will be:

DatabaseConnectionestablishedExecutingquery: SELECT * FROMusersExecutingquery: INSERTINTOusersVALUES (1, 'John')
true
  • Database Connection established is printed once when the instance is first created by DatabaseConnection.getInstance().
  • Executing query: SELECT * FROM users is printed after called the db1.query("SELECT * FROM users").
  • Executing query: INSERT INTO users VALUES (1, 'John') is printed when db2.query("INSERT INTO users VALUES (1, 'John')") is called.
  • true is printed when System.out.println(db1 == db2) is executed, confirming that both references point to the same instance.
Decorator Pattern Overview 🎨✨

Introduction 🌟🧩

The Decorator Pattern allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. This pattern is typically used to extend the functionalities of classes in a flexible and reusable way.

Scenario: Coffee Shop with Different Add-Ons ☕🍰

In a coffee shop, you can order a basic coffee and add various add-ons like milk or sugar. The Decorator pattern allows you to add behavior to objects dynamically.

Code Explanation 📜💻

Coffee Interface defines the structure that all coffee types (both base and decorated) must follow.

interfaceCoffee {
StringgetDescription();
doublegetCost();
}
  • The getDescription() method returns a description of the coffee.
  • It implemented by both base and decorator classes.
  • The getCost() method returns the cost of the coffee.
  • It also implemented by both base and decorator classes.

SimpleCoffee Class represents a basic coffee without any decorations.

classSimpleCoffeeimplementsCoffee {
@OverridepublicStringgetDescription() {
return"Simple Coffee";
}
@OverridepublicdoublegetCost() {
return5.0;
}
}
  • The getDescription() method returns Simple Coffee.
  • The getCost() Method returns a base cost of 5.0.

CoffeeDecorator Abstract Class implements the Coffee interface and serves as the base class for all coffee decorators.

abstractclassCoffeeDecoratorimplementsCoffee {
protectedCoffeedecoratedCoffee;
publicCoffeeDecorator(Coffeecoffee) {
this.decoratedCoffee = coffee;
}
publicStringgetDescription() {
returndecoratedCoffee.getDescription();
}
publicdoublegetCost() {
returndecoratedCoffee.getCost();
}
}
  • The decoratedCoffee field holds the reference to the coffee object being decorated.
  • The constructor initializes the decoratedCoffee with the given coffee.
  • The getDescription() method returns the description of the decorated coffee.
  • The getCost() method returns the cost of the decorated coffee.

MilkDecorator Class adds milk to the coffee, extending the CoffeeDecorator.

classMilkDecoratorextendsCoffeeDecorator {
publicMilkDecorator(Coffeecoffee) {
super(coffee);
}
@OverridepublicStringgetDescription() {
returndecoratedCoffee.getDescription() + " + Milk";
}
@OverridepublicdoublegetCost() {
returndecoratedCoffee.getCost() + 1.5;
}
}
  • The constructor passes the coffee to be decorated to the CoffeeDecorator constructor.
  • The getDescription() method appends + Milk to the existing description.
  • The getCost() method adds 1.5 to the existing cost.

SugarDecorator Class adds sugar to the coffee, extending the CoffeeDecorator.

classSugarDecoratorextendsCoffeeDecorator {
publicSugarDecorator(Coffeecoffee) {
super(coffee);
}
@OverridepublicStringgetDescription() {
returndecoratedCoffee.getDescription() + " + Sugar";
}
@OverridepublicdoublegetCost() {
returndecoratedCoffee.getCost() + 0.5;
}
}
  • The constructor passes the coffee to be decorated to the CoffeeDecorator constructor.
  • The getDescription() method appends + Sugar to the existing description.
  • The getCost() method adds 0.5 to the existing cost.

Main Class demonstrates the use of the decorators to add features to the base coffee.

publicclassMain {
publicstaticvoidmain(String[] args) {
Coffeecoffee = newSimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
coffee = newMilkDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
coffee = newSugarDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
}
}

Creating a Simple Coffee:

Coffeecoffee = newSimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
  • This creates a SimpleCoffee instance.
  • It prints the description and cost: Simple Coffee $5.0.

Adding Milk to Coffee:

coffee = newMilkDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
  • This decorates the coffee with MilkDecorator.
  • It prints the new description and cost: Simple Coffee + Milk $6.5.

Adding Sugar to Coffee:

coffee = newSugarDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
  • This further decorates the coffee with SugarDecorator.
  • It prints the new description and cost: Simple Coffee + Milk + Sugar $7.0.

Output Explanation 🖥️🔍

The Final Output generated as follows:

SimpleCoffee$5.0SimpleCoffee + Milk$6.5SimpleCoffee + Milk + Sugar$7.0

When the Main class is executed, it follows these steps to generate the output:

1. Creating a Simple Coffee:

Coffeecoffee = newSimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
  • A SimpleCoffee instance is created.
  • The getDescription() method of SimpleCoffee returns Simple Coffee.
  • The getCost() method of Simpl`eCoffee returns 5.0.
  • The output is: Simple Coffee $5.0.

2. Adding Milk to Coffee:

coffee = newMilkDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
  • The SimpleCoffee instance is decorated with MilkDecorator.
  • The getDescription() method of MilkDecorator calls the getDescription() method of the decorated coffee (which is SimpleCoffee) and appends + Milk.
  • The decoratedCoffee.getDescription() returns Simple Coffee.
  • Then the final description is Simple Coffee + Milk.
  • The getCost() method of MilkDecorator calls the getCost() method of the decorated coffee (which is SimpleCoffee) and adds 1.5.
  • In this case, the decoratedCoffee.getCost() returns 5.0.
  • After that, the final cost is 5.0 + 1.5 = 6.5.
  • Then the output is: Simple Coffee + Milk $6.5.

3. Adding Sugar to Coffee:

coffee = newSugarDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
  • The MilkDecorator instance (which already decorates SimpleCoffee) is further decorated with SugarDecorator.
  • The getDescription() method of SugarDecorator calls the getDescription() method of the decorated coffee (which is MilkDecorator) and appends + Sugar.
  • In here the decoratedCoffee.getDescription() (which is MilkDecorator.getDescription()) returns Simple Coffee + Milk.
  • So the final description is Simple Coffee + Milk + Sugar.
  • After that, the getCost() method of SugarDecorator calls the getCost() method of the decorated coffee (which is MilkDecorator) and adds 0.5.
  • In that case, the decoratedCoffee.getCost() (which is MilkDecorator.getCost()) returns 6.5.
  • So the, final cost is 6.5 + 0.5 = 7.0.
  • Finally, the output is: Simple Coffee + Milk + Sugar $7.0.
Observer Pattern Overview 👀📡

Introduction 🌟🧩

The Observer pattern allows you to define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Scenario: A Weather Station that Monitors Temperature 🌤️🌡️

Imagine a weather station that monitors temperature and updates multiple displays whenever the temperature changes.

Code Explanation 📜💻

WeatherSubject Interface defines the methods that any subject (in this case, a weather station) must implement to allow observers to register, unregister, and be notified of changes.

interfaceWeatherSubject {
voidregisterObserver(WeatherObserverobserver);
voidremoveObserver(WeatherObserverobserver);
voidnotifyObservers();
}
  • The registerObserver() method adds an observer to the list of observers.
  • The removeObserver() method removes an observer from the list of observers.
  • The notifyObservers() method notifies all registered observers of a change.

WeatherStation Class implements the WeatherSubject interface and maintains a list of observers. It also holds the temperature data and notifies observers when the temperature changes.

classWeatherStationimplementsWeatherSubject {
privateList<WeatherObserver> observers;
privatefloattemperature;
publicWeatherStation() {
this.observers = newArrayList<>();
}
@OverridepublicvoidregisterObserver(WeatherObserverobserver) {
observers.add(observer);
}
@OverridepublicvoidremoveObserver(WeatherObserverobserver) {
observers.remove(observer);
}
@OverridepublicvoidnotifyObservers() {
for (WeatherObserverobserver : observers) {
observer.update(temperature);
}
}
publicvoidsetTemperature(floattemperature) {
this.temperature = temperature;
notifyObservers();
}
}
  • The observers field is a list to keep track of registered observers.
  • The temperature field shows the current temperature.
  • The registerObserver() method adds an observer to the list.
  • The removeObserver() method removes an observer from the list.
  • The notifyObservers() method calls the update() method on each registered observer, passing the current temperature.
  • The setTemperature() method sets the temperature and calls notifyObservers()` method to update all observers.

WeatherObserver Interface defines the update() method that observers must implement to get updates from the subject.

interfaceWeatherObserver {
voidupdate(floattemperature);
}
  • The update() method takes the new temperature as an argument and updates the observer.

CurrentConditionsDisplay Class implements the WeatherObserver interface and displays the current temperature.

classCurrentConditionsDisplayimplementsWeatherObserver {
@Overridepublicvoidupdate(floattemperature) {
System.out.println("Current conditions: " + temperature + "F degrees");
}
}
  • The update() method prints the current temperature to the console.

StatisticsDisplay Class implements the WeatherObserver interface and maintains statistics about the temperature (average, maximum, and minimum).

classStatisticsDisplayimplementsWeatherObserver {
privatefloatmaxTemp = 0.0f;
privatefloatminTemp = 200;
privatefloattempSum = 0.0f;
privateintnumReadings;
@Overridepublicvoidupdate(floattemperature) {
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) {
maxTemp = temperature;
}
if (temperature < minTemp) {
minTemp = temperature;
}
display();
}
publicvoiddisplay() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
  • The following is how the fields are shown in this instance:
    • maxTemp: Stores the maximum recorded temperature.
    • minTemp: Stores the minimum recorded temperature.
    • tempSum: Stores the sum of all recorded temperatures.
    • numReadings: Counts the number of temperature readings.
  • The update() method updates the statistics with the new temperature and calls display.
  • The display() method prints the average, maximum, and minimum temperatures to the console.

Main Class demonstrates the Observer Pattern by creating a WeatherStation, registering observers, and changing the temperature.

publicclassMain {
publicstaticvoidmain(String[] args) {
WeatherStationweatherStation = newWeatherStation();
CurrentConditionsDisplaycurrentDisplay = newCurrentConditionsDisplay();
StatisticsDisplaystatisticsDisplay = newStatisticsDisplay();
weatherStation.registerObserver(currentDisplay);
weatherStation.registerObserver(statisticsDisplay);
weatherStation.setTemperature(80);
weatherStation.setTemperature(82);
weatherStation.setTemperature(78);
}
}
  • Creating the WeatherStation:

    • This creates an instance of WeatherStation.

      WeatherStationweatherStation = newWeatherStation();
  • Creating the Observers:

    • This creates instances of CurrentConditionsDisplay and StatisticsDisplay.

      CurrentConditionsDisplaycurrentDisplay = newCurrentConditionsDisplay();
      StatisticsDisplaystatisticsDisplay = newStatisticsDisplay();
  • Registering the Observers:

    • This registers the observers with the WeatherStation.

      weatherStation.registerObserver(currentDisplay);
      weatherStation.registerObserver(statisticsDisplay);
  • Setting the Temperature:

    • This changes the temperature, which triggers the notifyObservers() method to update all registered observers.

      weatherStation.setTemperature(80);
      weatherStation.setTemperature(82);
      weatherStation.setTemperature(78);

Output Explanation 🖥️🔍

The Final output is generated as follows:

Currentconditions: 80.0FdegreesAvg/Max/Mintemperature = 80.0/80.0/80.0Currentconditions: 82.0FdegreesAvg/Max/Mintemperature = 81.0/82.0/80.0Currentconditions: 78.0FdegreesAvg/Max/Mintemperature = 80.0/82.0/78.0

When the Main class is executed, it follows these steps to generate the output:

1. Setting the temperature to 80:

  • The CurrentConditionsDisplay prints: Current conditions: 80.0F degrees.
  • The StatisticsDisplay updates its statistics as:
    • Average: 801=80.0\frac{80}{1} = 80.0180=80.0
    • Maximum: 80.0
    • Minimum: 80.0
    • Prints: Avg/Max/Min temperature = 80.0/80.0/80.0

2. Setting the temperature to 82:

  • The CurrentConditionsDisplay prints: Current conditions: 82.0F degrees.
  • The StatisticsDisplay updates its statistics as:
    • Average: 80+822=81.0\frac{80 + 82}{2} = 81.0280+82=81.0
    • Maximum: 82.0
    • Minimum: 80.0
    • Prints: Avg/Max/Min temperature = 81.0/82.0/80.0

3. Setting the temperature to 78:

  • The CurrentConditionsDisplay prints: Current conditions: 78.0F degrees.
  • The StatisticsDisplay updates its statistics as:
    • Average: 80+82+783≈80.0\frac{80 + 82 + 78}{3} \approx 80.0380+82+78≈80.0
    • Maximum: 82.0
    • Minimum: 78.0
    • Prints: Avg/Max/Min temperature = 80.0/82.0/78.0
  1. Clone the repository:
    git clone https://github.com/MenathNDGD/Java-Design-Patterns-Examples.git
  2. Navigate to the desired pattern directory:
    cd"Singleton Pattern"# or "Decorator Pattern", "Observer Pattern"
  3. Compile and run the Main.java file:
    javac Main.java
    java Main

Contributions are welcome! Please fork this repository and submit a pull request for any enhancements or bug fixes.

This project is licensed under the MIT License. See the LICENSE file for details.

About

This repository contains Java implementations of key design patterns: Singleton, Decorator, and Observer. Each pattern is demonstrated through real-world examples, including a database connection manager, a customizable coffee shop, and a weather monitoring system. Ideal for learning OOP design principles.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages