Что такое хороший способ сообщить объектам низкого уровня, какие фабрики использовать? - PullRequest
0 голосов
/ 05 октября 2018

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

Рассмотрим следующий упрощенный пример:

У меня есть класс MainProgram (я просто сделал это, чтобы представить, чтов моей программе есть другой код ...) В какой-то момент во время выполнения я хочу создать IGeneticAlgorithm с абстрактной фабрикой:

public class MainProgram{

    private AbstractGeneticAlgorithm geneticAlgorithm;
    private IGeneticAlgorithmFactory geneticAlgorithmFactory;

    public MainProgram(IGeneticAlgorithmFactory geneticAlgorithmFactory){
        this.geneticAlgorithmFactory = geneticAlgorithmFactory;
    }

    private void makeGeneticAlgorithm(){
        geneticAlgorithm = geneticAlgorithmFactory.getInstance();
    }

    public static void main(String[] args){
        MainProgram mainProgramm = new MainProgram(new FastGeneticAlgorithmFactory());
        //...
    }

}


public interface IGeneticAlgorithmFactory{
    public IGeneticAlgorithm getInstance();
}

public class FastGeneticAlgorithmFactory implements IGeneticAlgorithmFactory{
    public IGeneticAlgorithm getInstance(){
        return new FastGeneticAlgorithm();
    }
}


public abstract class AbstractGeneticAlgorithm{

    private IIndividual individual;
    private IIndividualFactory individualFactory;

    private void makeIndividual(){
        individual = individualFactory.getInstance();
    }

    //...
}

В какой-то момент во время выполнения я хочу создать II-индивид в моем GeneticAlgorithm.IIndividual не может быть создан при запуске.Необходимость иметь возможность создавать экземпляр индивида во время выполнения проистекает из того, как работают генетические алгоритмы, когда в основном после каждого шага селекции-рекомбинации-мутации должны создаваться экземпляры.(Для получения дополнительной информации см. https://en.wikipedia.org/wiki/Genetic_algorithm). Я решил предоставить здесь здесь AbstractGeneticAlgorithm только один отдельный индивид, чтобы этот пример был простым.

public class FastGeneticAlgorithm implements AbstractGeneticAlgorithm{

    private IIndividual individual; 
    private IIndividualFactory individualFactory;

}


public interface IIndividualFactory{
    public IIndividual getInstance();
}

public class SmallIndividualFactory implements IIndividualFactory{
    public IIndividual getInstance(){
        return new SmallIndividual();
    }

    //...
}


public interface IIndividual{
    //...
}

public class SmallIndividual implements IIndividual{
    //...
}

Создание SmallIndividualFactory статической переменной в FastGeneticAlgorithm, похоже, неМне нравится хорошая практика. И передача SmallIndividualFactory в Main, так что Main может передать его в FastGeneticAlgorithm, также кажется неправильной.

У меня вопрос, как это решить? Спасибо.

1 Ответ

0 голосов
/ 07 октября 2018

Когда дело доходит до внедрения зависимостей, шаблон абстрактной фабрики часто используется слишком часто.Это не означает, что это плохой шаблон сам по себе, но во многих случаях есть более подходящие альтернативы шаблону абстрактной фабрики.Это подробно описано в Внедрение зависимостей в .NET, второе издание (глава 6.2), где описано, что:

  • Абстрактные фабрики не должны использоваться для создания недолговечных,зависимости с состоянием, поскольку потребитель зависимости должен не обращать внимания на время ее существования;с точки зрения потребителя, концептуально должен быть только один экземпляр услуги.
  • Абстрактные фабрики часто являются нарушениями принципа инверсии зависимости (DIP), потому что их дизайн часто не устраивает потребителя, в то время как DIP заявляет: «тезисы принадлежат верхним уровням / уровням политики», что означает, что потребитель абстракции должен диктовать свою форму и определять абстракцию таким образом, который больше всего соответствует его потребностям.Разрешение потребителю зависеть как от фабричной зависимости, так и от производимой ею зависимости усложняет потребителя.

Это означает, что:

  • Абстрактные фабрики с методом создания без параметров должны быть предотвращеныпотому что это подразумевает, что зависимость недолговечна, а ее срок жизни контролируется потребителем.Вместо этого абстрактные фабрики должны создаваться для зависимостей, которые концептуально требуют создания данных времени выполнения (предоставляемых потребителем).
  • Но даже если фабричный метод содержит параметры, необходимо позаботиться о том, чтобы абстрактныйФабрика действительно требуется.Шаблон Proxy часто (но не всегда) лучше подходит, потому что он позволяет потребителю иметь единственную зависимость вместо зависимости как от фабрики, так и от ее продукта.

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

Поскольку корень компоновки принимает зависимость от всех других модулей в системеобычно нет смысла потреблять абстрактные фабрики в корне композиции.Например, если вы определили абстракцию IXFactory для получения IX зависимостей, но корень композиции является единственным потребителем абстракции IXFactory, вы отделяете что-то, что не требует разделения: корень композиции по сути знаето любой другой части системы в любом случае.

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

Это сложнодля меня, чтобы понять, требуют ли ваши реализации IIndividual фабрики (это было по крайней мере 14 лет назад, с тех пор как я реализовал генетический алгоритм в университете), но они больше походят на данные времени выполнения, а не на «реальные» зависимости.Таким образом, фабрика может иметь здесь смысл, хотя и проверяет, должно ли их создание и реализация скрываться за абстракцией.Я мог бы представить, что приложение достаточно слабо связано, когда FastGeneticAlgorithm создает SmallIndividual экземпляров напрямую.Это, однако, просто дикая догадка.

Кроме того, лучше всего применять Constructor Injection.Это предотвращает временную связь .Кроме того, воздержитесь от указания зависимостей реализаций в определенных абстракциях, как это делает ваш AbstractGeneticAlgorithm.Это делает абстракцию Leaky Abstraction (что является нарушением DIP).Вместо этого объявите зависимости, объявив их в качестве аргументов конструктора в реализации (FastGeneticAlgorithm в вашем случае).

Но даже при наличии IIndividualFactory ваш код можно упростить, следуя следующим рекомендациям:

// Use interfaces rather than base classes. Prefer Composition over Inheritance.
public interface IGeneticAlgorithm { ... }
public interface IIndividual { ... }
public interface IIndividualFactory {
    public IIndividual getInstance();
}

// Implementations
public class FastGeneticAlgorithm implements IGeneticAlgorithm {
    private IIndividualFactory individualFactory;

    // Use constructor injection to declare the implementation's dependencies
    public FastGeneticAlgorithm(IIndividualFactory individualFactory) {
        this.individualFactory = individualFactory;
    }
}

public class SmallIndividual implements IIndividual { }
public class SmallIndividualFactory implements IIndividualFactory {
    public IIndividual getInstance() {
        return new SmallIndividual();
    }
}

public static class Program {
    public static void main(String[] args){
        AbstractGeneticAlgorithm algoritm = CreateAlgorithm();
        algoritm.makeIndividual();
    }

    private AbstractGeneticAlgorithm CreateAlgorithm() {
        // Build complete object graph inside the Composition Root
        return new FastGeneticAlgorithm(new SmallIndividualFactory());
    }
}
...