2013-08-13 3 views
1

컨텍스트에서는 포켓몬의 라인을 따라 게임을 만들려고합니다. 당신은 괴물을 얻고, 훈련시키고 싸우게됩니다.많은 수의 클래스 중 하나를 만드는 우아한 방법

괴물의 각 종은 추상적 인 기본 클래스를 상속하는 클래스이므로 (고유 한 동작을 가질 수 있음) 게임 전체에 걸쳐 매우 많은 수의 다양한 종이있을 것으로 기대됩니다. 예 :

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

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

플레이어가 탐색 중일 때 영역에 국한하는 몬스터가 무작위로 나옵니다. 게임은 그 지역에 사는 종의 목록에서 무작위로 괴물을 만들어야합니다. 이제이 코드를 영역간에 재사용 할 수있게 만들고 코드의 다른 위치에서 동적으로 괴물을 만들 수있게 만들었습니다.이 영역에 가능성을 하드 코딩하고 싶지는 않습니다. 대신에 나는 수요에 주어진 종의 몬스터를 만드는 공장의 라인을 따라 뭔가, 뭔가를하고 싶은 생각 : 문제는 그 다음 "좋은"또는 "고급"에서 createMonster을 구현하고

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

(잠재적으로) 수십 수백 가지의 몬스터 클래스가있을 때. 물론 매우 긴 if-else if-else 또는 switch 문을 사용할 수도 있지만 작성하고 확장하는 것은 끔찍한 일입니다. 이것을 할 수있는 좋은 방법이 있습니까? 몬스터를 추가 할 때 확장하기가 비교적 쉽다면 좋을 것입니다.

아니면 대신 완전히 다른 디자인을 사용해야합니까?

면책 조항 : 내 Java는 녹슨 것이지만 구문이 완벽하지 않을 수 있습니다. 죄송합니다.

+1

왜 많은 수업이 있습니까? 종의 이름과 유형과 같은 필드가있는 몬스터 클래스를 하나 가질 수 있으며 다른 클래스를 가져야 할 수도 있습니다. – taronish4

+1

내가 권하는 것은 Monster 클래스가 하나 밖에 없지만 동적으로 stat 파일을로드하는 것입니다 (설정 파일 등에서). 그런 다음 펑터 (하나의 메서드 만 가진 클래스)를 사용하여 attack() 작업을 재정의 할 수 있습니다. 예를 들어, WaterMonster는 WaterAttack이라고 불리는 AttackAction을 가지며, WaterMonster의'attack' 메소드는'AttackAction'에 위임합니다. –

+0

모든 종류의 포켓몬을 파일 (CSV, XML, JSON, YAML, ... 등)에 저장하는 것을 고려 했습니까? 그런 다음 코드는이 파일을 한 번 읽으면 모든 유형을 초기화 한 다음 'createMonster'에서 동적으로 올바른 것을 만들 수 있습니다. – andersschuller

답변

4

모든 Monster 구현 클래스를 List에 등록 할 수 있습니다.

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

이것은 하드 코드 될 필요가 없습니다. XML, Json 또는 다른 파일 형식으로 외부화 할 수 있습니다.

공장 인스턴스 또는 클래스는 목록에서 임의의 인덱스로 몬스터 유형을 선택할 수 있습니다. 그런 다음 리플렉션을 사용하여 유형을 인스턴스화 할 수 있습니다.

+0

유망한 것으로 들립니다. 나는 당신이 자바에서 이런 식으로 클래스들을 다룰 수 있다는 것을 몰랐다. 누구든지 이것에 대해 배울 좋은 자원을 알고 있습니까? 제네릭이 맞습니까? 나는 그것에 대해 많이 모른다. – mrhthepie

+0

[이 기사] (http://docs.oracle.com/javase/tutorial/reflect/)는 공식 자습서입니다. –

+1

@mrhthepie "Generics"는'List' 매개 변수화 된 타입을 가리 킵니다. 'FireBreathingDragon.class'의 사용은 generics가 아닙니다. Java의 reflection 지원 부분입니다. –

2

가장 간단한 해결책은 데이터 기반 괴물 클래스를 만드는 것입니다. 이것은 하나의 클래스 (또는 소수)를 가짐을 의미하며이 클래스는 속성과 능력이 다른 다양한 몬스터에 사용할 수 있습니다.

당신은 각 종과 그 종족의 모든 속성과 능력을 포함하는 CSV 파일을 가질 수 있습니다. 이렇게하면 스프레드 시트에 선을 추가하여 종을 추가 할 수 있습니다.

+0

글쎄요, 그 아이디어는 괴물의 종류가 다르고 자신 만의 수업을 보증 할만큼 충분히 독특하다는 아이디어였습니다. 이 방법을 사용하면 공장을 크게 단순화 할 수 있습니다. 그러나 괴물을 만드는 사람은 누구나 미리 그들에 대해 더 많이 알 필요가 있습니다. – mrhthepie

+0

@mrhthepie 예를 들어 주시겠습니까? AFAIK, 이것이 바로이 유형의 게임이 개발되는 방식입니다. 예 : 디아블로. –

+0

@mrhthepie 또한 새로운 생물을 추가하는 것은 재 컴파일/재배포를 의미합니다. – asawyer

0

모든 클래스를 특정 패키지에 넣은 다음 해당 클래스 파일을 검색하여로드하고 Monster 확장 파일을 추적 할 수 있습니다. 이를 관리 할 수있는 맞춤 주석을 정의 할 수도 있습니다 (예 : @IgnoreMonster을 사용하면 파일의 위치를 ​​변경하지 않고 일부 파일을 일시적으로 비활성화 할 수 있습니다. 이것은 예를 들어, Hibernate는 엔티티 매핑을 찾기 위해 소스를 스캔한다.

다음은 예입니다. 몬스터 클래스는 모두 패키지 dload.monsters에 배치됩니다.그런 다음

package dload.monsters; 

public abstract class Monster { 

    public abstract String getName(); 

} 

dload.monsters 패키지의 모든 클래스를 스캔합니다 MonsterFactory (유감의 약간의 실수, 나는 예외 처리에 밖으로 전혀 부족함이) : 첫째, 여기에 내가이 예를 들어 사용하고 기본 클래스는 다음과 같습니다 언제든지 당신이 몬스터의 Class F를 확인할 수

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

} 

참고 : 당신이 그것을 사용하려면 다음

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

    } 

} 

, 또는 관련 주석.

또 다른 옵션은 클래스가 이미로드 된 경우 (사용되지 않았거나 명시 적으로로드 된 적이없는 경우) Instrumentation.getAllLoadedClasses()을 사용하여 현재로드 된 모든 클래스의 목록을 가져온 다음 모든 클래스를 스캔하는 것입니다 Monster에 할당 할 수있는 것을 찾고 있습니다.

참고 : 실제로 스캔을 수행하는 더 깨끗한 방법이있는 것처럼 느껴지고 JAR에서 테스트하지 않았습니다. 제안을 환영합니다.

몬스터의 행동을 데이터로 완전히 정의 할 수있는 경우, 위에서 설명한 데이터 기반 접근 방식도 지원하고 권장합니다.

+0

흥미로운 아이디어이며, 많은 노력을 기울인 것처럼 보입니다. 감사! – mrhthepie

0

Cartesian Product Algorithm을 살펴 봐야합니다. 제품의 모든 조합을 생성 할 것이므로 임의로 선택할 수 있습니다.

기본적으로 알고리즘은 속성 배열을 가져 와서 서로 다른 속성의 고유 한 조합을 만들어 배열에 추가합니다. 그런 다음 적을 생성 할 때 배열에서 임의로 키를 선택할 수 있습니다. 그런 식으로 모든 적들은 무작위로 많은 수의 속성을 가질 수 있습니다.

+0

나는이 대답을 이해하지 못한다. 나는 두려워한다. 내가 너의 제품을 가져 가라고 제안하는 바? – mrhthepie

+0

@Matthew R.이 주석을 대답으로 이동하는 것을 고려하십시오. –

+0

@Mario 좋은 전화 –

0

에는 괴물을 제공하는 인터페이스 또는 기본 클래스가 있습니다.

나는이 wiki-bit를 포함시킬 것이라고 생각했다. "팩토리 메소드 패턴은 정확한 개념을 지정하지 않고 객체 개념을 구현하고 객체를 생성하는 문제를 다루는 객체 지향적 인 디자인 패턴입니다. 생성 될 개체의. "

이렇게하면 인터페이스의 특정 하위 유형을 알 필요가없이 수퍼 클래스 메소드 또는 인터페이스를 독점적으로 사용할 수 있습니다. 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()); 

.
. 이것이 정적 메서드가없는 문제를 해결하길 바랍니다.

+1

그리고 어떻게 각 구체적인 수업의 첫 괴물을 만드나요? 'make()'는 인스턴스 메소드이므로 미리 존재하는 객체가 필요합니다. –

+0

좋은 지적 Mario, 나는 포스트와 추상 수정자를 편집 할 것이다. – Dru

+0

안녕하세요! 공상, 그 중 하나를 작동하지 않습니다! 내가 미래에 내 게시물을 테스트해야 할거야, 내게 당신이 기본 생성자가 없을 것이라고 생각하지 마십시오. – Dru

1

이 솔루션은 클래스 팩토리 을 사용합니다. 질문의 맥락에서 이것이 중요한 이유는 무엇입니까 ("우아한")? 다른 참여자와의 매우 흥미로운 교환 : Sun/Oracle의 인용문 Reflection API Tutorial "반사는 강력하지만 무차별로 사용해서는 안됩니다. 반사를 사용하지 않고 조작을 수행 할 수 있다면 사용하지 않는 것이 좋습니다." 이를 정당화하기 위해 썬/오라클의 저자는 자바 내부의 기술적 인 이유에 의지합니다. 나는 그것들에 동의하지만, 나의 주된 이유는 장기적인 코드 유지 보수와 툴링이다. 리플렉션의 주요 대안은 무엇입니까? 주석 기반 자동 코드 생성.나는이 짧은 공간에서 같은 일을 할 수는 없지만, 내가해야 무엇을 생산할 수있는, 더 많거나 적은, 결과 코드 : 클래스를 표시하는 데 사용

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가 리플렉션을 통해 초기화 된 경우에도 코드에 팩토리 객체가 있으면 반사 생성자 호출보다 높은 수준의 정적 분석이 가능합니다.

관련 문제