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

Как скрыть внутреннюю структуру

2.0 Middle🔥 151 комментариев
#Другое

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

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

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

Как скрыть внутреннюю структуру

Инкапсуляция — это один из основных принципов OOP. Скрывание внутренней структуры защищает от неправильного использования и позволяет менять реализацию без влияния на внешний код.

Модификаторы доступа (Access Modifiers)

// public — видно отовсюду
public class PublicClass {
    public void publicMethod() { }
}

// default (package-private) — видно только в пакете
class PackagePrivateClass {
    void packagePrivateMethod() { }
}

// protected — видно в пакете и наследникам
class Parent {
    protected void protectedMethod() { }
}

// private — видно только в этом классе
class PrivateClass {
    private void privateMethod() { }
}

Пример: скрытие деталей реализации

// ПЛОХО — внутренняя структура видна
public class BankAccount {
    public List<Transaction> transactions = new ArrayList<>();
    public double balance = 0;
    
    // Кто-то может напрямую менять баланс!
    // account.balance = -1000; // Ошибка, но скомпилируется
}

// ХОРОШО — инкапсуляция
public class BankAccount {
    private List<Transaction> transactions = new ArrayList<>();
    private double balance = 0;
    
    public double getBalance() {
        return balance;
    }
    
    public void withdraw(double amount) {
        if (amount > balance) {
            throw new IllegalArgumentException("Insufficient funds");
        }
        balance -= amount;
        transactions.add(new Transaction("WITHDRAW", amount));
    }
    
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        balance += amount;
        transactions.add(new Transaction("DEPOSIT", amount));
    }
    
    public List<Transaction> getTransactionHistory() {
        // Возвращаем копию, чтобы избежать внешних изменений
        return new ArrayList<>(transactions);
    }
}

Принцип: возвращай неизменяемые копии

public class User {
    private List<String> roles = new ArrayList<>();
    private Map<String, String> metadata = new HashMap<>();
    
    // ПЛОХО — вернули ссылку на внутреннее состояние
    public List<String> getRoles() {
        return roles; // Кто-то может сделать roles.clear()!
    }
    
    // ХОРОШО — вернули неизменяемый список
    public List<String> getRoles() {
        return Collections.unmodifiableList(roles);
    }
    
    // ХОРОШО — вернули копию
    public List<String> getRolesAsCopy() {
        return new ArrayList<>(roles);
    }
    
    // ХОРОШО для больших данных — используй Stream
    public List<String> getRolesStream() {
        return roles.stream()
            .collect(Collectors.toUnmodifiableList());
    }
    
    public Map<String, String> getMetadata() {
        return Collections.unmodifiableMap(metadata);
    }
}

Скрытие сложности через Facade паттерн

// Сложная структура — прячем детали
public class DatabaseConnection {
    private Connection connection;
    private Statement statement;
    private ResultSet resultSet;
    
    // Детали скрыты
}

public class PaymentProcessor {
    private DatabaseConnection db;
    private PaymentGateway gateway;
    private EmailService email;
    
    // Детали скрыты
}

// Фасад — скрывает всю сложность
public class CheckoutFacade {
    private PaymentProcessor paymentProcessor;
    private OrderService orderService;
    
    // Простой публичный метод скрывает всю сложность внутри
    public void checkout(Order order, PaymentInfo payment) {
        paymentProcessor.processPayment(order.getId(), payment);
        orderService.confirmOrder(order);
    }
}

// Клиент работает с простым фасадом
checkoutFacade.checkout(order, paymentInfo);

Data Transfer Object (DTO) для скрытия сущности

// Сущность БД — скрыта от клиента
@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    private Long id;
    private String name;
    private String email;
    private String passwordHash;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // много других деталей реализации
}

// DTO — только нужные данные
public class UserDTO {
    private Long id;
    private String name;
    private String email;
    
    // НЕ ВКЛЮЧАЕМ passwordHash, timestamps, etc.
}

// Контроллер возвращает DTO, не сущность
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserRepository userRepository;
    
    @GetMapping("/{id}")
    public UserDTO getUser(@PathVariable Long id) {
        UserEntity entity = userRepository.findById(id).orElseThrow();
        
        // Преобразуем в DTO, скрывая детали
        return new UserDTO(
            entity.getId(),
            entity.getName(),
            entity.getEmail()
        );
    }
}

Builder паттерн для сложных объектов

// Сложный объект со множеством параметров
public class DatabaseConfig {
    private final String host;
    private final int port;
    private final String username;
    private final String password;
    private final int connectionPoolSize;
    private final int timeoutSeconds;
    private final boolean sslEnabled;
    
    // Конструктор скрыт
    private DatabaseConfig(Builder builder) {
        this.host = builder.host;
        this.port = builder.port;
        this.username = builder.username;
        this.password = builder.password;
        this.connectionPoolSize = builder.connectionPoolSize;
        this.timeoutSeconds = builder.timeoutSeconds;
        this.sslEnabled = builder.sslEnabled;
    }
    
    // Простой Builder API
    public static class Builder {
        private String host;
        private int port = 5432;
        private String username;
        private String password;
        private int connectionPoolSize = 10;
        private int timeoutSeconds = 30;
        private boolean sslEnabled = false;
        
        public Builder host(String host) {
            this.host = host;
            return this;
        }
        
        public Builder port(int port) {
            this.port = port;
            return this;
        }
        
        public DatabaseConfig build() {
            if (host == null || username == null) {
                throw new IllegalStateException("Required fields missing");
            }
            return new DatabaseConfig(this);
        }
    }
}

// Клиент не знает деталей конструирования
DatabaseConfig config = new DatabaseConfig.Builder()
    .host("localhost")
    .port(5432)
    .username("admin")
    .build();

Интерфейсы для скрытия реализации

// Интерфейс определяет контракт
public interface PaymentService {
    void processPayment(BigDecimal amount);
    PaymentStatus getStatus(String transactionId);
}

// Реализация скрыта за интерфейсом
class StripePaymentService implements PaymentService {
    @Override
    public void processPayment(BigDecimal amount) {
        // Детали работы со Stripe API
    }
}

class PayPalPaymentService implements PaymentService {
    @Override
    public void processPayment(BigDecimal amount) {
        // Детали работы с PayPal API
    }
}

// Клиент работает только с интерфейсом
@Configuration
public class PaymentConfig {
    @Bean
    public PaymentService paymentService() {
        return new StripePaymentService();
        // Можем менять реализацию, не меняя клиентский код
    }
}

Приватные вспомогательные методы

public class OrderProcessor {
    // Публичный метод — простой интерфейс
    public void processOrder(Order order) {
        validateOrder(order);
        calculateDiscount(order);
        applyTaxes(order);
        saveToDatabase(order);
    }
    
    // Приватные методы — детали реализации
    private void validateOrder(Order order) { }
    private void calculateDiscount(Order order) { }
    private void applyTaxes(Order order) { }
    private void saveToDatabase(Order order) { }
}

Ключевые правила инкапсуляции

  1. Делай поля приватными — используй public getter/setter
  2. Возвращай копии — не ссылки на внутреннее состояние
  3. Используй Collections.unmodifiable* — для защиты списков
  4. Создавай DTO — для публичных API вместо сущностей
  5. Используй интерфейсы — чтобы скрыть реализацию
  6. Builder для сложных объектов — упростить конструирование
  7. Фасад для сложности — один простой метод вместо много операций
  8. Валидация в сеттерах — защити инварианты объекта

Хорошая инкапсуляция делает код надёжнее, проще тестировать и менять.

Как скрыть внутреннюю структуру | PrepBro