Рубрики
Без рубрики

Почему вы должны отдавать предпочтение композиции, а не наследованию

Композиция всегда имела некоторые преимущества перед наследованием. Создание глубоких иерархий наследования l… Помеченный как java, ооп.

Композиция всегда имела некоторые преимущества перед наследованием. Создание иерархий глубокого наследования приводит к хрупкому/негибкому коду, когда вы пытаетесь внести изменения глубоко в иерархию.

В интерфейсы Java 8 и выше были внесены два ключевых улучшения, которые делают аргумент в пользу композиции еще более убедительным:

  • Методы по умолчанию – Это позволяет вам определять методы с реализацией по умолчанию внутри интерфейсов. Классы, реализующие интерфейс, могут переопределять методы по умолчанию или сохранять их как есть.
  • Частные/Статические методы – Таким образом, становится проще писать функциональность по умолчанию для интерфейса, используя частные вспомогательные методы, которые не должны быть доступны как часть интерфейса.

Давайте рассмотрим пример, в котором мы можем использовать композицию вместе с новой функциональностью интерфейсов.

Давайте создадим класс, который будет представлять биологического человека. Мы можем представить, что человеческое существо состоит из множества различных систем, которые работают вместе для того, чтобы человек мог функционировать. Есть скелетная система, сердечно-сосудистая система, иммунная система, нервная система и так далее.

В этом упрощенном примере мы можем думать о каждой системе как об интерфейсе. Наш человеческий класс будет реализовывать каждый из этих интерфейсов:

Отлично! Теперь, что, если мы захотим создать новый биологический организм, который имеет много общих черт с людьми, но не имеет скелетной системы? Это иллюстрирует главное преимущество композиции. Мы можем сделать это, просто создав новый класс, который реализует только 2 из 3 систем:

Теперь мы можем написать простой интерфейс скелетной системы и посмотреть, как человек может его реализовать. Мы продемонстрируем частные методы интерфейса и методы интерфейса по умолчанию, а также поля интерфейса:

public interface SkeletalSystem {
    List bones = new ArrayList<>();
    List joints = new ArrayList<>();

    abstract String getSex();

    default void addBone(Bone b) {
        bones.add(b);
        recalculateJointHealth();
    }

    default void addJoint(Joint j) {
        joints.add(j);
        recalculateJointHealth();
    }

    default int getBoneMass() {
        return bones.stream().map(Bone::getMass).reduce(0, Integer::sum);
    }

    private void recalculateJointHealth() {
        for(Joint j : joints) {
            //... do something
        }
    }

    public class Bone {
        private String name;
        private int mass;

        public Bone(String name, int mass) {
            this.name = name;
            this.mass = mass;
        }

        public int getMass() { return mass; }
        public void setMass(int mass) { this.mass = mass; }

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }

    public class Joint {
        private Bone b1;
        private Bone b2;
        private int strength;

        public Joint(Bone b1, Bone b2, int strength) {
            this.b1 = b1;
            this.b2 = b2;
            this.strength = strength;
        }

        public Bone getB1() { return b1; }
        public void setB1(Bone b1) { this.b1 = b1; }

        public Bone getB2() { return b2; }
        public void setB2(Bone b2) { this.b2 = b2; }

        public int getStrength() { return strength; }
        public void setStrength(int strength) { this.strength = strength; }
    }
}

Для краткости Skeletal System представлена как полностью автономный интерфейс. Он определяет классы костей и суставов и коллекции для их хранения. Существуют методы по умолчанию для добавления костей и суставов, а также частные вспомогательные методы.

public class Human implements SkeletalSystem {
    private final String sex;
    private int height;
    private int weight;

    public Human(String sex) {
        this.sex = sex;

        Bone lfemur = new Bone("LFemur", 50);
        Bone lHip = new Bone("lHip", 50);
        addBone(lfemur);
        addBone(lHip);

        addJoint(new Joint(lfemur, lHip, 100));

        System.out.println(getBoneMass());
    }

    public int getHeight() { return height; }
    public void setHeight(int height) { this.height = height; }

    public int getWeight() { return weight; }
    public void setWeight(int weight) { this.weight = weight; }

    @Override
    public String getSex() { return sex; }
}

Наш класс Human определяет свои собственные поля данных и использует Скелетную систему для добавления некоторых костей и суставов. Мы также могли бы создать вспомогательные методы, которые могли бы инициализировать Скелетную систему, используя стандартную структуру костей человека.

Я надеюсь, что этот пример поможет вам выбрать композицию вместо наследования, когда это уместно. Помните об этом основном правиле при принятии решения:

  • Выберите наследование при моделировании отношения “is-a” .

    • Пожилой человек – это гражданин.
    • Стоячий письменный стол – это письменный стол.
  • Выберите композицию при моделировании отношения “имеет-а” .

    • У человека есть костная система, сердечно-сосудистая система и т.д.
    • У автомобиля есть двигатель, коробка передач, бензобак и т.д.

Оригинал: “https://dev.to/romansery/why-you-should-favor-composition-over-inheritance-57m6”