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

Как организовать инкапсуляцию на уровне пакета, чтобы внутри пакета все было доступно, а снаружи запрещено

1.6 Junior🔥 71 комментариев
#ООП#Основы Java

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

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

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

# Инкапсуляция на уровне пакета в Java

В Java существует четыре уровня доступа, и package-private (по умолчанию) — мощный инструмент для организации инкапсуляции на уровне пакета.

Модификаторы доступа в Java

МодификаторКлассПакетНаследникВне пакета
private
package-private (нет)
protected
public

1. Package-Private (Default Access)

Это идеальный уровень для инкапсуляции на уровне пакета.

Пример структуры

com.example.auth/
  ├── AuthService.java (public)
  ├── PasswordValidator.java (package-private)
  ├── TokenGenerator.java (package-private)
  └── JwtParser.java (package-private)

Код

// File: com/example/auth/AuthService.java
package com.example.auth;

public class AuthService {
    private PasswordValidator passwordValidator = new PasswordValidator();
    private TokenGenerator tokenGenerator = new TokenGenerator();
    
    public boolean authenticate(String username, String password) {
        // PasswordValidator доступен внутри пакета
        if (!passwordValidator.validate(password)) {
            return false;
        }
        // TokenGenerator доступен внутри пакета
        String token = tokenGenerator.generate(username);
        return true;
    }
}

// File: com/example/auth/PasswordValidator.java
// Без модификатора public = package-private
class PasswordValidator {
    boolean validate(String password) {
        return password != null && password.length() >= 8;
    }
}

// File: com/example/auth/TokenGenerator.java
class TokenGenerator {
    String generate(String username) {
        return "token_" + username + "_" + System.currentTimeMillis();
    }
}

// File: com/example/auth/JwtParser.java
class JwtParser {
    String parseToken(String token) {
        // Разбор JWT токена
        return token.split("_")[1];
    }
}

Использование вне пакета

// File: com/example/user/UserService.java
package com.example.user;

import com.example.auth.AuthService;
// PasswordValidator, TokenGenerator недоступны!
import com.example.auth.PasswordValidator; // COMPILE ERROR!

public class UserService {
    private AuthService authService = new AuthService();
    
    public void loginUser(String username, String password) {
        // ✓ Работает
        boolean authenticated = authService.authenticate(username, password);
        
        // ✗ COMPILE ERROR: PasswordValidator не видна
        // PasswordValidator validator = new PasswordValidator();
    }
}

2. Практический пример: HTTP клиент

Структура

com.example.http/
  ├── HttpClient.java (public API)
  ├── Request.java (public DTO)
  ├── Response.java (public DTO)
  ├── ConnectionPool.java (package-private внутреннее)
  ├── RequestSerializer.java (package-private вспомогательный)
  └── ResponseParser.java (package-private вспомогательный)

Реализация

// Public API
public class HttpClient {
    private ConnectionPool pool = new ConnectionPool();
    private RequestSerializer serializer = new RequestSerializer();
    private ResponseParser parser = new ResponseParser();
    
    public Response get(String url) {
        // Эти классы видны только внутри пакета
        Connection conn = pool.getConnection(url);
        String serialized = serializer.serialize(new Request(url));
        return parser.parse(conn.execute(serialized));
    }
}

public class Request {
    private String url;
    public Request(String url) { this.url = url; }
    public String getUrl() { return url; }
}

public class Response {
    private int statusCode;
    private String body;
    // Getters...
}

// Скрыто от внешних пакетов
class ConnectionPool {
    Connection getConnection(String url) {
        // Сложная логика управления подключениями
        return new Connection(url);
    }
}

class RequestSerializer {
    String serialize(Request request) {
        return "{\"url\":\"" + request.getUrl() + "\"}";
    }
}

class ResponseParser {
    Response parse(String response) {
        // Парсинг ответа
        return new Response(200, response);
    }
}

class Connection {
    String execute(String data) { return data; }
}

3. Сложный пример: ORM пакет

// com/example/orm/EntityManager.java (PUBLIC API)
public class EntityManager {
    private SessionFactory factory = new SessionFactory();
    private QueryBuilder queryBuilder = new QueryBuilder();
    
    public <T> T find(Class<T> entityClass, Long id) {
        Session session = factory.createSession();
        return queryBuilder.buildSelectQuery(entityClass, id).execute(session);
    }
}

// com/example/orm/Session.java (PUBLIC)
public interface Session {
    void save(Object entity);
    Object load(Class<?> entityClass, Long id);
}

// Внутренние классы (PACKAGE-PRIVATE)
class SessionFactory {
    Session createSession() {
        return new SessionImpl();
    }
}

class SessionImpl implements Session {
    @Override
    public void save(Object entity) {
        // Сложная логика сохранения
    }
    
    @Override
    public Object load(Class<?> entityClass, Long id) {
        // Сложная логика загрузки
        return null;
    }
}

class QueryBuilder {
    Query buildSelectQuery(Class<?> entityClass, Long id) {
        return new SqlQuery("SELECT * FROM " + entityClass.getSimpleName() + " WHERE id = " + id);
    }
}

class SqlQuery implements Query {
    private String sql;
    
    SqlQuery(String sql) { this.sql = sql; }
    
    Object execute(Session session) {
        // Выполнение запроса
        return null;
    }
}

interface Query {
    Object execute(Session session);
}

4. Лучшие практики

1. Создавайте фасад (Facade Pattern)

// Один public класс как точка входа
public class DatabaseService {
    private ConnectionManager connManager = new ConnectionManager();
    private TransactionManager transactionManager = new TransactionManager();
    private QueryExecutor queryExecutor = new QueryExecutor();
    
    public void executeTransaction(String sql) {
        // Координирует внутренние компоненты
        transactionManager.begin();
        queryExecutor.execute(sql);
        transactionManager.commit();
    }
}

// Внутренние вспомогательные классы
class ConnectionManager { }
class TransactionManager { }
class QueryExecutor { }

2. Группируйте по функциям

com.example.ecommerce/
  └── order/                     // Пакет заказов
      ├── OrderService.java      // PUBLIC
      ├── Order.java             // PUBLIC DTO
      ├── OrderRepository.java    // PUBLIC интерфейс
      ├── OrderRepositoryImpl.java // PACKAGE-PRIVATE
      ├── OrderValidator.java     // PACKAGE-PRIVATE
      └── OrderPriceCalculator.java // PACKAGE-PRIVATE

3. Разделяйте интерфейсы и реализацию

// PUBLIC интерфейс
public interface PaymentProcessor {
    boolean process(Payment payment);
}

// PACKAGE-PRIVATE реализация
class StripePaymentProcessor implements PaymentProcessor {
    @Override
    public boolean process(Payment payment) {
        // Специфичная для Stripe логика
        return true;
    }
}

class PayPalPaymentProcessor implements PaymentProcessor {
    @Override
    public boolean process(Payment payment) {
        // Специфичная для PayPal логика
        return true;
    }
}

// PUBLIC фабрика
public class PaymentProcessorFactory {
    public static PaymentProcessor create(String provider) {
        switch (provider) {
            case "stripe":
                return new StripePaymentProcessor();
            case "paypal":
                return new PayPalPaymentProcessor();
            default:
                throw new IllegalArgumentException();
        }
    }
}

5. Аннотации для документации

/**
 * Internal API - not for public use.
 * This class is part of internal implementation and can change without notice.
 * 
 * @internal
 */
class InternalHelper {
    // ...
}

Преимущества

Инкапсуляция — скрываем реализацию ✓ Сохранение совместимости — можем менять внутренние классы без breaking changes ✓ Чистая архитектура — четкое разделение на public API и internal ✓ Безопасность — запрещаем неправильное использование ✓ Простота — не требует дополнительных библиотек

Недостатки

✗ IDE не может помочь с автодополнением для package-private ✗ Рефлексия может обойти защиту ✗ Требует понимания архитектуры

Вывод

Package-private доступ — мощный инструмент для организации инкапсуляции на уровне пакета. Используйте его для скрытия внутренней реализации и предоставления четкого public API.