← Назад к вопросам

Меняются ли типы данных при использовании Factory

1.7 Middle🔥 61 комментариев
#SOLID и паттерны проектирования#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Меняются ли типы данных при использовании Factory паттерна

Это глубокий вопрос, который касается полиморфизма, типизации и архитектурных паттернов в Java. Ответ: типы данных не меняются в смысле JVM, но логически мы работаем с разными типами через общий интерфейс.

Что происходит с типами в Factory

Важное различие:

  • Статический тип (compile-time) — то, что видит компилятор
  • Динамический тип (runtime) — реальный класс объекта в памяти
// Factory интерфейс
public interface DatabaseFactory {
    Database createDatabase();
}

// Конкретные реализации
public class MySQLDatabase implements Database {
    @Override
    public Connection getConnection() {
        // MySQL специфичная реализация
    }
}

public class PostgreSQLDatabase implements Database {
    @Override
    public Connection getConnection() {
        // PostgreSQL специфичная реализация
    }
}

// Factory реализация
public class DatabaseFactoryImpl implements DatabaseFactory {
    @Override
    public Database createDatabase(String dbType) {
        if ("mysql".equalsIgnoreCase(dbType)) {
            return new MySQLDatabase(); // Реальный тип: MySQLDatabase
        } else if ("postgresql".equalsIgnoreCase(dbType)) {
            return new PostgreSQLDatabase(); // Реальный тип: PostgreSQLDatabase
        }
        throw new IllegalArgumentException("Unknown database type");
    }
}

// Использование
DatabaseFactory factory = new DatabaseFactoryImpl();
Database db = factory.createDatabase("mysql");
// Статический тип (compile-time): Database
// Динамический тип (runtime): MySQLDatabase

Статический тип остаётся неизменным

Когда ты используешь Factory, переменная имеет статический тип интерфейса или базового класса:

public class OrderService {
    private final PaymentGateway paymentGateway;
    
    public OrderService(PaymentGateway paymentGateway) {
        // Статический тип переменной: PaymentGateway
        this.paymentGateway = paymentGateway;
    }
    
    public void processPayment(Order order) {
        // На compile-time компилятор знает, что это PaymentGateway
        // Он может вызвать только методы PaymentGateway
        paymentGateway.charge(order.getAmount());
        
        // Это вызовет ОШИБКУ компиляции:
        // paymentGateway.logToStripe(); // Stripe-специфичный метод
    }
}

// Factory создаёт разные реализации
public class PaymentGatewayFactory {
    public static PaymentGateway create(String provider) {
        if ("stripe".equalsIgnoreCase(provider)) {
            return new StripePaymentGateway(); // Динамический тип: StripePaymentGateway
        } else if ("paypal".equalsIgnoreCase(provider)) {
            return new PayPalPaymentGateway(); // Динамический тип: PayPalPaymentGateway
        }
        throw new IllegalArgumentException();
    }
}

// Но статический тип остаётся PaymentGateway
PaymentGateway gateway = PaymentGatewayFactory.create("stripe");
// gateway: статический тип = PaymentGateway, динамический тип = StripePaymentGateway

Runtime vs Compile-time

public interface Animal {
    void makeSound();
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
    
    // Метод, специфичный для Dog
    public void fetch() {
        System.out.println("Fetching the ball");
    }
}

public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if ("dog".equals(type)) {
            return new Dog(); // Динамический тип: Dog
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        // Статический тип: Animal
        Animal animal = AnimalFactory.createAnimal("dog");
        
        // Это работает (Animal имеет makeSound)
        animal.makeSound(); // Выведет: Woof!
        
        // Это НЕ работает на compile-time
        // animal.fetch(); // ОШИБКА КОМПИЛЯЦИИ!
        // Компилятор видит только интерфейс Animal
        
        // Но динамический тип знает fetch:
        if (animal instanceof Dog) {
            ((Dog) animal).fetch(); // Type casting требуется
        }
    }
}

Полиморфизм и диспетчеризация

Хотя статический тип не меняется, динамическая диспетчеризация вызывает правильный метод в runtime:

public interface PaymentProcessor {
    void process(BigDecimal amount);
}

public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void process(BigDecimal amount) {
        System.out.println("Processing credit card payment: " + amount);
    }
}

public class CryptoProcessor implements PaymentProcessor {
    @Override
    public void process(BigDecimal amount) {
        System.out.println("Processing crypto payment: " + amount);
    }
}

public class PaymentFactory {
    public static PaymentProcessor getProcessor(String type) {
        return switch(type) {
            case "credit_card" -> new CreditCardProcessor();
            case "crypto" -> new CryptoProcessor();
            default -> throw new IllegalArgumentException();
        };
    }
}

public class CheckoutService {
    public void checkout(String paymentType, BigDecimal amount) {
        PaymentProcessor processor = PaymentFactory.getProcessor(paymentType);
        // Статический тип: PaymentProcessor
        // Динамический тип: CreditCardProcessor или CryptoProcessor
        
        processor.process(amount);
        // JVM будет вызывать правильный метод в зависимости от динамического типа
    }
}

// Использование
checkout("credit_card", BigDecimal.valueOf(100));
// Вызовет: CreditCardProcessor.process()

checkout("crypto", BigDecimal.valueOf(100));
// Вызовет: CryptoProcessor.process()

Отражение (Reflection) и проверка типов

Ты можешь проверить реальный тип в runtime:

public class TypeCheckingExample {
    public static void main(String[] args) {
        DatabaseFactory factory = new DatabaseFactoryImpl();
        Database db = factory.createDatabase("mysql");
        
        // Проверяем реальный тип
        System.out.println(db.getClass()); // class com.example.MySQLDatabase
        System.out.println(db.getClass().getSimpleName()); // MySQLDatabase
        
        // instanceof проверка
        if (db instanceof MySQLDatabase) {
            System.out.println("It's MySQL!");
            MySQLDatabase mysqlDb = (MySQLDatabase) db;
            // Теперь можешь использовать MySQL-специфичные методы
        }
    }
}

Generics и Factory

Типы остаются постоянными и при работе с generics:

public interface Repository<T> {
    void save(T entity);
    T findById(Long id);
}

public class UserRepository implements Repository<User> {
    @Override
    public void save(User entity) {
        // User-специфичная сохранение
    }
    
    @Override
    public User findById(Long id) {
        // User-специфичный поиск
    }
}

public class RepositoryFactory {
    @SuppressWarnings("unchecked")
    public static <T> Repository<T> createRepository(Class<T> entityClass) {
        if (entityClass == User.class) {
            return (Repository<T>) new UserRepository();
        } else if (entityClass == Product.class) {
            return (Repository<T>) new ProductRepository();
        }
        throw new IllegalArgumentException();
    }
}

// Использование
Repository<User> userRepo = RepositoryFactory.createRepository(User.class);
Repository<Product> productRepo = RepositoryFactory.createRepository(Product.class);

// Статические типы: Repository<User> и Repository<Product>
// Динамические типы: UserRepository и ProductRepository
// Type erasure стирает информацию о <User> и <Product> в runtime

Abstract Factory с разными типами

public interface DatabaseFactory {
    Connection getConnection();
    Dialect getDialect();
}

public class MySQLDatabaseFactory implements DatabaseFactory {
    @Override
    public Connection getConnection() {
        return new MySQLConnection();
    }
    
    @Override
    public Dialect getDialect() {
        return new MySQLDialect(); // Динамический тип: MySQLDialect
    }
}

public class PostgreSQLDatabaseFactory implements DatabaseFactory {
    @Override
    public Connection getConnection() {
        return new PostgreSQLConnection();
    }
    
    @Override
    public Dialect getDialect() {
        return new PostgreSQLDialect(); // Динамический тип: PostgreSQLDialect
    }
}

// Использование
DatabaseFactory factory = selectFactory(dbType);

// Статический тип возвращаемого значения: Dialect
// Динамический тип: MySQLDialect или PostgreSQLDialect
Dialect dialect = factory.getDialect();

// Полиморфизм вызывает правильный SQL
String sqlQuery = dialect.generateLimitClause(10);
// MySQL: "LIMIT 10"
// PostgreSQL: "LIMIT 10"

Ключевые моменты

  1. Статический тип (Compile-time): Определяется тем, что ты объявил в коде. Не меняется.
PaymentGateway gateway = factory.create(); // Статический тип: PaymentGateway
  1. Динамический тип (Runtime): Зависит от того, что реально создала factory. Это может быть одна из нескольких реализаций.

  2. Полиморфизм: JVM вызывает правильный метод на основе динамического типа (virtual method dispatch).

  3. Безопасность типов: Компилятор проверяет, что ты вызываешь только методы, определённые в статическом типе.

  4. Гибкость: Factory позволяет менять реальные объекты без изменения кода, который их использует.

Выводы

При использовании Factory паттерна:

  • Статический тип переменной остаётся интерфейсом/базовым классом
  • Динамический тип может быть одной из многих конкретных реализаций
  • Полиморфизм обеспечивает вызов нужного метода в runtime
  • Type safety проверяется на compile-time через интерфейс
  • Это позволяет менять реализации, не меняя код, который их использует

Так что типы данных логически остаются абстрактными/интерфейсными, но JVM знает реальные типы объектов и вызывает нужные методы.