Ответ
Композиция — это принцип проектирования «has-a» («имеет»), при котором один класс (целое) включает в себя один или несколько объектов других классов (части) как часть своей внутренней структуры. Жизненный цикл части жестко привязан к жизненному циклу целого: часть создается и уничтожается вместе с целым.
Почему композиция предпочтительнее наследования? Принцип «Favor composition over inheritance» (предпочитай композицию наследованию)提倡使用组合而非继承,因为组合 обеспечивает:
- Гибкость: Поведение можно изменять динамически во время выполнения, подменяя компоненты.
- Слабое зацепление: Классы зависят от интерфейсов, а не от конкретных реализаций.
- Избегание хрупкости: Изменения в иерархии наследования могут сломать подклассы.
Пример композиции (Java):
// Классы-компоненты
class Engine {
void start() { System.out.println("Engine starts..."); }
}
class StereoSystem {
void playMusic() { System.out.println("Playing music..."); }
}
// Класс-композит, использующий композицию
class Car {
// Композиция: Car HAS-A Engine и HAS-A StereoSystem
private final Engine engine;
private final StereoSystem stereo;
public Car() {
// Части создаются внутри конструктора целого
this.engine = new Engine();
this.stereo = new StereoSystem();
}
public void startCar() {
engine.start(); // Делегирование ответственности компоненту
stereo.playMusic();
}
}
// Использование
Car myCar = new Car();
myCar.startCar(); // Engine starts... Playing music...
// Когда 'myCar' будет уничтожен, его Engine и StereoSystem также перестанут существовать.
Композиция vs. Агрегация: Оба являются ассоциациями «has-a», но при агрегации часть может существовать независимо от целого (например, Driver в Car). Композиция — более сильная связь.
Ответ 18+ 🔞
Слушай, ну вот смотри, опять эти ваши принципы проектирования. «Композиция», «наследование»... Звучит, как будто речь про наследство бабушкиной квартиры, а не про код. Но ладно, разбираемся.
Вот представь, блядь, у тебя есть класс Car. И ты такой думаешь: «О, машина! Значит, это частный случай Vehicle! Наследуюсь, и все дела!». И вот ты уже, сука, в этой иерархии по уши, а потом бац — тебе надо сделать FlyingCar. И ты уже наследуешься и от Car, и от Plane, а в итоге у тебя получается пиздопроебибна, которая и едет криво, и летает через жопу, потому что у Plane в родителях метод move() переопределён для воздуха, а у Car — для асфальта. И ты сидишь и думаешь: «Ну я же просто хотел машину с пропеллером, ёпта!».
А теперь смотри на композицию. Это как конструктор «Лего», в рот меня чих-пых! Ты не пытаешься переделать один кубик в другой. Ты берёшь кубик Engine, берёшь кубик StereoSystem, и хуяк — собираешь из них целый класс Car. Машина имеет (has-a) мотор. Машина имеет магнитолу. Всё, логика проще некуда.
И главная фишка в чём? В том, что эти кубики-компоненты живут и умирают вместе с твоей сборкой. Создал машину — внутри неё создались мотор и магнитола. Отправил машину в утиль нахуй — и мотор с магнитолой отправились следом. Жёсткая связь, никаких полупиздатых ситуаций, когда мотор остался, а машины уже нет.
А почему это круче наследования? Да потому что гибкость, блядь! Захотел машину с электромотором — не лезешь в дебри переписывания родительского класса, рискуя всё сломать. Ты просто, сука, в момент создания машины подсовываешь ей другой объект — ElectricEngine вместо PetrolEngine. И всё! Машина даже не в курсе, что её начинку поменяли, она просто вызывает engine.start(). Это и есть слабая связанность, когда классы договариваются между собой через интерфейсы, а не через родственные связи, которые, как известно, только портят отношения.
Ну и чтобы не быть голословным, вот тебе живой пример, смотри:
// Вот два независимых кубика-компонента
class Engine {
void start() { System.out.println("Двигатель заурчал..."); }
}
class StereoSystem {
void playMusic() { System.out.println("Шансон пошёл..."); }
}
// А вот наш главный сборщик — класс Car.
class Car {
// Композиция в чистом виде: Car В СЕБЕ СОДЕРЖИТ Engine и StereoSystem.
// Ключевое слово `final` тут — как гвоздём прибито: эти части неотъемлемы.
private final Engine engine;
private final StereoSystem stereo;
public Car() {
// Части рождаются прямо здесь, в недрах целого.
this.engine = new Engine();
this.stereo = new StereoSystem();
}
public void startCar() {
engine.start(); // Делегируем работу мотору
stereo.playMusic(); // Делегируем работу магнитоле
System.out.println("Поехали, блядь!");
}
}
// Использование
Car myLada = new Car();
myLada.startCar();
// На экран выведет:
// Двигатель заурчал...
// Шансон пошёл...
// Поехали, блядь!
// А когда `myLada` умрёт (выйдет из области видимости), её движок и магнитола умрут с ней — верные до конца.
И да, не путай с агрегацией, это другая история. Агрегация — это когда твой Car имеет Driver, но водитель-то, сука, самостоятельная единица! Он может выйти из этой машины и сесть в другую, и не помрёт, когда машину раздавят. А в композиции часть без целого — просто кусок металлолома, смысла не имеющий.
Вот и весь принцип. Не выёбывайся с глубокими иерархиями наследования, а собирай объекты, как из кубиков. И будет тебе, блядь, гибкость, и будет тебе понимание, и не придётся потом рефакторить эту многоэтажную пиздопроебибну.