簡體   English   中英

創建大量類之一的優雅方法

[英]Elegant way to create one of a large number of classes

就上下文而言,我正在嘗試使游戲類似於Pokemon。 您可以獲取,訓練和對抗怪物。

每種怪物都是從抽象基類繼承的類(因此它們可以具有獨特的行為),並且希望在整個游戲中會有很多不同的物種。 例如:

abstract class Monster {
  int hp;
  void attack();
  //Etc.
}

public class FireBreathingDragon extends Monster {
  static String species_name = "Fire Breathing Dragon";
  //Blah
}

因此,當玩家探索時,他們會隨機遇到某個區域本地的怪物。 然后,游戲需要從居住在該地區的物種列表中隨機創建一個怪物。 現在,要使該代碼在區域之間可重用(並使其易於在代碼的其他位置動態創建怪物),我不想將可能的代碼硬編碼到該區域中。 取而代之的是,我想我想要一種類似於工廠的產品,該產品可以按需創建給定物種的怪物,例如:

public class MonsterFactory {
  Monster createMonster(
    String species_name,
    //Possibly other paramters
  );
}

問題是,當您(可能)擁有數十個或數百個不同的Monster類時,以“不錯”或“優雅”的方式實現createMonster 當然,您可以使用非常長的if-else if-elseswitch語句,但是編寫和擴展這太可怕了。 有沒有很好的方法可以做到這一點? 如果在添加更多怪物時相對容易擴展,那也很好。

還是我應該使用一些完全不同的設計?

免責聲明:我的Java有點生銹,語法可能並不完美,對此感到抱歉。

您可以在List注冊所有Monster實現類。

List<Class<? extends Monster>> monsterTypes = new LinkedList<>();
monsterTypes.add(FireBreathingDragon.class);
// more

這不必進行硬編碼。 您可以將其外部化為某些XML,Json或其他文件格式。

然后,工廠實例或類可以從列表中以隨機索引選擇怪物類型。 然后,您可以使用反射實例化類型。

最簡單的解決方案是擁有一個數據驅動的怪物類。 這意味着您只有一個類別(或少量),並且該類別可以用於具有不同屬性和能力的各種各樣的怪物。

您可能有一個CSV文件,其中包含每個物種以及該物種的所有屬性和能力。 這樣,您可以通過在電子表格中添加一行來添加種類。

該解決方案使用類工廠, 沒有任何形式的反思。 為什么這在問題中很重要(“最優雅的方式”)? 與另一位貢獻者進行了非常有趣的交流:我引用了Sun / Oracle的Reflection API教程: “反射功能強大,但不應任意使用。如果可以在不使用反射的情況下執行操作,那么最好避免使用它。 ” 為了證明這一點,Sun / Oracle的作者訴諸於Java內部的極端技術原因。 我同意他們的觀點,但是我的主要理由是長期的代碼維護和工具。 反射的主要替代方法是什么? 基於注釋的自動代碼生成。 我不能在這么短的空間里做類似的事情,但是我可以產生或多或少應該是結果代碼:

public interface Factory<T> {
    T make();
}
public static class BasicMonster {
}
public static class Monster1 extends BasicMonster {
    public static final Factory<Monster1> getFactory() {
        return new Factory<Monster1>() {
            public Monster1 make() { return new Monster1() ;  }
        };
    }
}
public static class Monster2 extends BasicMonster {
    public static final Factory<Monster2> getFactory() {
        return new Factory<Monster2>() {
            public Monster2 make() { return new Monster2() ;  }
        };
    }
}
List<Factory<? extends BasicMonster>> monsterFactories= new ArrayList<Factory<? extends BasicMonster>>();
{
    monsterFactories.add(Monster1.getFactory());
    monsterFactories.add(Monster2.getFactory());
}
...
BasicMonster newMonster= monsterFactories.get(aRandomOne).make() ;

形式static class用於指示不打算用作內部類的類。

即使列表MonsterFactories是通過反射初始化的,代碼中工廠對象的存在也允許比反射構造函數調用更高級別的靜態分析。

您可以將所有類放在特定的程序包中,然后在該目錄中掃描類文件,加載它們,並跟蹤擴展Monster的那些文件。 您甚至可以定義一些自定義注釋來幫助管理此問題,例如@IgnoreMonster可以暫時禁用某些注釋,而不必更改文件的位置。 這類似於例如Hibernate掃描源以查找實體映射的方式。

這是一個例子。 所有的Monster類都放在dload.monsters包中。 首先,這是我在此示例中使用的基類:

package dload.monsters;

public abstract class Monster {

    public abstract String getName ();

}

然后,一個MonsterFactory掃描dload.monsters包中的所有類(對不起,它有點草率,我跳過了異常處理):

package dload.monsters;
import java.io.*;
import java.net.*;
import java.util.*;

public class MonsterFactory {

    private static final List<Class<? extends Monster>> monsterClasses = new ArrayList<Class<? extends Monster>>();
    private static final Random random = new Random();

    @SuppressWarnings("unchecked") // <- for monsterClasses.add((Class<? extends Monster>)cls);
    public static void loadMonsters () throws Exception {

        // in this example, Monster is in the same package as the monsters. if
        // that is not the case, replace "." with path relative to Monster.
        File folder = new File(Monster.class.getResource(".").toURI());

        for (File f : folder.listFiles()) {
            if (f.getName().endsWith(".class")) {
                String name = f.getName().split("\\.")[0];
                // replace "dload.monsters." below with package monsters are in
                Class<?> cls = ClassLoader.getSystemClassLoader().loadClass("dload.monsters." + name);
                // if Monster is not in same package as monsters, you can remove
                // cls.equals(Monster.class) check. this check makes sure the loaded
                // class extends Monster, but is not the Monster class itself (since
                // its also in that package).
                if (Monster.class.isAssignableFrom(cls) && !cls.equals(Monster.class)) {
                    System.out.println("Found " + cls.getSimpleName());
                    monsterClasses.add((Class<? extends Monster>)cls);
                }
            }
        }

        // at this point all Class's for monsters are in monsterClasses list.

    }

    public static Monster randomMonster () throws Exception {

        // choose a class at random
        int n = random.nextInt(monsterClasses.size());
        Class<? extends Monster> cls = monsterClasses.get(n);

        // instantiate it
        return cls.newInstance();

    }

}

然后,當您要使用它時:

public static void main (String[] args) throws Exception {

    // load monsters; only need to do this once at startup
    MonsterFactory.loadMonsters();

    // create 10 random monsters
    for (int n = 0; n < 10; ++ n) {
        Monster m = MonsterFactory.randomMonster();
        System.out.println("name is " + m.getName());
    }

}

請注意,您隨時可以檢查怪物的Class以獲取相關注釋。

如果已經加載了類(如果從未使用過或未顯式加載過這些類),則另一個選擇是使用Instrumentation.getAllLoadedClasses()獲取當前已加載的所有類的列表,然后掃描所有類尋找可分配給Monster

注意:我確實覺得有一種更干凈的方式來進行實際掃描,並且我還沒有在JAR中進行測試。 歡迎提出建議。

話雖這么說, 如果一個怪物的行為可以完全由數據定義,我也支持並推薦上述數據驅動方法。

您應該看一下笛卡爾積算法 它將生成產品的每種組合,然后您可以隨機選擇一種。

本質上,該算法將采用屬性數組並創建不同屬性的唯一組合,並將其添加到數組中。 然后,當您創建敵人時,可以從陣列中隨機選擇一個鍵。 這樣,每個敵人都有隨機的機會擁有任意數量的屬性。

具有提供怪物的接口或基類。

我以為我應該包含這個Wiki位,“工廠方法模式是一種面向對象的創建設計模式,用於實現工廠的概念並處理創建對象(產品)的問題,而無需指定將要使用的對象的確切類。被創造出來。”

這使您可以排他地使用超類方法或接口,而無需了解接口的特定子類型。 這很重要,因為您不能調用new base_monster() ;

abstract class base_monster  {
  abstract base_monster factory();
}

/// make sure every monster has a name...
//
abstract class Monster extends base_monster { 
   String name; 
   static int object_counter = 0;

    Monster factory() { 
       name = Integer(object_counter).toString(); 
       object_counter();
       return this; 
    }

    /// this class has a useful setter
    void object_counter( int c ) { object_counter++; out.println( object_counter );    }
}

class Griffon extends Monster {
  Monster factory() { return new Griffon(); }
}


class Harpy extends Monster {
  Harpy() { name = "Grizelda WhuttleThut III"; }
  Harpy factory() { return new Harpy(); }
}


class BlackHarpy  extends Harpy {
  BlackHarpy  factory() { super.factory(); return new BlackHarpy(); }
}


// we assume that each class has a default constructor. But, 
// if the array is filled with monsters of different subclasses we
// would have to use reflection or nasty instanceof switches to be
// able to call a (specific) defined constructor.

ArrayList<Monster> monsters = new ArrayList<Monster>();    

monsters.add( new BlackHarpy() );
for( int I = 0; I < ave_monsters_appearing; I++ )
    monsters.add( new Harpy() );
//
// an array of ten harpies and a boss Harpy.

///
// how can this array of monsters be copied into the other array?
// (we want object copies, not reference copies)
///

ArrayList<Monster> local_monsters = new ArrayList<Monster>();    

/// solution: use the factory method
for( Monster m : monsters ) 
   local_monsters.add( m.factory() ); 


希望這解決了沒有靜態方法的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM