← Назад к вопросам
Как добавить список к закрытому для модификаций классу
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 | Обычно (ЛУЧШИЙ ВАРИАНТ) |
| Decorator | Interface design | Много кода | Нужна interface |
| Inheritance | Polymorphism | Хрупко | Если не 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.