Сравнение композиции и наследования – один из часто задаваемых вопросов на собеседованиях. Вы наверняка также слышали, что рекомендуется использовать композицию вместо наследования.
Сравнение композиции и наследования
Композиция и наследование – это концепции объектно-ориентированного программирования. Они не привязаны к конкретному языку программирования, такому как Java. Прежде чем мы приступим к программному сравнению композиции и наследования, давайте быстро определим их.
Композиция
Композиция – это техника проектирования в объектно-ориентированном программировании для реализации отношения имеет между объектами. В Java композиция достигается с использованием переменных экземпляра других объектов. Например, человек, у которого есть работа, реализуется следующим образом в объектно-ориентированном программировании на Java.
package com.journaldev.composition;
public class Job {
// переменные, методы и т. д.
}
package com.journaldev.composition;
public class Person {
// композиция - отношение имеет
private Job job;
// переменные, методы, конструкторы и т. д. объектно-ориентированный
Наследование
Наследование – это техника проектирования в объектно-ориентированном программировании для реализации отношения is-a между объектами. Наследование в Java реализуется с использованием ключевого слова extends. Например, отношение “Кот – это Животное” в программировании на Java будет реализовано следующим образом.
package com.journaldev.inheritance;
public class Animal {
// переменные, методы и так далее.
}
package com.journaldev.inheritance;
public class Cat extends Animal{
}
Композиция против наследования
Как композиция, так и наследование способствуют повторному использованию кода разными способами. Так какой выбрать? Как сравнить композицию и наследование? Вы, наверное, слышали, что в программировании следует отдавать предпочтение композиции перед наследованием. Давайте рассмотрим некоторые причины, которые помогут вам выбрать между композицией и наследованием.
-
Наследование тесно связано, тогда как композиция слабо связана. Давайте предположим, у нас есть следующие классы с наследованием.
package com.journaldev.java.examples; public class ClassA { public void foo(){ } } class ClassB extends ClassA{ public void bar(){ } }
Для простоты мы поместили как суперкласс, так и подкласс в один пакет. Но, как правило, они будут находиться в отдельных кодовых базах. Может быть много классов, расширяющих суперкласс ClassA. Очень распространенным примером такой ситуации является расширение класса Exception. Теперь давайте предположим, что реализация ClassA изменена следующим образом, добавлен новый метод bar().
package com.journaldev.java.examples; public class ClassA { public void foo(){ } public int bar(){ return 0; } }
Как только вы начнете использовать новую реализацию ClassA, вы получите ошибку времени компиляции в ClassB, как
The return type is incompatible with ClassA.bar()
. Решением будет изменение либо суперкласса, либо метода bar() подкласса, чтобы сделать их совместимыми. Если бы вы использовали композицию вместо наследования, вы никогда не столкнулись бы с этой проблемой. Простым примером реализации ClassB с использованием композиции может быть следующее.class ClassB{ ClassA classA = new ClassA(); public void bar(){ classA.foo(); classA.bar(); } }
-
В наследовании отсутствует контроль доступа, в то время как в композиции доступ может быть ограничен. Мы раскрываем все методы суперкласса для других классов, имеющих доступ к подклассу. Таким образом, если в суперклассе вводится новый метод или есть пробелы в безопасности, подкласс становится уязвимым. Поскольку в композиции мы выбираем, какие методы использовать, это более безопасно, чем наследование. Например, мы можем предоставить экспозицию метода foo() из ClassA другим классам с использованием следующего кода в ClassB.
class ClassB { ClassA classA = new ClassA(); public void foo(){ classA.foo(); } public void bar(){ } }
Это одно из основных преимуществ композиции перед наследованием.
-
Композиция предоставляет гибкость при вызове методов, что полезно в сценарии с несколькими подклассами. Например, предположим, у нас есть следующий сценарий наследования.
abstract class Abs { abstract void foo(); } public class ClassA extends Abs{ public void foo(){ } } class ClassB extends Abs{ public void foo(){ } } class Test { ClassA a = new ClassA(); ClassB b = new ClassB(); public void test(){ a.foo(); b.foo(); } }
И что, если у нас есть больше подклассов, сделает ли композиция наш код уродливым, имея по одному экземпляру для каждого подкласса? Нет, мы можем переписать класс Test следующим образом.
class Test { Abs obj = null; Test1(Abs o){ this.obj = o; } public void foo(){ this.obj.foo(); } }
Это даст вам гибкость использования любого подкласса на основе объекта, используемого в конструкторе.
-
Еще одним преимуществом композиции перед наследованием является область тестирования. Юнит-тестирование легко в композиции, потому что мы знаем, какие методы мы используем из другого класса. Мы можем использовать его для тестирования, в то время как в наследовании мы сильно зависим от суперкласса и не знаем, какие методы суперкласса будут использованы. Так что нам придется тестировать все методы суперкласса. Это лишняя работа, и мы должны делать ее из-за наследования.
Вот и все по сравнению композиции и наследования. У вас достаточно причин выбрать композицию вместо наследования. Используйте наследование только тогда, когда вы уверены, что суперкласс не будет изменен, в противном случае предпочтительнее использовать композицию.
Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance