Лучший способ реализовать Factory Pattern в Java - PullRequest
5 голосов
/ 02 апреля 2009

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

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

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

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

Как бы я поступил об этом?

РЕДАКТИРОВАТЬ: Проблема здесь в том, что приведенный выше код находится в моем GUI, где значения для 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. Как я уже сказал, графический интерфейс и логика разделены. GUI находится в пакете GUI, а остальная часть кода находится в пакете логики.

Ответы [ 8 ]

12 голосов
/ 02 апреля 2009

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

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

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

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

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

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

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

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

Итак, мы используем шаблон стратегии 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 для режима Mode; это будет удивительно похоже на шаблон Стратегии:

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);
  }
}

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

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

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

(Предположим, у нас есть игра, в которой пользователь может выбрать в командной строке, сражаться ли с роботами или драконами; тогда мы хотим создать экземпляр OpponentFactory, который создает Opponent (интерфейс), с производными классами RobotOpponent и DragonOpponent, и передать эту фабрику той части игры, которая вызывает spawnsNewOpponent (). Аналогично, пользователь может выбрать смелых или трусливых противников, которых мы бы определили как Стратегию. Нам не нужно создавать больше экземпляров Стратегии, так как Стратегия обычно идемпотент (без состояний и синглтон).

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 ); 

}
1 голос
/ 02 апреля 2009

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

Итак, я бы построил такой завод:

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.

0 голосов
/ 03 июня 2014

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

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 будет вызывать методы для него и получать логику, реализованную в конкретных потомках (и частично в методах абстрактного класса), не зная, какую конкретно реализацию он получил. Это также известно как инверсия управления или внедрение зависимости

0 голосов
/ 09 июля 2013

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

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;
    }
}
0 голосов
/ 27 июня 2011

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

0 голосов
/ 02 апреля 2009
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);

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

0 голосов
/ 02 апреля 2009

Попробуйте что-то вроде,

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, а затем создать приватный конструктор.

0 голосов
/ 02 апреля 2009

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

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

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

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

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

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

Редактировать (в ответ на обновление OP):

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

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

С другой стороны (модель вызывает ваш GUI) сложнее и, вероятно, плохая идея.

...