Композия или наследование?
Представим, что у нас есть такой класс Developer:
Developer{
fun writeCode()
fun drinkCoffee()
}
И мы хотим сделать класс Devops, который умеет запускать докер и помимо этого, чтобы он еще и умел пить кофе как Developer. Очевидный путь это создать класс Devops написать функцию по запуску докера, а функцию пить кофе просто скопировать из Developer.
Однако получится дублирование кода, что довольно скверно и несет кучу проблем в будущем. Как решить это дерьмо? Есть два варианта: Наследованиe и Композиция, разберем каждый.
👨👩👧 Наследованиe. Если класс
Devops наследует класс Developer, значит все открыте методы и поля Developer окажутся в объекте Devops:class Devops : Developer{
fun launchDocker()
}
val devops = Devops()
devops.writeCode()
devops.launchDocker()
Вроде бы все круто, но возникает сложность. Допустим мы не хотим, чтобы Devops умел писал код (ведь
Devops это не человек а идеалогия, но кофе пить можно!). В таком случае лучше использовать композицию.🪆Композиция, это когда одно из полей класса
Devops является классом Developer. Эначит мы сначала конструируем объект Developer, а потом устанавливаем этот объект в поле объекта Devops. А Devops уже будет обращаться к методам из класса Developer:class Devops(
private val developer:Developer
){
fun launchDocker()
fun drinkCoffee(){
developer.drinkCoffee()
}
}
val devops = Devops()
devops.drinkCoffee()
devops.launchDocker()
мы избавились от дублирования кода и при этом в классе
Devops нет метода drinkCoffee().Возникает вопрос зачем нам тогда нужно наследование, ведь все вокруг трубят, что лучше использовать композицию а не наследование?
Самым правильным ответом на этот вопрос будет: это зависит от вашего случая.
Наследование стоит выбирать тогда, когда у двух классов есть отношение
является. Например есть класс Promotion (Акция) и есть класс NewYearPromotion (Новогодняя акция) очевидно, что у них есть отношение является, так как NewYearPromotion это просто другая разновидность Promotion и тут нужно наследование.Композицию стоит выбирать когда есть отношение
использует. Допустим есть класс Car и класс Wheel, явно Car использует Wheel, а не является Wheel значит тут нужна композиция.Некоторые практических советов как выбрать и итоги:
👉При использовании стороних библиотек, стоит унаследоваться только от абстрактных классов или интерфейсов. Во всех других случаях лучше использовать композицию. Это связано с тем, что простые классы могуть меняться, и эти изменения могут сильно стрельнуть.
👉Если вы сами делаете либу, то делайте ваши классы закрытыми для наследования. Давайте возможность клиентам наследоваться только от ваших абстактных классов и интерфейсов.
👉Если класс
B расширяет и является классом A то наследование.👉Если класс
B только использует часть функционала класса A то композиция.