2011-10-10 3 views
1

디자인 패턴을 알고있는 것처럼 느껴진다. 그러나 이것은 나를 벗어나고있다. 저는 두 개의 개별 프로젝트를 가지고 있습니다. 하나는 다른 프로젝트를위한 라이브러리입니다. 라이브러리는 XML 파일을 읽고 데이터 구조로 파싱합니다. 이것은 XML과의 변환을 책임지는 것을 의미합니다. 내 다른 프로젝트는 라이브러리가 조립하는 데이터에 작용하는 엔진입니다. 아마도 라이브러리에있는 것들을 반영하는 클래스를 포함 할 것이지만, 비헤이비어 메소드를 포함 할 것입니다. 궁금한 점이 있다면, 엔진과 라이브러리를 분리하는 이유는 XML 데이터를 수정하는 에디터 인 세 번째 프로젝트가 있기 때문입니다. 그것은 게임이다.설명과 구현 사이의 개체 추상화

이제 엔진에서 실행할 수있는 명령 세트와 많은 다른 명령을 포함하는 오브젝트를 나타내는 클래스를 라이브러리에 작성합니다. 내 문제는 이러한 명령을 정의하는 좋은 패턴과이를 추상화하는 컨테이너를 찾아 낼 수 없기 때문에 엔진이 유형을 전환 할 필요가 없다는 것입니다. 여기에 두 개의 프로젝트가 없다면, 그것은 쉽습니다. 다른 명령은 Execute() 메서드 또는 그 종류의 것을 포함하는 인터페이스를 구현합니다. 그러나 여기서는 효과가 없을 것입니다. 라이브러리에는 명령 동작을 구현하는 기능이 없으며 XML에서 가져온 특성 만 구현할 수 있습니다.

XML 데이터를로드하고 저장하는 메소드가 들어있는 컨테이너에서 사용할 수있는 인터페이스를 라이브러리에서 만들 수 있습니다. 그러나 엔진은 아직까지 중 하나를 위해 명령에 switch해야합니다 :

  1. 실행 그에 따라 자신의 상태를 수정하는 엔진의 컨테이너 클래스의 메소드, 또는
  2. 는 제어 엔진 개체의 정확한 유형을 생성 명령의 동작을 확인한 다음 인터페이스를 통해 명령을 실행하십시오.

해당 컨테이너는 내 게임의 컷씬과 그 안에있는 키 프레임입니다. 등 음악, 이미지, 텍스트, 여기에

등의 동작을 제어 할 명령은, 라이브러리에서 몇 가지 예제 코드입니다 :

public class SceneInfo 
{ 
    /* Other stuff... */ 
    public List<KeyFrameInfo> KeyFrames { get; private set; } 
} 

public class KeyFrameInfo 
{ 
    public List<IKeyFrameCommandInfo> Commands { get; private set; } 
} 

public class KeyFramePlayCommandInfo : IKeyFrameCommandInfo 
{ 
    public int Track { get; set; } 

    public static KeyFramePlayCommandInfo FromXml(XElement node) 
    { 
     var info = new KeyFramePlayCommandInfo(); 
     info.Track = node.GetInteger("track"); 
     return info; 
    } 

    public void Save(XmlTextWriter writer) 
    { 
     writer.WriteStartElement("PlayMusic"); 
     writer.WriteAttributeString("track", Track.ToString()); 
     writer.WriteEndElement(); 
    } 
} 

나는 아직 엔진의 절반을 작성하지 않은,하지만 것 keyframe.Commands에 액세스하고이를 반복하여 수행 할 수 있습니다. 이 문제를 과도하게 설계하지 않고 유형 전환을 피하려고합니다. KeyFramePlayCommand과 같은 클래스가있을 수 있습니다 (KeyFramePlayCommandInfo과 비교).

이 문제를 해결하는 좋은 패턴이 있습니까?

+0

게시 IKeyFrameCommandInfo :) –

+0

Save 메서드 만 있지만 실제로는 관련이 없습니다. 답변을 위해 수정해야하는 경우 그렇게하십시오. – Tesserex

답변

0

주어진 명령 유형에 대해 명령 실행 프로그램을 작성하는 팩토리에 대한 인터페이스를 작성할 수 있습니다. 또한 라이브러리에 팩토리를 등록하는 함수를 추가하십시오. 명령을 실행해야 할 때, 등록 된 팩토리에 명령을 제공하고 실행 프로그램 오브젝트를 확보 할 수 있습니다. 귀하의 문제를 올바르게 이해했다면이 코드는 라이브러리 측면에 있습니다.

이제 응용 프로그램 측면에서 팩토리 구현을 생성하고 알려진 모든 명령 유형을 실행할 수있는 클래스로 등록한 다음이 팩토리를 라이브러리에 등록 할 수 있습니다.


아주 다양한 방법이 있습니다. 나는 당신이 SceneInfo, KeyFrameInfo, 또는 IKeyFrameCommandInfovoid Execute()을 추가하고 싶지 않다고 가정합니다. 그들은 결국 정보 클래스입니다.자,이 SceneRunner 클래스를 만들 수 있습니다 :

public class SceneRunner 
{ 
    public ExecuteScene(SceneInfo scene) { 
     // loop over scene.KeyFrames and keyFrames.Commands, and execute 
    } 
} 

우리가이 명령을 실행하는 방법을 알고하지 않기 때문에, 이제 우리가 그것을 할 방법을 알고있는 클래스를 얻을 수 있도록 해주는 공장을 만들 수 있습니다 :

public interface IKeyFrameCommandFactory 
{ 
    IKeyFrameCommand GetCommand(IKeyFrameCommandInfo info); 
} 

그리고 공장 인터페이스와 등록 메커니즘을 러너에 추가하십시오. 응용 프로그램 측에서이 인스턴스를 처리하지 않으려 고하므로 다음과 같이 정적으로 설정해 보겠습니다.

지금까지는 그렇게 좋았습니다. 이제 공장 코드 시간 (라이브러리 클라이언트 측). ,

public void KeyCommandFactory: IKeyFrameCommandFactory 
{ 
    private static Map<Type, Type> mappings; 

    public IKeyCommand GetKeyCommand(IKeyCommandInfo info) 
    { 
     Type infoType = info.GetType(); 
     return Activator.CreateInstance(mappings[infoType], info); 
    } 
} 

당신이 반사에 가고 싶어하지 않는 경우

, 당신은 당신의 명령에 Prototype Pattern을 구현할 수 있습니다 :이 Daryl suggested은 (그냥 인터페이스 사양을 추가하여, 그대로 자신의 코드를 사용할 수 있습니다) 것과 매우 유사하다 IDictionary<Type, IPrototype> mappings;을 맵으로 사용하고 mappings[infoType].Clone()을 사용하여 명령의 새 인스턴스를 얻으십시오.

이제 정보 클래스를 명령 클래스와 연결하고 팩토리를 등록하는 두 가지 사항이 남아 있습니다. 둘 다 꽤 분명해야합니다.

연관성을 위해 RegisterCommand(Type infoType, IPrototype command)을 팩토리에 추가하고 프로그램 엔트리 포인트에 연결을 추가하거나 정적 메서드로 연결을 고정시킨 다음 프로그램 엔트리 포인트에서 해당 메서드를 호출 할 수 있습니다. 명령 클래스의 정보 클래스를 지정하고 어셈블리를 구문 분석하고 연관을 자동으로 추가하는 특성을 디자인 할 수도 있습니다.

마지막으로 SceneRunner.RegisterFactory(new KeyCommandFactory())으로 전화하여 SceneRunner에 공장을 등록하십시오.

+0

일부 샘플 코드로 확장 할 수 있습니까? 나는 모든 것을 이해하지 못한다. – Tesserex

0

인스턴스화 (특히 로직에서 인스턴스화를 추상화) 문제는 일반적으로 공장에서 발생합니다. 클래스를 인스턴스화 :

public void KeyCommandFactory{ 
    public static GetKeyCommand(IKeyCommandInfo info){ 
     /* ? */ 
    } 
} 

당신은 단지 하나의 인터페이스를 노출하고 있기 때문에, 우리는 또 다른 문제가?

제가 생각할 수있는 가장 좋은 방법은 키/유형 매핑입니다.

public void KeyCommandFactory{ 
    private static Map<string,Type> Mappings; 

    private static KeyCommandFactory(){ 
     /* Example */ 
     Mappings.Add("PlayMusic",typeof(PlayMusicCommand)); 
     Mappings.Add("AnimateFrame",typeof(AnimateFrameCommand)); 
     Mappings.Add("StopFrame",typeof(StopFrameCommand)); 
    } 

    public static GetKeyCommand(IKeyCommandInfo info){ 
     return (IKeyCommandInfo)Activator.CreateInstance(Mappings[info.CommandName]); // Add this property 
    } 
} 

당신은 심지어 명령 이름이 명령 개체의 이름과 일치하도록 반사를 사용하여 시도 할 수있는 규칙에 충실하고자하는 경우, 그러나 이것은 물론 덜 유연하다.

public void KeyCommandFactory{ 
    public static GetKeyCommand(IKeyCommandInfo info){ 
     Type type = Type.GetType("Namespace.To." + info.CommandName + "Command"); 
     return (IKeyCommandInfo)Activator.CreateInstance(type, info); // Add this property 
    } 
} 

나는 이것을 엔진 외부에 넣을 것이다. 위의 예에서는 명령 인스턴스를 매개 변수로 전달하여 명령의 새 인스턴스에 전달합니다.

+0

그래, 네가 일종의 스위칭을 사용하지만 매핑으로 싸여있다. 하지만 괜찮아요. Activator.CreateInstance를 이미 엔진의 다른 곳에서 사용하고 있습니다. – Tesserex

+0

@Tesserex 형식 전환을 없애기 위해 어떤 종류의 매핑이 필요합니다. 나는 이것을 성취 할 다른 방법을 찾지 못했습니다. –