Наследование против композиции? - PullRequest
0 голосов
/ 05 декабря 2010

Я задавал подобный вопрос месяц назад: Наследование через состав?

Я взял примеры из этой статьи: http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=1 Однако на этот раз это другая проблема. Извините за публикацию большого количества кодов, но это просто долго, это не трудно читать.

class Fruit {
    public int peel() {

            System.out.println("Peeling is appealing.");
            return 1;
        }
    }

class Apple extends Fruit {
}

class Example1 {

    public static void main(String[] args) {

        Apple apple = new Apple();
        int pieces = apple.peel();
    }
}

После модификации это уже не нормально:

class Peel {

    private int peelCount;   
    public Peel(int peelCount) {
        this.peelCount = peelCount;
    }    
    public int getPeelCount() {    
        return peelCount;
    }
}

Модификация класса Fruit приводит к коду ошибки компиляции.

class Peel {

    private int peelCount;

    public Peel(int peelCount) {
        this.peelCount = peelCount;
    }

    public int getPeelCount() {

        return peelCount;
    }
}

class Fruit {

// Return a Peel object that
// results from the peeling activity.
    public Peel peel() {

        System.out.println("Peeling is appealing.");
        return new Peel(1);
    }
}

Старая реализация не работает. Эту проблему можно решить по составу:

class Peel {

    private int peelCount;

    public Peel(int peelCount) {
        this.peelCount = peelCount;
    }

    public int getPeelCount() {

        return peelCount;
    }
}


class Fruit {

// Return int number of pieces of peel that
// resulted from the peeling activity.
public Peel peel() {

System.out.println("Peeling is appealing.");
        return new Peel(1);
    }
}

// Apple must be changed to accomodate
// the change to Fruit
class Apple {

    private Fruit fruit = new Fruit();

    public int peel() {

        Peel peel = fruit.peel();
        return peel.getPeelCount();
    }
}

// This old implementation of Example2
// still works fine.
class Example1 {

    public static void main(String[] args) {

        Apple apple = new Apple();
        int pieces = apple.peel();
    }
}

Со стилем композиции яблоко больше не является - это отношения с Fruit. Это стало имением. Методы из Fruit делегируются Apple как наследство. Здесь возникают мои вопросы:

  • С методами-обертками для делегирования методов из Fruit, разве это не продвигает копирование и вставку, что является плохой практикой? Изображение, если у нас есть около 10-50 методов во фруктах. Как можно наследовать по составу, если существует около 50 подклассов для наследования?

  • Что касается первого примера наследования с использованием расширения, автор предполагает, что одно изменение в суперклассе нарушит реализации, использующие его. Однако, мне интересно, почему это нужно изменить? Разве один из принципов ОО «закрыт для модификации, открыт для расширения»? В случае заявленного автора, я все еще могу сохранить старую реализацию суперкласса и все же заставить ее адаптироваться к новым изменениям. Как это:

    класс Фрукты {

    // Return a Peel object that
    // results from the peeling activity.
    private Peel getPeel() {
    
        System.out.println("Peeling is appealing.");
        return new Peel(1);
    }
    public int peel(){
        Peel peel = getPeel();
        return peel.getPeelCount();
    }
    

    }

Что-то не так с добавлением новых методов в суперкласс для адаптации вместо изменения или замены старых методов, что нарушит структуру программы? Насколько я вижу, делая это, я все еще могу достичь того же, что и в примере композиции, а также мне не нужно будет создавать множество методов-оболочек для делегирования методов суперкласса для каждого подкласса.

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

интерфейс Talkative {

 public void talk();

}

Семейство классов животных может реализовывать интерфейс Talkative, а также семейство классов Person. Что вы думаете о моем заключении?

1 Ответ

3 голосов
/ 05 декабря 2010

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

К сожалению, это не очень хорошо работает.Это приводит к увеличению числа классов-оболочек и тому подобному, что создает экземпляров классов-оболочек.Если вы сделаете большую часть этого, ваша кодовая база / приложение будет раздутой, производительность ухудшится, и ваш код будет становиться все труднее понять.

Лучшие подходы:

  • Попробуйте получитьAPI прямо на первом месте.(Дух!)
  • Если изменение API не добавляет существенной ценности или исправляет что-то важное, откладывайте это.
  • Когда вам нужно внести изменение API, сделайте это вбинарный совместимый путь;Например, добавьте новые методы вместо внесения двоичных несовместимых изменений в существующие.Пометьте старые методы как устаревшие, если вы планируете от них избавиться.
  • Если у вас есть для внесения двоичных несовместимых изменений API, измените весь код, который использует его одновременно.

Тег @Deprecated позволяет вам предупреждать своих API-интерфейсов о том, что метод больше не должен использоваться, и в то же время дает им окно для исправления их кода с использованием метода замены / альтернативного подхода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...