Sitemap
Aditi Tripathi

Aditi Tripathi

Software Enthusiast, AWS

All about Java — Part1(a)

5 min readJun 11, 2023

--

Hi There! These posts will be slightly unconventional. This is in the sense of making the most popular industry conventions around any language more apparent from the get-go.

Starting it off is Java Factory classes and Java Dependency Injections (DI) paradigm, both widely used in the Java industry design. This post highlights when to use one over/along with the other, coupled with the functional style of Java coding.

Press enter or click to view image in full size
Source: Dall-E 2 Red cartoon of a funny squid alongside a Factory, digital art

In Java, there are two ways to manage how objects are created and used in a program. Think of objects as things you can create and use in your code, like a car or a phone.

The first way is by using a Factory class. Imagine you have a special machine that can make different types of objects. This machine is the Factory class. It knows how to create objects based on certain rules or criteria. For example, if you tell the machine you want a car, it will create a car for you. The Factory class makes it easier for you to get the objects you need without worrying about how they are made.

The second way is called Dependency Injection. This is like having a friend who gives you the things you need when you need them. In this case, objects often need other objects to work properly. For example, a car needs an engine to run. With Dependency Injection, instead of the objects creating what they need themselves, they ask someone else to give them what they need. This helps keep things organized and makes it easier to change or replace things later on.

So, to summarize, a Factory class is like a machine that creates objects for you based on certain rules, while Dependency Injection is like having a helpful friend who gives you the things you need when you need them. Both ways help with managing objects and making your code easier to work with.

Let’s explore each one in more detail:

  1. Factory Class: A Factory class is a design pattern that provides an interface for creating objects without exposing the underlying logic of object instantiation. It encapsulates the creation process and allows clients to create objects of a certain type without knowing the specific implementation details.

Use cases of a Factory class:

  • Complex object creation: When creating an object involves multiple steps, such as initialization, configuration, or dependency injection, a Factory class can simplify the process.
  • Object customization: A Factory class can allow clients to customize object creation by providing different options or parameters.
  • Object pooling: In some cases, a Factory class can manage a pool of pre-created objects and provide them on demand, improving performance.

We will cover this with 2 concrete examples — one around object creation/customization and another for object pooling.

Here’s an example that demonstrates the use of a factory class for creating instances of different database connectors:

In this example, we have an interface DatabaseConnector that defines the contract for connecting to a database, executing queries, and disconnecting. We then have two concrete implementations MySQLConnector and OracleConnector that provide the specific logic for connecting, executing queries, and disconnecting from MySQL and Oracle databases, respectively.

Get Aditi Tripathi’s stories in your inbox

Join Medium for free to get updates from this writer.

The DatabaseConnectorFactory class acts as a factory class and provides a static method createDatabaseConnector() that takes a database type as input and returns an instance of the appropriate database connector based on the provided type.

public interface DatabaseConnector {
void connect();
void executeQuery(String query);
void disconnect();
}

public class MySQLConnector implements DatabaseConnector {
@Override
public void connect() {
// Connect to MySQL database
System.out.println("Connected to MySQL database");
}

@Override
public void executeQuery(String query) {
// Execute query in MySQL database
System.out.println("Executing query in MySQL database: " + query);
}

@Override
public void disconnect() {
// Disconnect from MySQL database
System.out.println("Disconnected from MySQL database");
}
}

public class OracleConnector implements DatabaseConnector {
@Override
public void connect() {
// Connect to Oracle database
System.out.println("Connected to Oracle database");
}

@Override
public void executeQuery(String query) {
// Execute query in Oracle database
System.out.println("Executing query in Oracle database: " + query);
}

@Override
public void disconnect() {
// Disconnect from Oracle database
System.out.println("Disconnected from Oracle database");
}
}

public class DatabaseConnectorFactory {
public static DatabaseConnector createDatabaseConnector(String databaseType) {
if (databaseType.equalsIgnoreCase("mysql")) {
return new MySQLConnector();
} else if (databaseType.equalsIgnoreCase("oracle")) {
return new OracleConnector();
}

throw new IllegalArgumentException("Invalid database type: " + databaseType);
}
}


public class Main {
public static void main(String[] args) {
DatabaseConnector mysqlConnector = DatabaseConnectorFactory.createDatabaseConnector("mysql");
mysqlConnector.connect();
mysqlConnector.executeQuery("SELECT * FROM customers");
mysqlConnector.disconnect();

DatabaseConnector oracleConnector = DatabaseConnectorFactory.createDatabaseConnector("oracle");
oracleConnector.connect();
oracleConnector.executeQuery("SELECT * FROM employees");
oracleConnector.disconnect();
}
}

In this example, we create a MySQL connector using DatabaseConnectorFactory.createDatabaseConnector("mysql") and an Oracle connector using DatabaseConnectorFactory.createDatabaseConnector("oracle"). We can then use these connectors to connect to the respective databases, execute queries, and disconnect.

Here’s another concrete example of object pooling using a factory class in Java:

In this example, we have a Connection class representing a connection to a database. The Connection class has methods for executing queries and closing the connection. The inUse boolean variable indicates whether the connection is currently being used or not.

The ConnectionFactory class acts as a factory class for creating and managing a pool of Connection objects. It initializes the connection pool with a maximum pool size and provides methods for getting a connection from the pool (getConnection()) and releasing a connection back to the pool (releaseConnection()).

import java.util.ArrayList;
import java.util.List;

public class Connection {
private boolean inUse;

public Connection() {
System.out.println("Establishing a new connection");
}

public void executeQuery(String query) {
System.out.println("Executing query: " + query);
}

public void close() {
System.out.println("Closing connection");
}

public boolean isInUse() {
return inUse;
}

public void setInUse(boolean inUse) {
this.inUse = inUse;
}
}

public class ConnectionFactory {
private static final int MAX_POOL_SIZE = 10;
private static List<Connection> connectionPool = new ArrayList<>(MAX_POOL_SIZE);

static {
for (int i = 0; i < MAX_POOL_SIZE; i++) {
connectionPool.add(new Connection());
}
}

public static Connection getConnection() {
return connectionPool.stream()
.filter(connection -> !connection.isInUse())
.peek(connection -> connection.setInUse(true))
.findFirst()
.orElseThrow(() -> new RuntimeException("No available connections in the pool"));
}

public static void releaseConnection(Connection connection) {
connection.setInUse(false);
}
}

public class Main {
public static void main(String[] args) {
Connection connection1 = ConnectionFactory.getConnection();
connection1.executeQuery("SELECT * FROM customers");
ConnectionFactory.releaseConnection(connection1);

Connection connection2 = ConnectionFactory.getConnection();
connection2.executeQuery("SELECT * FROM orders");
ConnectionFactory.releaseConnection(connection2);
}
}

The main() method of the Main class, demonstrates how to use the object pool. We acquire a connection using ConnectionFactory.getConnection(), execute a query, and then release the connection back to the pool using ConnectionFactory.releaseConnection(). Maybe also note the functional programming style being used with lambda expression and stream operations in the getConnection() method to filter and find the first available connection from the pool.

By using object pooling, we can reuse connections instead of creating new connections every time we need one. This can improve performance and resource utilization, especially in scenarios where establishing connections is expensive.

Note: This example demonstrates the concept of object pooling but does not handle concurrency or thread safety. In a production environment, you would need to consider thread safety and handle concurrent access to the connection pool appropriately.

Part-1b will cover the Dependency Injection paradigm and how they can be leveraged alongside Factory classes in real world use-cases.

--

--

Aditi Tripathi
Aditi Tripathi
0 followers

Software Enthusiast, AWS