سؤال

أحاول كتابة نموذج المصنع لإنشاء إما MainMode أو TestMode في برنامجي.الكود الذي كنت أستخدمه سابقًا لإنشاء هذه الكائنات هو:

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

قد تقوم لعبتي (اللعب) إما بإنشاء كائن MainMode أو كائن TestMode اعتمادًا على قيمة منطقية (isMode).كما ترون أقوم بإضافة قيمة إضافية إلى كائن TestMode الخاص بي (randNo()).يتم استخدام هذه القيمة داخل TestMode للسماح للمستخدم بإدخال "الرقم العشوائي" الخاص به، بينما يتم إنشاء هذا بشكل عشوائي داخل مُنشئ MainMode.في هذا البرنامج، يعد كل من MainMode وTestMode فئات فرعية من فئة Game المجردة.

أريد الآن استبدال هذا السطر بنمط المصنع، على الرغم من أنني غير متأكد لأن منشئ 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.

الآن أريد استبدال هذا السطر بنمط المصنع ، على الرغم من أنني غير متأكد من أن مُنشئ الأزياء الخاصة بي يتطلب كائنًا إضافيًا وأنا غير متأكد من حيث أحتاج إلى تمرير هذه القيمة.

حسنًا، يمكنك التفكير في الأمر بهذه الطريقة:MainMode، وليس TestMode، هو الذي يقوم بعمل خاص.الشيء المميز الذي تفعله هو أن يتجاهل الرقم المحدد للتأكد من أنه عشوائي حقًا.بهذه الطريقة في التفكير في الأمر، فإن الوضع الرئيسي هو الذي يفعل شيئًا إضافيًا.

أو، بخلاف العشوائية، لا يختلف MainMode وTestMode، فربما تفكر في أنه يمكنك تحليل هذا التشابه في فئة واحدة، والتي يتم توفيرها بإحدى استراتيجيتين لحساب الأرقام العشوائية.قد تكون إحدى الاستراتيجيات عشوائية في الواقع، وقد تكون الأخرى منحرفة، مع نطاق عشوائي بقيمة واحدة فقط.

ولكن لنفترض أن هناك اختلافات أخرى بين 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(); }
}

الآن، إذا كان تطبيقك بالكامل ينشئ "وضعًا" واحدًا فقط، فلا حاجة إلى مصنع؛يمكنك استخدام المصنع عندما تحتاج إلى إنشاء نفس نوع الفصل مرارًا وتكرارًا؛المصنع هو في الواقع مجرد استراتيجية لإنشاء النوع المناسب من الفئة (الفرعية).

في كود الإنتاج، استخدمت المصانع حيث لدي فئة عامة تنشئ أشياء، وأحتاج إلى معرفة كيفية إنشاء الفئة الفرعية المناسبة لإنشائها؛أنا أمر في مصنع للقيام بذلك.

الآن نقوم بإنشاء نمط المصنع للوضع؛سيكون هذا مشابهًا بشكل مدهش لنمط الإستراتيجية:

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

الآن أنت تعرف عن نمط المصنع ونمط الإستراتيجية، وكيف أنهما متشابهان في "الشكل"، لكنهما مختلفان في كيفية استخدامهما:نمط المصنع هو كائن إنشاءي ويقوم بإرجاع كائن ليتم استخدامه؛الإستراتيجية هي كائن سلوكي، وعادةً ما يتم إنشاء المثيل بشكل صريح ويتم الاحتفاظ بمرجع للمثيل، لتغليف الخوارزمية.ولكن من حيث الهيكل، فهي متشابهة تماما.

يحرر:يسأل OP، في أحد التعليقات، "كيف يمكنني دمج هذا في واجهة المستخدم الرسومية الخاصة بي؟"

حسنًا، لا ينتمي أي من هذا إلى واجهة المستخدم الرسومية لبرنامجك، باستثناء "الوضع".يمكنك إنشاء استراتيجية ConcreteStrategy وتمريرها إلى المصنع المفضل في بعض إجراءات الإعداد، وربما تحديد أي منها سيتم استخدامه بناءً على وسيطات سطر الأوامر أو ملفات التكوين.في الأساس، ستختار المصنع الصحيح تمامًا كما تختار الفئة الصحيحة في مشاركتك الأصلية. مرة أخرى، إذا كنت تقوم بإنشاء شيء ما فقط، فلن تحتاج إلى مصنع؛المصانع مخصصة للإنتاج الضخم (أو إنشاء عائلات من أنواع الخرسانة ذات الصلة - على الرغم من أن هذا خارج نطاق هذا السؤال).

(لنفترض أن لدينا لعبة يمكن للمستخدم من خلالها الاختيار في سطر الأوامر ما إذا كان سيقاتل الروبوتات أو التنانين؛ثم نرغب في إنشاء مثيل لـ OponentFactory الذي ينتج المعارضين (واجهة)، مع الفئات المشتقة RobotOpponent وDragonOpponent، وتمرير هذا المصنع إلى جزء اللعبة الذي يولد NewOpponent().وبالمثل، قد يختار المستخدم خصومًا شجعانًا أو جبناء، وهو ما قمنا بإعداده كاستراتيجية.لا نحتاج إلى إنشاء المزيد من مثيلات الإستراتيجية، لأن الإستراتيجية عادة ما تكون غير فعالة (عديمة الحالة ومفردة).)

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

}

الفصل مجرد لإيقاف التهيئة.يمكنك تعديله لاستخدامه نهائيًا ثم إنشاء مُنشئ خاص.

ربما يمكن تغيير التعليمات البرمجية الخاصة بك إلى نمط المصنع.

شيء مثل:

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

ضع هذه الطريقة في مكان معقول (هذه الطريقة صعبة، ربما تكون ModeFactory ثابتة)

يفترض هذا أن MainMode وTestMode هما نوعان فرعيان من نفس النوع (فئات فرعية أو واجهة وضع التنفيذ)

الآن كل ما عليك فعله هو استدعاء ModeFactory.createMode(...) وتمرير القيمة المنطقية المناسبة.

تحرير (ردًا على تحديث OP):

يتم تقييم 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