Thursday, September 4, 2025

Preguntas de entrevista Java - Explica IoC y como se relaciona con Dependency Injection

Preguntas de entrevista Java:

Preguntas de entrevista Java:

⁉️ Explica “Inversión de Control” (IoC) y cómo se relaciona con “inyección de dependencias”.

⬇️

La inversión de control es un principio, y la inyección de dependencias es un patrón que aplica IoC.

La Inversión de Control (IoC) es un principio de diseño de software en el cual, en lugar de que tu código llame o ejecute directamente cierto bloque de código, se invierte la responsabilidad y ahora se delega a una fuente externa (biblioteca, framework, clase extendida, etc.) la responsabilidad de ejecutar ese bloque.

En el caso del framework Spring, IoC tiene mucha relación con la inyección de dependencias porque es a Spring a quien le delegamos la responsabilidad de crear e inyectar las dependencias entre objetos (beans en el lenguaje de Spring) en lugar de que el programador directamente instancie y asigne esas dependencias.

IoC es fundamental para lograr el desacople de componentes de software. Al invertir el control, ayudamos a que nuestros componentes sean más flexibles, removiendo dependencias directas.

Sin embargo, Dependency Injection no es el único patrón donde el principio de inversión de control se puede aplicar.

Algunos otros patrones donde aplica son:

👀 Patrón Observer

En el patrón Observer, cuando nosotros especificamos el método/lambda que tiene que ser llamado cuando cierto evento sea identificado por el “observador”, es ahí cuando el principio de inversión de control sucede.

🔲 Patrón Template Method

En este patrón generalmente tenemos una clase abstracta con un método que implementa parcialmente algún algoritmo y que en cierto punto llama a un método abstracto que sirve de “plantilla” para variar el algoritmo. Aquí el principio de inversión de control sucede al extender la clase y definir de manera concreta una implementación de ese método “plantilla”, ya que en la ejecución de algún flujo donde se estén usando instancias derivadas de la clase abstracta original, en cierto momento ese flujo llamará la definición concreta del método “plantilla”.

Y así hay otros patrones, técnicas e incluso arquitecturas de software donde la inversión de control sucede.

Es más, en Java, cuando usamos Streams y definimos ciertos lambdas que ayudan a filtrar y/o mapear alguna colección, ahí también podemos decir que hay cierta inversión de control, ya que nosotros no ejecutamos ese bloque de código directamente, se lo delegamos a un tercero.

¿Qué opinan? ¿Qué otros ejemplos de inversión de control se les vienen a la mente?

Wednesday, September 3, 2025

Preguntas de entrevista Java - Tipos de relación entre clases

Preguntas de entrevista Java:

Preguntas de entrevista Java:

📎 ¿Explica los tipos de relaciones que hay entre clases?

Las clases no pueden trabajar de manera aislada, deben trabajar en conjunto para implementar una funcionalidad.

🤝 Asociación (Association).

Indica que una propiedad de una clase (digamos clase A) mantiene una referencia a la instancia de otra clase (digamos clase B). Al existir esta relación, la clase A puede hacer uso de la clase B llamando a los métodos públicos de esta y así trabajar en conjunto para implementar algún requerimiento.

Puede ser de los siguientes tipos:
* Unidireccional: La clase A puede ver y usar la clase B, pero la clase B no sabe nada de la clase A.
* Bidireccional: Tanto la clase A como la clase B se conocen entre ellas, es decir, mantienen una propiedad haciendo referencia la una a la otra.

➕ Agregación (Aggregation)

Esta relación es una especialización de asociación. En una agregación, las clases forman conceptualmente un “todo y sus partes”.

Usaré una analogía para tratar de explicar este concepto. Imaginen que están creando un sistema para una agencia de autos, específicamente el módulo de venta de refacciones; entonces un “todo y sus partes” podría ser el carro y sus diferentes refacciones. La característica importante en una relación de agregación es que las “partes” pueden ser separadas del “todo” y tener un ciclo de vida separado. Ejemplo: En el módulo de refacciones del sistema, una parte (o refacción) de un carro puede colocarse en otro carro si estas son compatibles.

⛓️‍💥Composición (Composition)

Esta relación es otra especialización de asociación. En una composición, las clases forman, de manera similar a la agregación, un “todo y sus partes”, pero en este caso las partes no pueden ser separadas del todo; viven y tienen sentido solo si están en unidad.

Usando de nuevo la analogía del sistema de la agencia de autos, pero en este caso el módulo de ventas de autos, un “todo y sus partes” podría ser el carro y el motor, faros, llantas, etc., pero aquí, cuando un carro se vende, se vende con todo y sus partes.

🐔 🐥 Herencia (Inheritance)

Esta relación no es una asociación, es más bien una relación de herencia “padre-hij@” (is-a). Esta relación entre clases se da cuando en Java una clase A “extiende” una clase B.

📄 Implementación / realización (Implementation / Realization)

Similar a la herencia, esta relación no es una asociación como tal. Esta relación indica que una clase implementa un contrato definido en una interfaz; esto en Java se traduce en que una clase A implementa una interfaz B.

👉 Dependencia (Dependency)

El nombre de esta relación entre clases puede ser confuso; al final del día, todas las relaciones de las que hablamos forman dependencia entre clases, pero en este caso la relación de dependencia es más temporal; es decir, una clase A podría instanciar y usar una clase B en alguno de sus métodos.

¿Qué opinan?

Tuesday, August 19, 2025

Encapsulation in Java: Writing secure and maintainable code

# Encapsulation in Java: Writing secure and maintainable code

Encapsulation is one of the pillars of Object-Oriented Programming. It’s the foundation that makes your code secure, maintainable, and robust. But what exactly is encapsulation, and why should you care about it as a Software Developer?

What is Encapsulation?

Encapsulation is the practice of bundling data (attributes) and the methods (behavior) that operate on that data into a single unit (in this case, an object), while restricting direct access to the internal components. It is like a protective shield around your data that controls how it can be accessed and modified.

The key is: Hide the internal state and require all interactions to happen through well-defined interface.

Without encapsulation, your program becomes vulnerable to several issues:

  • Data corruption: Any part of your code could modify critical data inappropriately, and with this I don’t only mean hackers but even our fellow team members because the poor designed interface of the component
  • Inconsistent state: Objects might end up in invalid states that could cause horrendous errors
  • Debugging nightmares: When data can be modified anywhere, tracking down bugs becomes unbearable

A Banking Example

Let’s use a very simple use case, imagine we have to write software to handle bank accounts (yes! I know, yet another banking example 😂). Requirements are:

  • Bank Accounts must have an owner and a balance
  • Bank Accounts must maintain a history of all transactions performed on them
  • Users can deposit funds into Bank Accounts
  • Users can withdraw funds from Bank Accounts

Let’s see encapsulation at work with this simple, practical example:

Poor Encapsulation (Don’t do this, please!)

public class BadBankAccount {
    public String owner;
    public double balance;
    public List<String> transactions;

    public BadBankAccount(String owner, double initialBalance) {
        this.owner = owner;
        this.balance = initialBalance;
        this.transactions = new ArrayList<>();
    }
}

// Let's create an account, no issues here
BadBankAccount account = new BadBankAccount("John Doe", 1000.0);
// Oops! Negative balance allowed
account.balance = -500.0;
// Transaction history is lost!
account.transactions.clear();
// Invalid state
account.owner = null;

As you can see, this approach is problematic, because:

  • All internals are publicly accessible, which is not good
  • No control over what values can be set
  • No protection or access control to internal data, important information can be deleted!
  • No validation or business logic is enforced
  • No clear indication of how to use this component

With Encapsulation (not perfect, but better)

public class BankAccount {
    // Private fields - hidden from outside access
    private String owner;
    private double balance;
    private List<String> transactions;
    private static final double MINIMUM_BALANCE = 0.0;

    // Constructor with validation (This can be better implemented as a factory method)
    public BankAccount(String owner, double initialBalance) {
        if (owner == null || owner.trim().isEmpty()) {
            throw new IllegalArgumentException("Owner name cannot be null or empty");
        }
        if (initialBalance < MINIMUM_BALANCE) {
            throw new IllegalArgumentException("Initial balance cannot be negative");
        }

        this.owner = owner;
        this.balance = initialBalance;
        this.transactions = new ArrayList<>();
        addTransaction("Account opened with balance: $" + initialBalance);
    }

    // Controlled access to balance (read-only)
    public double getBalance() {
        return balance;
    }

    // Controlled access to owner (read-only)
    public String getOwner() {
        return owner;
    }

    // Safe way to view transactions (returns unmodifiable copy)
    public List<String> getTransactionHistory() {
        return Collections.unmodifiableList(transactions); 
    }

    // Business logic for deposits
    public boolean deposit(double amount) {
        if (amount <= 0) {
            System.out.println("Deposit amount must be positive");
            return false;
        }

        balance += amount;
        addTransaction("Deposited: $" + amount + " | New balance: $" + balance);
        return true;
    }

    // Business logic for withdrawals
    public boolean withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("Withdrawal amount must be positive");
            return false;
        }

        if (balance - amount < MINIMUM_BALANCE) {
            System.out.println("Insufficient funds. Current balance: $" + balance);
            return false;
        }

        balance -= amount;
        addTransaction("Withdrew: $" + amount + " | New balance: $" + balance);
        return true;
    }

    // Private helper method - internal implementation detail
    private void addTransaction(String transaction) {
        String timestamp = java.time.LocalDateTime.now().toString();
        transactions.add(timestamp + " - " + transaction);
    }

    // Utility method for account summary
    public void printAccountSummary() {
        String str = String.format(
            "{ owner: %s, balance: %.2f, totalTransactions: %d }",
            owner, balance, transactions.size()
        );
        System.out.println(str);
    }
}

Using the Encapsulated Class

public class BankingDemo {
    public static void main(String[] args) {
        // Create account safely
        BankAccount account = new BankAccount("Alice Johnson", 1000.0);

        // All interactions go through controlled methods
        account.deposit(250.0);    
        account.withdraw(100.0);   
        account.withdraw(2000.0);  

        // Data access is safe and controlled
        System.out.println("Current balance: $" + account.getBalance());
        System.out.println("Account owner: " + account.getOwner());

        // Transaction history is safely accessible
        List<String> history = account.getTransactionHistory();
        System.out.println("\nTransaction History:");
        for (String transaction : history) {
            System.out.println(transaction);
        }
        // This will throw an exception, you cannot modify the list
        // history.clear();

        account.printAccountSummary();
    }
}

Achieving Encapsulation with Java

1. Access Modifiers

The first step to achieve encapsulation is to use correctly the access modifiers.

  • private: Only accessible within the same class
  • protected: Accessible within the same package and subclasses
  • public: Accessible from anywhere
  • Package-private (no modifier): Accessible within the same package

2. Getter Methods

Provide controlled read access to private data:

public String getName() {
    return name;
}

// For collections, return copies (and unmodifiable) to prevent external modification
public List<String> getItems() {
    return new ArrayList<>(items);
    // or
    return Collections.unmodifiableList(items); 
}

3. Setter Methods with Validation

Provide controlled write access with business logic:

public void setAge(int age) {
    if (age < 0 || age > 150) { // business rule
        throw new IllegalArgumentException("Age must be between 0 and 150");
    }
    this.age = age;
}

4. Immutable Objects

For some cases, make objects unchangeable after creation:

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    // No setters - object cannot be changed after creation
}

Best Practices for Encapsulation

  1. Always start by declaring everything private: Classes, fields, methods, always start by making them private, then as needed, make them protected or even public.
  2. Provide public methods only when needed: Don’t create getters/setters automatically
  3. Validate inputs: Always check parameters in public methods
  4. Return copies of mutable internal data or make it immutable: Prevent external modification of internal data
  5. Use meaningful method names: Methods should describe business operations, not just data access
  6. Keep internal logic private: Helper methods should be private, they only have meaning inside the class.

Conclusion

If you need a frontline defender against bugs, data corruption, and maintenance headaches, encapsulation should be part of your go-to strategy. The investment in designing good encapsulation pays dividends throughout the lifetime of your application.

There is a quote I read somewhere and after some research I found who said it:

“Make it easy to do right, and hard to go wrong” - Gretchen Rubin

Design your components with that quote in mind: Design your components to do the “right thing” easy and to do the “wrong thing” hard. Encapsulation is your friend here.