Java, как расширить стороннее приложение, сохраняя при этом совместимость

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

Конечно, самая большая проблема здесь — слияние с новыми обновлениями, если я сильно модифицировал исходный код. Я искал в Интернете лучшее решение и, среди прочего, нашел такие веб-сайты, как этот. Поэтому я решил не изменять исходный код настолько, насколько мог. Если я хочу добавить функциональность к существующей функции, я расширяю исходный класс, делаю так, чтобы приложение указывало на мой расширенный класс (что является достаточно небольшой модификацией, чтобы не создавать проблем) и реализую свои изменения.

Проблема здесь в свойствах стороннего класса, которые я также хочу расширить. В приведенном ниже примере class A вносятся некоторые изменения в свойство myClass, которые я хочу, чтобы он продолжал делать. Но class B, которое расширяет class A, содержит свойство с типом, который расширяет само MyClass, здесь MyCustomClass. Внутри class B я хочу, чтобы свойство myCustomClass было расширением myClass, любые изменения, внесенные в myClass, должны быть доступны в myCustomClass.

Насколько мне известно, приведение myClass к myCustomClass невозможно, так как myClass просто не относится к типу myCustomClass. Итак, какое лучшее решение здесь, которое сохраняет совместимость с class A и не требует простого копирования всего кода из class A, поскольку это создаст точно такие же проблемы, если class A получит обновление?

Вот пример кода, который должен лучше объяснить мой вопрос:

// Classes 'A' and 'MyClass' are 3rd party, I do not want to edit them
public class A {
    private MyClass myClass;

    A(){
        myClass = new MyClass();

        // Changes I want to keep doing
        doSomething();
    }

    public void doSomething(){
        // Do some calculations to myClass and modify it accordingly 
    }

    public MyClass getMyClass(){
        return myClass
    }
}


// Classes 'B' and 'MyCustomClass' are 1st party, I can do anything I want with them :)
public class B extends A{
    private MyCustomClass myCustomClass;        

    B(){
        super();

        myCustomClass = ?????????;


        // Extremely important message, this should keep working
        System.out.println(myCustomClass.verySpecialString);

        // Build further on made modifications, this should keep working too
        useModificationsByClassA();
    }

    public void useModificationsByClassA(){
        // Use result of class A's calculations in order to continue
    }
}


public class MyCustomClass extends MyClass{
    private String verySpecialString;

    MyCustomClass(){
        super();

        verySpecialString = "Hey!";
    }
}

person Samuël Visser    schedule 10.09.2018    source источник
comment
Я не верю, что твое желание возможно. В тот момент, когда вы расширяете кодовую базу, вы несете ответственность за сохранение изменений. Единственный способ синхронизировать их — зафиксировать ваши изменения в исходном проекте как с открытым исходным кодом. Таким образом, будущие выпуски также будут иметь ваши изменения. Это означает, что поставщики исходного кода должны будут согласиться на слияние ваших материалов.   -  person duffymo    schedule 10.09.2018
comment
@duffymo, мой проект довольно сильно отличается от исходного кода. Это не сработает. Если это невозможно, каково ближайшее возможное решение? Или единственное, что я могу сделать, это скопировать код class A?   -  person Samuël Visser    schedule 10.09.2018
comment
Скопировать код? Боже, нет. Вы можете использовать библиотеку как зависимость и писать свои собственные расширения. Вы признаете и принимаете риск того, что если библиотека внесет изменения, вам придется либо обновиться, чтобы сохранить синхронизацию, либо остаться на старой версии. Вы должны относиться к библиотеке как к неприкосновенной - ни в коем случае не изменяйте ее. Расширяйте, переносите, используйте композицию, но не расширяйте, редактируя их исходные файлы.   -  person duffymo    schedule 10.09.2018
comment
Да, я знаю, что мне придется проделать некоторую работу, чтобы быть в курсе. Это нормально. Конкретная проблема, которую я пытался решить, заключается в изменении расположения некоторых кнопок, желательно без редактирования исходного кода. Как бы я сделал это в расширении?   -  person Samuël Visser    schedule 10.09.2018
comment
Качающийся пользовательский интерфейс? О, парень. Теперь у вас есть проблема, потому что я сомневаюсь, что авторы много думали о точках расширения и облегчали вам выполнение таких действий. Вы можете написать свою собственную JPanel, дать ей кнопки, которые вы хотели бы иметь, и настроить их с помощью прослушивателей, которые делают вызовы в библиотеку, которую вы хотите, чтобы они имели. Но этот код полностью твой. Вы будете использовать их API только для добавления вызовов в прослушиватели кнопок для выполнения желаемых задач.   -  person duffymo    schedule 10.09.2018
comment
Вы заплатили им за эти расширения. Возможно, вам было бы лучше выяснить, как вы могли бы захотеть изменить их материал, и сделать дизайн и реализацию этих точек расширения частью новой реализации. Авторы не будут возражать, если вы вернетесь и заплатите за каждое новое расширение. Они бы предпочли, чтобы вы платили. Это зависит от вас, чтобы продумать требования.   -  person duffymo    schedule 10.09.2018
comment
Хорошо, спасибо за вашу помощь. Дает мне некоторое представление о возможных решениях. Думаю, мне будет чем заняться :)   -  person Samuël Visser    schedule 10.09.2018
comment
Спросите их, какой у них API. Он должен быть полностью отделен от любого пользовательского интерфейса. У вас должна быть возможность обернуть его как службу REST и вызывать из веб-интерфейса или мобильного интерфейса, если хотите. Я думаю, что привязывать себя к пользовательскому интерфейсу — плохой дизайн. Технологии пользовательского интерфейса приходят и уходят, но DSL для решения проблемы и общедоступный API сохраняют актуальность ваших материалов в течение длительного времени.   -  person duffymo    schedule 10.09.2018


Ответы (2)


В общем, в приложении, библиотеке и т. д. некоторые компоненты предназначены для расширения (потому что для), а некоторые другие компоненты действительно не предназначены для расширения.
Но модифицированный существующий компонент является как правило, это не способ расширить поведение приложения.
Потому что существующие компоненты, как правило, не предназначены для модификации. Их фактическая логика/обработка может сильно меняться от версии к другой. Они могут быть удалены из исходного кода или чего-либо еще... Таким образом, вам будет трудно объединить обновления исходного кода с исходным кодом измененной реализации приложения.

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

person davidxxx    schedule 10.09.2018
comment
Хорошо, но если он просто не предназначен для расширения, есть ли способ надежно добавить изменения? Или мне просто не повезло? Каков общий способ решения такой проблемы? - person Samuël Visser; 10.09.2018
comment
надежно? Никогда, как потенциально вам может понадобиться изменить много классов/файлов. Вам даже может понадобиться изменить тестовые классы (если они есть). Решений на самом деле не так много: разветвление приложения как внутреннего может быть хорошим в первый раз, но позже это может быть рискованной ставкой. - person davidxxx; 10.09.2018

Рассмотрите возможность использования шаблона адаптера или декоратора и используйте композицию вместо наследования. Например:

public class B {
    private final A a;        
    private final MyCustomClass myCustomClass;

    B(A a) {
        this.a = a;
        myCustomerClass = new MyCustomClass(a.getMyClass());
    }

   public void doSomething()  {
     a.doSomething();   // updates the state of a.myClass.
     myCustomerClass.doSomething();  // use the change of a.myClass to do more stuff         
   }
}

public class MyCustomClass {
   private final MyClass myClass;
   private String verySpecialString;

   public MyCustomClass(MyClass myClass) {
      this.myClass = myClass;
   }

   public void doSomething() {
      verySpecialString = "Hey!" + myClass.getSomeResult();
   }
}
person Andrew S    schedule 10.09.2018
comment
Спасибо. Я думал об этом решении раньше и пробовал его, но оно мне не очень понравилось, так как ломало много вещей. Но я думаю, что у меня не так много выбора, я собираюсь вложить еще немного, чтобы попытаться заставить это работать. Выберу ответ, если это то, что я в конечном итоге сделаю :) - person Samuël Visser; 10.09.2018
comment
@Andrew S. OP должен изменить существующие классы, чтобы сделать изменение эффективным. Таким образом, на самом деле композиция будет долго реализовываться, не защищая вас по-настоящему, потому что, если вы измените существующий класс другим классом (то есть классом композитора), все ссылки на существующие классы, которые вы изменили, должны быть изменены и отслеживаются для модификаций. . - person davidxxx; 10.09.2018