Лучший способ реализовать шаблон Factory в Java

StackOverflow https://stackoverflow.com/questions/708336

  •  22-08-2019
  •  | 
  •  

Вопрос

Я пытаюсь написать шаблон Factory, чтобы создать в моей программе либо MainMode, либо TestMode.Код, который я ранее использовал для создания этих объектов, был:

play = (isMode) ? new MainMode(numberRanges, numberOfGuesses) : 
                  new TestMode(numberRanges, numberOfGuesses, randNo());

Моя игра (игра) создаст либо объект MainMode, либо объект TestMode в зависимости от логического значения (isMode).Как видите, я добавляю дополнительное значение в свой объект TestMode (randNo()).Это значение используется в TestMode, чтобы позволить пользователю вводить свое собственное «случайное число», тогда как в конструкторе MainMode оно генерируется случайным образом.В этой программе и MainMode, и TestMode являются подклассами абстрактного класса Game.

Теперь я хочу заменить эту строку шаблоном Factory, хотя я не уверен, поскольку моему конструктору TestMode требуется дополнительный объект, и я не уверен, куда мне нужно будет передать это значение.Если бы я собирался создать Фабрику, она должна была бы находиться в новом классе, вероятно, с именем GameFactory или ModeFactory или что-то в этом роде.

Как бы мне это сделать?

РЕДАКТИРОВАТЬ: Проблема здесь в том, что приведенный выше код находится в моем графическом интерфейсе, где находятся значения для NumberRanges, NumberOfGuesses и метода randNo().Я хочу создать класс Factory, но не могу передать эти значения, потому что randNo() активируется сам.Вот мой метод randNo().

private int randNo() {
    boolean isValidNumber = true;
    int testRandomNum = 0;
    while(isValidNumber) {
        try {
            testRandomNum = Integer.parseInt(JOptionPane.showInputDialog("Enter Random Number"));
            isValidNumber = false;
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(null, "Sorry, but the number you entered was invalid");
        }
    }

    return testRandomNum;
}

Проблема в том, что всякий раз, когда я передаю randNo(), он отображает JOptionPane.Как я уже говорил, графический интерфейс и логика разделены.Графический интерфейс находится в пакете графического интерфейса, а остальная часть кода — в пакете логики.

Это было полезно?

Решение

Обратите внимание, что некоторые другие ответы могут, возможно, описывать фабрики, но не описывают Фабрика GOF.

Теперь я хочу заменить эту строку на Заводская картина, хотя я не уверен как мой конструктор TestMode требует дополнительный объект, и я не уверен, где я Meme it нужно будет передать это значение.

Ну, можно подумать об этом так:MainMode, а не TestMode, выполняет особую функцию.Особая вещь, которую он делает, заключается в том, что игнорировать заданное число, чтобы гарантировать, что оно действительно случайное.Если рассуждать об этом, то MainMode делает что-то дополнительное.

Или, если кроме случайности MainMode и TestMode не различаются, то вы, возможно, думаете, что можете выделить это сходство в один класс, который предоставляет одну из двух стратегий для расчета случайных чисел.Одна стратегия на самом деле будет случайной, а другая — ошибочной, со случайным диапазоном всего в 1 значение.

Но давайте предположим, что между MainMode и TestMode есть и другие различия — предположительно TestMode выводит дополнительную отладку в System.out или что-то в этом роде.

Мы все еще можем учитывать, «как мы обеспечиваем случайность» от того, тестируем ли мы игру или играем в нее по-настоящему».Это ортогональный обеспокоенность.

Итак, теперь мы знаем, что помимо всего остального, что делает «Режим», он должен принимать стратегию случайности.Тогда мы могли бы, например, когда вам говорят, что стандартный случайный случай платформы на самом деле недостаточно случайный, вы можете заменить его на более лучший случайный случай.

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

Поэтому мы используем шаблон стратегии GOF для построения стратегий случайности:

interface RandomStrategy { 
  public double random();
}

public class NotSoRandom implements RandomStrategy {
  private double r;
  public NotSoRandom( final double r ) { this.r = r; }
  public double random() { return r; }
}

public class PlatformRandom implements RandomStrategy {
  public double random() { return Math.random(); }
}

Теперь, если все ваше приложение создает только один «Режим», фабрика не нужна;вы используете фабрику, когда вам нужно снова и снова создавать один и тот же тип класса;Фабрика на самом деле — это всего лишь стратегия создания правильного типа (под)класса.

В рабочем коде я использовал фабрики, где у меня есть некий общий класс, который создает вещи, и мне нужно сказать, как создать правильный подкласс для создания;Чтобы сделать это, я прохожу на фабрику.

Теперь мы создаем шаблон Factory для режима;это будет удивительно похоже на паттерн «Стратегия»:

abstract class Mode() {
 private RandomStrategy r;
 public Mode( final RandomStrategy r ) { this.r = r; }
 // ... all the methods a Mode has
}

public class MainMode implements Mode {
  public MainMode( final RandomStrategy r ) { super(r); }
}

public class TestMode implements Mode {
  public TestMode( final RandomStrategy r ) { super(r); }
}

interface ModeFactory{ 
  public Mode createMode( final RandomStrategy r );
}

public class MainFactory() {
  public Mode createMode( final RandomStrategy r ) {
      return new MainMode(r);
  }
}

public class TestFactory() {
  public Mode createMode( final RandomStrategy r ) {
      return new TestMode(r);
  }
}

Итак, теперь вы знаете о шаблонах «Фабрика» и «Стратегия» и о том, насколько они похожи по «форме», но различаются по способу использования:Фабричный шаблон является объектным созданием и возвращает объект, который будет использоваться;Стратегия является объектно-поведенческой, и экземпляр обычно создается явно, и на него сохраняется ссылка для инкапсуляции алгоритма.Но по структуре они очень похожи.

Редактировать:ОП спрашивает в комментарии: «Как мне интегрировать это в свой графический интерфейс?»

Что ж, ничего из этого не принадлежит графическому интерфейсу вашей программы, за исключением, возможно, файла «Mode.Вы должны создать ConcreteStrategy и передать его предпочтительной фабрике в какой-либо процедуре установки, возможно, определяя, что использовать, на основе аргументов командной строки или файлов конфигурации.по сути, вы выбираете правильную фабрику так же, как вы выбираете правильный класс в исходном сообщении. Опять же, если вы создаете что-то только одно, вам не нужна Фабрика;заводы предназначены для массового производства (или создания семейств родственных типов бетона - хотя это выходит за рамки данного вопроса).

(Предположим, у нас есть игра, в которой пользователь может выбрать в командной строке, сражаться ли с роботами или драконами;затем мы хотели бы создать экземпляр OpпонентFactory, который производит Оппонентов (интерфейс), с производными классами RobotOppent и DragonOppent, и передать эту фабрику той части игры, которая порождает NewOpComponent().Точно так же пользователь может выбрать храбрых или трусливых противников, которых мы настроим в качестве стратегии.Нам не нужно создавать больше экземпляров стратегии, поскольку стратегия обычно идемпотентна (без сохранения состояния и одноэлементная).)

static int main( String[] args ) {
// setup game world

final RandomStrategy r = "random".equals(args[0]) 
  ? new PlatformRandom() : new  NotSoRandom( Integer.intValue(args[0]) ) ;
// notice the simlarity to the code you originally posted;
// we factored out how to achieve "randomness" as a Strategy.

// now we will use our Strategy to setup our Factory;

final ModeFactory f = "test".equals(args[1])
  ? new TestFactory(r) : new MainFactory(r);
// also similar to your code
// we've just added an extra level of indirection: 
// instead of creating a Mode, we've created an object that can create Modes
//  of the right derived type, on demand.

// call something that uses our factory
functionThatRunsameAndNeedstoProduceModesWhenevertNeedsTo( f ); 

}

Другие советы

Вся суть Фабрики в том, что она должна иметь необходимое состояние для правильного создания вашей Игры.

Поэтому я бы построил такую ​​фабрику:

public class GameFactory {
   private boolean testMode;

   public GameFactory(boolean testMode) {
     this.testMode = testMode;
   }

   public Game getGame(int numberRanges, int numberOfGuesses) {
     return (testMode) ? new MainMode(numberRanges, numberOfGuesses) : 
       new TestMode(numberRanges, numberOfGuesses, getRandom());
   }

   private int getRandom() {
     . . . // GUI code here
   }
}

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

Попробуйте что-нибудь вроде:

abstract class ModeFactory {

    public static Mode getMode(isMode, numberRanges, numberofGuesses) {
        return isMode ? new MainMode(numberRanges, numberofGuesses) : new TestMode(numberRanges, numberOfGuesses, randNo());
    }

    public static Mode getMode(isMode, numberRanges, numberofGuesses, someNumber) {
        return isMode ? new MainMode(numberRanges, numberofGuesses) : new TestMode(numberRanges, numberOfGuesses, someNumber);
    }

}

Класс является абстрактным только для того, чтобы остановить инициализацию.Вы можете изменить его, чтобы использовать Final, а затем создать частный конструктор.

Ваш код, вероятно, можно изменить на фабричный шаблон.

Что-то вроде:

public static Mode createMode(boolean isMainMode)
{
    if(isMainMode) return new MainMode(...);
    return new TestMode(...);
}

Разместите этот метод где-нибудь разумным (это сложно, возможно, это статическая ModeFactory)

Предполагается, что MainMode и TestMode являются подтипами одного и того же типа (подклассы или интерфейс режима реализации).

Теперь все, что нужно сделать игре, — это вызвать ModeFactory.createMode(...) и передать соответствующее логическое значение.

Изменить (в ответ на обновление ОП):

Ваш rand() оценивается до вызова фактического конструктора и представляет графический интерфейс.Это то, что вы имеете в виду под активацией?

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

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

interface ModeFactory {
    Mode createMode(int numberRanges, int numberOfGuesses);
}

class MainModeFactory implements ModeFactory {
    Mode createMode(int numberRanges, int numberOfGuesses) {
        return new MainMode(numberRanges, numberOfGuesses);
    }
}

class TestModeFactory implements ModeFactory {
    Mode createMode(int numberRanges, int numberOfGuesses) {
        return new TestMode(numberRanges, numberOfGuesses, randNo());
    }
}

...

play = modeFactory.createMode(numberRanges, numberOfGuesses);

Таким образом, при запуске вы создаете соответствующую фабрику режимов, передавая ее туда, где необходимо создать игру.

Очень просто, ВСЕГДА ИСПОЛЬЗУЙТЕ ПАРАМЕТР, если параметр не используется, отправьте null, если у вас есть несколько параметров для других «Режимов», инкапсулируйте их в один параметр.

Если вы сразу используете фабричный метод, который создаст для вас класс с заданным именем, попробуйте следующее:

public static MyInterface createClass(String name) throws IllegalAccessException,
        InstantiationException, ClassNotFoundException {
    try {
        Class myClass = Class.forName(name);
        MyInterface myObj = (MyInterface) myObj.newInstance();
        return myObj;
    } catch (ClassNotFoundException ex) {
        logger.error("Could not find a class {}", name);
        throw ex;
    } catch (InstantiationException e) {
        logger.error("Class must be concrete {}", name);
        throw e;
    } catch (IllegalAccessException e) {
        logger.error("Class must have a no-arg constructor {}", name);
        throw e;
    }
}

Что вам действительно нужно сделать, так это создать фабрику, которая возвращает вам объект абстрактного класса или интерфейса (конечно, их разработчиков).Затем в фабричном методе вы решаете, какой реализацию выбрать.Если вы выберете абстрактный класс, вы сможете реализовать в нем некоторую общую логику и оставить другие методы нереализованными (объявив их абстрактными).Вы бы позволили конкретным спусковым устройствам реализовывать их в зависимости от их потребностей.Это фабричный шаблон проектирования:

public class GridManagerFactory {
    public static AbstractGridManager getGridManager(LifecicleAlgorithmIntrface lifecicleAlgorithm, String... args){
        AbstractGridManager manager = null;

        // input from the command line
        if(args.length == 2){
            CommandLineGridManager clManager = new CommandLineGridManager();
            clManager.setWidth(Integer.parseInt(args[0]));
            clManager.setHeight(Integer.parseInt(args[1]));
            // possibly more configuration logic
            ...
            manager = clManager;
        } 
        // input from the file
        else if(args.length == 1){
            FileInputGridManager fiManager = new FileInputGridManager();
            fiManager.setFilePath(args[0]);
            // possibly more method calls from abstract class
            ...
            manager = fiManager ;
        }
        //... more possible concrete implementors
        else{
            manager = new CommandLineGridManager();
        }
        manager.setLifecicleAlgorithm(lifecicleAlgorithm);
        return manager;
    }
}

Общая логика абстрактного класса доступна его потомкам:

public abstract class AbstractGridManager {
    private LifecicleAlgorithmIntrface lifecicleAlgorithm;
    // ... more private fields

    //Method implemented in concrete Manager implementors 
    abstract public Grid initGrid();

    //Methods common to all implementors
    public Grid calculateNextLifecicle(Grid grid){
        return this.getLifecicleAlgorithm().calculateNextLifecicle(grid);
    }

    public LifecicleAlgorithmIntrface getLifecicleAlgorithm() {
        return lifecicleAlgorithm;
    }
    public void setLifecicleAlgorithm(LifecicleAlgorithmIntrface lifecicleAlgorithm) {
        this.lifecicleAlgorithm = lifecicleAlgorithm;
    }
    // ... more common logic and geter-seter pairs
}

Конкретному разработчику необходимо реализовать только метод, объявленный абстрактным:

  public class FileInputGridManager extends AbstractGridManager {

        private String filePath;

        @Override
        public Grid initGrid() {
            return this.initGrid(this.getFilePath());
        }

        public Grid initGrid(String filePath) {
            List<Cell> cells = new ArrayList<>();
            char[] chars;
            File file = new File(filePath); // for ex foo.txt
            // ... more logic
            return grid;
        }
    }

Получатель AbstractGridManager вызовет его методы и получит логику, реализованную в конкретных потомках (и частично в методах абстрактного класса), не зная, какую конкретную реализацию он получил.Это также известно как инверсия управления или внедрение зависимостей.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top