Supongamos que se requiere consrtuir una aplicación que pueda cifrar y descifrar datos empleando DES o IDEA. En lugar de crear directamente instancias de DesEngine o IdeaEngine en el código cliente, se define una interfaz común y una familia de fábricas especializadas.
Diseño de la arquitectura Java
La interfaz CryptoEngine representa el producto abstracto. Cada algoritmo concreto la implementa y provee su propia lógica de cifrado. La interfaz CryptoFactory actúa como creador abstracto y declara el método de fabricación.
public interface CryptoEngine {
byte[] transform(byte[] input, byte[] key);
}
public interface CryptoFactory {
CryptoEngine createEngine();
}
Implementación de los algoritmos
La clase DesEngine utiliza el proveedor estándar de Java para realizar el cifrado y descifrado con Cipher. Se simplifica la gestión de claves generando automáticamente un secreto de 112 bits para el modo DESede.
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class DesEngine implements CryptoEngine {
private static final String ALGORITHM = "DESede";
@Override
public byte[] transform(byte[] input, byte[] key) {
try {
KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);
generator.init(112);
SecretKey secretKey = generator.generateKey();
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypted = cipher.doFinal(input);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
} catch (Exception e) {
throw new RuntimeException("Error en operación DES", e);
}
}
}
La clase IdeaEngine aprovecha Bouncy Castle para el soporte de IDEA y gestiona la clave de 128 bits requerida por este algoritmo.
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.Security;
public class IdeaEngine implements CryptoEngine {
private static final String KEY_ALGORITHM = "IDEA";
private static final String CIPHER_ALGORITHM = "IDEA/ECB/ISO10126Padding";
public IdeaEngine() {
Security.addProvider(new BouncyCastleProvider());
}
@Override
public byte[] transform(byte[] input, byte[] key) {
try {
byte[] rawKey = initKey();
Key k = new SecretKeySpec(rawKey, KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, k);
byte[] encrypted = cipher.doFinal(input);
cipher.init(Cipher.DECRYPT_MODE, k);
return cipher.doFinal(encrypted);
} catch (Exception e) {
throw new RuntimeException("Error en operación IDEA", e);
}
}
private byte[] initKey() throws Exception {
KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGORITHM);
generator.init(128);
return generator.generateKey().getEncoded();
}
}
Fábricas concretas
Cada fábrica se encarga únicamente de instanciar el motor que le corresponde. De este modo, el cliente solo interactúa con la abstracción.
public class DesFactory implements CryptoFactory {
@Override
public CryptoEngine createEngine() {
System.out.println("Creando motor DES");
return new DesEngine();
}
}
public class IdeaFactory implements CryptoFactory {
@Override
public CryptoEngine createEngine() {
System.out.println("Creando motor IDEA");
return new IdeaEngine();
}
}
Código cliente
El cliente selecciona el algoritmo y la fábrica correspondiente. No existe acoplamiento directo con DesEngine ni IdeaEngine.
import java.util.Scanner;
public class CryptoClient {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Seleccione algoritmo: 1. DES 2. IDEA 3. Salir");
int option = scanner.hasNextInt() ? scanner.nextInt() : -1;
CryptoFactory factory = resolveFactory(option);
if (factory == null) {
System.out.println("Opción no válida");
return;
}
CryptoEngine engine = factory.createEngine();
byte[] result = engine.transform("Texto de prueba".getBytes(), null);
System.out.println("Resultado: " + new String(result));
}
private static CryptoFactory resolveFactory(int option) {
return switch (option) {
case 1 -> new DesFactory();
case 2 -> new IdeaFactory();
default -> null;
};
}
}
Equivalente en C++
En C++ se puede modelar el mismo patrón mediante una jerarquía de clases con métodos virtuales. El ejemplo siguiente conserva la idea central del Factory Method y utiliza std::unique_ptr para gestionar la memoria.
#include <iostream>
#include <memory>
#include <string>
class CryptoEngine {
public:
virtual ~CryptoEngine() = default;
virtual std::string process(const std::string& text) = 0;
};
class DesCrypto : public CryptoEngine {
public:
std::string process(const std::string& text) override {
// Lógica de cifrado/descifrado DES
return "DES(" + text + ")";
}
};
class IdeaCrypto : public CryptoEngine {
public:
std::string process(const std::string& text) override {
// Lógica de cifrado/descifrado IDEA
return "IDEA(" + text + ")";
}
};
class CryptoFactory {
public:
virtual ~CryptoFactory() = default;
virtual std::unique_ptr<CryptoEngine> buildEngine() = 0;
};
class DesFactory : public CryptoFactory {
public:
std::unique_ptr<CryptoEngine> buildEngine() override {
return std::make_unique<DesCrypto>();
}
};
class IdeaFactory : public CryptoFactory {
public:
std::unique_ptr<CryptoEngine> buildEngine() override {
return std::make_unique<IdeaCrypto>();
}
};
int main() {
std::unique_ptr<CryptoFactory> factory = std::make_unique<DesFactory>();
std::unique_ptr<CryptoEngine> engine = factory->buildEngine();
std::cout << engine->process("mensaje secreto") << std::endl;
return 0;
}
Con esta organización, agregar un nuevo algoritmo de cifrado solo requiere implementar una nueva clase de motor y su fábrica asociada, sin modificar el código que consume el servicio.