Меняются ли типы данных при использовании Factory
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Меняются ли типы данных при использовании 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"
Ключевые моменты
- Статический тип (Compile-time): Определяется тем, что ты объявил в коде. Не меняется.
PaymentGateway gateway = factory.create(); // Статический тип: PaymentGateway
-
Динамический тип (Runtime): Зависит от того, что реально создала factory. Это может быть одна из нескольких реализаций.
-
Полиморфизм: JVM вызывает правильный метод на основе динамического типа (virtual method dispatch).
-
Безопасность типов: Компилятор проверяет, что ты вызываешь только методы, определённые в статическом типе.
-
Гибкость: Factory позволяет менять реальные объекты без изменения кода, который их использует.
Выводы
При использовании Factory паттерна:
- Статический тип переменной остаётся интерфейсом/базовым классом
- Динамический тип может быть одной из многих конкретных реализаций
- Полиморфизм обеспечивает вызов нужного метода в runtime
- Type safety проверяется на compile-time через интерфейс
- Это позволяет менять реализации, не меняя код, который их использует
Так что типы данных логически остаются абстрактными/интерфейсными, но JVM знает реальные типы объектов и вызывает нужные методы.