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

Как добавить список к закрытому для модификаций классу

2.0 Middle🔥 191 комментариев
#ООП

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

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

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

# Как добавить список к закрытому для модификаций классу

Вопрос про то, как безопасно расширить функциональность класса, который мы не можем модифицировать (например, из внешней библиотеки или final класс). Расскажу несколько подходов.

Проблема

// Это final класс из библиотеки, мы не можем его менять
public final class ImmutablePerson {
    private final String name;
    private final int age;
    
    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
    // Больше нет методов!
}

// Нам нужно добавить список контактов, но класс закрыт
// Как это сделать?

Решение 1: Composition (Обёртка) — ЛУЧШИЙ ВАРИАНТ

Идея: создаём новый класс, который содержит исходный класс + дополнительные данные.

public class PersonWithContacts {
    private final ImmutablePerson person;  // Обёртываем исходный объект
    private final List<String> contacts;   // Добавляем новое поле
    
    public PersonWithContacts(ImmutablePerson person, List<String> contacts) {
        this.person = person;
        this.contacts = new ArrayList<>(contacts);  // Defensive copy
    }
    
    // Делегируем методы исходного класса
    public String getName() {
        return person.getName();
    }
    
    public int getAge() {
        return person.getAge();
    }
    
    // Добавляем новые методы
    public List<String> getContacts() {
        return new ArrayList<>(contacts);  // Defensive copy
    }
    
    public void addContact(String contact) {
        contacts.add(contact);
    }
    
    public void removeContact(String contact) {
        contacts.remove(contact);
    }
}

// Использование
public class Example1 {
    public static void main(String[] args) {
        ImmutablePerson person = new ImmutablePerson("Alice", 30);
        PersonWithContacts personWithContacts = new PersonWithContacts(
            person, 
            Arrays.asList("+1234567890", "alice@example.com")
        );
        
        System.out.println(personWithContacts.getName());  // Alice
        System.out.println(personWithContacts.getContacts()); // [+1234567890, alice@example.com]
        
        personWithContacts.addContact("+1111111111");
    }
}

Плюсы:

  • Не нарушаем original класс
  • Безопасно
  • Легко тестировать
  • Следует Single Responsibility Principle

Минусы:

  • Нужно делегировать все методы (boilerplate)
  • Instanceof проверки не будут работать

Решение 2: Decorator Pattern (если нужна interface)

Когда original класс реализует интерфейс:

public interface PersonInterface {
    String getName();
    int getAge();
}

public class ImmutablePersonImpl implements PersonInterface {
    private final String name;
    private final int age;
    
    public ImmutablePersonImpl(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public int getAge() { return age; }
}

// Decorator
public class PersonDecorator implements PersonInterface {
    private final PersonInterface person;
    private final List<String> contacts;
    
    public PersonDecorator(PersonInterface person, List<String> contacts) {
        this.person = person;
        this.contacts = new ArrayList<>(contacts);
    }
    
    @Override
    public String getName() {
        return person.getName();
    }
    
    @Override
    public int getAge() {
        return person.getAge();
    }
    
    public List<String> getContacts() {
        return new ArrayList<>(contacts);
    }
    
    public void addContact(String contact) {
        contacts.add(contact);
    }
}

// Использование
public class Example2 {
    public static void main(String[] args) {
        PersonInterface person = new ImmutablePersonImpl("Bob", 25);
        
        // Декорируем
        PersonDecorator decorated = new PersonDecorator(
            person,
            Arrays.asList("+9876543210")
        );
        
        System.out.println(decorated.getName());  // Bob
        System.out.println(decorated.getContacts());  // [+9876543210]
    }
}

Решение 3: Inheritance (если класс не final)

Если класс НЕ final, можешь наследоваться:

public class Person {
    private final String name;
    private final int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
}

// Наследуемся и расширяем
public class PersonWithContacts extends Person {
    private final List<String> contacts;
    
    public PersonWithContacts(String name, int age, List<String> contacts) {
        super(name, age);
        this.contacts = new ArrayList<>(contacts);
    }
    
    public List<String> getContacts() {
        return new ArrayList<>(contacts);
    }
    
    public void addContact(String contact) {
        contacts.add(contact);
    }
}

// Использование
public class Example3 {
    public static void main(String[] args) {
        PersonWithContacts person = new PersonWithContacts(
            "Charlie",
            35,
            Arrays.asList("+1122334455")
        );
        
        System.out.println(person.getName());  // Charlie
        System.out.println(person.getContacts());  // [+1122334455]
        
        // Polymorphism работает
        Person p = person;
        System.out.println(p.getName());  // Charlie
    }
}

Плюсы:

  • Polymorphism
  • Instanceof работает
  • Меньше boilerplate

Минусы:

  • Нарушает Liskov Substitution Principle если не правильно использовать
  • Хрупко наследование
  • Final методы не переопределяются

Решение 4: Delegation с Map (если много полей)

Когда нужна большая гибкость:

public class PersonWrapper {
    private final ImmutablePerson person;
    private final Map<String, Object> additionalData;
    
    public PersonWrapper(ImmutablePerson person) {
        this.person = person;
        this.additionalData = new HashMap<>();
    }
    
    // Original методы
    public String getName() {
        return person.getName();
    }
    
    public int getAge() {
        return person.getAge();
    }
    
    // Добавляем произвольные данные
    public void setData(String key, Object value) {
        additionalData.put(key, value);
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getData(String key) {
        return (T) additionalData.get(key);
    }
    
    // Специально для списков контактов
    @SuppressWarnings("unchecked")
    public List<String> getContacts() {
        Object contacts = additionalData.getOrDefault("contacts", new ArrayList<>());
        return (List<String>) contacts;
    }
    
    public void setContacts(List<String> contacts) {
        additionalData.put("contacts", new ArrayList<>(contacts));
    }
}

// Использование
public class Example4 {
    public static void main(String[] args) {
        ImmutablePerson person = new ImmutablePerson("Diana", 28);
        PersonWrapper wrapper = new PersonWrapper(person);
        
        // Добавляем контакты
        wrapper.setContacts(Arrays.asList("diana@example.com", "+555555555"));
        
        System.out.println(wrapper.getName());  // Diana
        System.out.println(wrapper.getContacts());  // [diana@example.com, +555555555]
        
        // Добавляем и другие данные
        wrapper.setData("department", "Engineering");
        wrapper.setData("salary", 100000);
        
        String dept = wrapper.getData("department");  // Engineering
    }
}

Решение 5: Builder Pattern для более красивого синтаксиса

public class PersonBuilder {
    private final ImmutablePerson person;
    private List<String> contacts = new ArrayList<>();
    
    public PersonBuilder(ImmutablePerson person) {
        this.person = person;
    }
    
    public PersonBuilder addContact(String contact) {
        contacts.add(contact);
        return this;
    }
    
    public PersonBuilder addContacts(List<String> contacts) {
        this.contacts.addAll(contacts);
        return this;
    }
    
    public PersonWithContacts build() {
        return new PersonWithContacts(person, contacts);
    }
}

// Красивое использование
public class Example5 {
    public static void main(String[] args) {
        ImmutablePerson person = new ImmutablePerson("Eve", 32);
        
        PersonWithContacts personWithContacts = new PersonBuilder(person)
            .addContact("+1234567890")
            .addContact("eve@example.com")
            .addContact("eve@company.com")
            .build();
        
        System.out.println(personWithContacts.getName());  // Eve
        System.out.println(personWithContacts.getContacts());
        // [+1234567890, eve@example.com, eve@company.com]
    }
}

Сравнение подходов

ПодходПлюсыМинусыКогда использовать
CompositionБезопасно, гибкоBoilerplateОбычно (ЛУЧШИЙ ВАРИАНТ)
DecoratorInterface designМного кодаНужна interface
InheritancePolymorphismХрупкоЕсли не final
MapОчень гибкоType-unsafeДинамические данные
BuilderКрасивоМного кодаКомплексные объекты

Лучший выбор (в 99% случаев)

// ИСПОЛЬЗУЙ ЭТО
public class PersonWithContacts {
    private final ImmutablePerson person;
    private final List<String> contacts;
    
    public PersonWithContacts(ImmutablePerson person, List<String> contacts) {
        this.person = person;
        this.contacts = new ArrayList<>(contacts);
    }
    
    public String getName() { return person.getName(); }
    public int getAge() { return person.getAge(); }
    public List<String> getContacts() { return new ArrayList<>(contacts); }
    public void addContact(String contact) { contacts.add(contact); }
}

Это Composition — самый безопасный и гибкий подход, не нарушает принципы Clean Code.

Как добавить список к закрытому для модификаций классу | PrepBro