Вопрос о природе унаследованных классов Java

Поэтому я думаю, что у меня есть довольно простой вопрос. Скажем, есть Java-программа с открытым исходным кодом com.cow.moo, которую вы включаете в свой проект com.bee.buzz.

У Му есть классные классы, большинство из которых вы не хотите трогать, но есть пара, которую вы делаете. Теперь, на данный момент, лучше всего расширить классы, которые вы хотите изменить, верно? (Я знаю, что было много сказано о расширениях и реализациях, но ни один из этих классов не является интерфейсом, так что об этом не может быть и речи.)

Мой вопрос, скажи, что это класс в Moo:

package com.cow.moo;
public class Milk {
    private float currentMilk;
    public int getMilk() { /* Stuff */ }
    public float convertToGallons (float liquid) { /* More Stuff */ }
}

Теперь, скажем, я хочу использовать getMilk в моем новом классе, расширяющем Milk. Однако getMilk в Milk использует закрытые переменные (например, currentMilk) и другие функции, которые я не буду включать (например, convertToGallons.) Должен ли я включать эти переменные и функции, если я хочу, чтобы моя новая функция работала правильно? Я не хочу сильно изменять функцию, просто добавлю немного к ней. Какой лучший способ сделать это?

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

5 ответов

Решение

Теперь, скажем, я хочу использовать getMilk в моем новом классе, расширяющем Milk. Однако getMilk в Milk использует закрытые переменные (например, currentMilk) и другие функции, которые я не буду включать (например, convertToGallons.) Должен ли я включать эти переменные и функции, если я хочу, чтобы моя новая функция работала правильно?

Вам не нужно будет включать публичные функции и переменные. Основная концепция наследования заключается в том, что в качестве подкласса все открытые (и защищенные) члены вашего родительского класса включаются в ваш подкласс бесплатно. Таким образом, ваш подкласс (скажем, HoneyMilk) может вызвать convertToGallons прямо с самого начала.

Переопределение getMilk в этом случае это намного сложнее, так как он опирается на закрытую переменную (к которой ваш подкласс не может получить доступ). Мой совет состоит в том, чтобы переключить ваше мышление с того, чтобы относиться к классу как к "белому ящику", к "черному ящику". Я имею в виду, что вы должны реализовать свою переопределенную версию getMilk как будто вы не смогли увидеть исходный код Милка. Хотя это может показаться обходным решением (я имею в виду, почему я не могу просто настроить эту строку здесь?!), это заставит вас реализовать свой подкласс, используя только то, что родительский класс предоставляет публично. Это также сильно подчеркивает важность абстракции, которую крайне важно использовать при разработке крупномасштабных проектов.

Общая рекомендация - отдавать предпочтение композиции перед наследованием.

Скажем, у вас есть интерфейс и существующая реализация, которая в основном соответствует вашим потребностям, например

public interface MilkProvider { public float getMilk(); }
public class Milk implements MilkProvider { // same as you example }

и нужна другая пользовательская реализация, вы можете написать ее так:

public class MyMilk implements MilkProvider {
  private MilkProvider milk; 

  public MyMilk(int someValue) {
    milk = new Milk(someValue);  // unfortunatly we can't get rid of a depencency
                                 // to the concrete class because we need to create
                                 // something. An existing factory could help, but
                                 // but usually there's none implemented.
  }

  public float getMilk() {
    float result = milk.getMilk();
    // do somethink with the result
    return float;
  }
}

Я думаю, что в этом случае лучшим решением будет полиморфизм (статический полиморфизм), или вы можете использовать отражение (не используйте этот способ), чтобы добраться до закрытой переменной.

Вы можете расширять класс и обращаться к переменным экземпляра с помощью методов доступа (методов получения и установки), если они являются общедоступными.

Вы можете использовать AOP (Аспектно-ориентированное программирование), чтобы изменить ваши классы moo во время выполнения, не меняя их источники.

Подумайте также, прочитайте некоторые темы "Состав против наследования".

Надеюсь, что это поможет вам.

Вы не сможете использовать закрытые члены класса, если не будете использовать отражение Java, которое будет отчасти уродливо. Если бы я был вами (и изменения не были бы слишком тяжелыми, в этом случае я бы раскошелил оригинальный проект), я бы посмотрел на изменение кода во время выполнения или статически с использованием аспектного плетения (аспектно-ориентированное программирование). AspectJ может выглядеть так, как если бы у него была четкая кривая обучения, но это отличный инструмент, который есть в вашем наборе инструментов и идеально соответствует вашим потребностям.

Другие вопросы по тегам