2012-01-09 3 views
5

데이터베이스에서 대기열에있는 작업 목록을 데이터베이스에서 읽고 스레드를 사용하여 병렬로 실행해야하며 각각을 실행하는 명령 클래스 목록이 있습니다. 이러한 작업은 모두 공통 인터페이스 (명령 패턴)를 구현합니다. 나는 데이터베이스에서 보류중인 작업을 검색 할 때, 나는 대기중인 작업을 실행하기위한 명령 및 팩토리 디자인 패턴 사용

ICommand command; 
switch (jobCode) 
{ 
    case "A": 
    command = new CommandA(); 
    break; 
    case "B": 
    command = new CommandB(); 
    break; 
    case "C": 
    command = new CommandC(); 
    break; 
} 

command.Execute(); 

오른쪽 명령 개체를 만들 수있는 더 좋은 방법이 있나요 (공장 클래스)이 같은 각 작업 뭔가에 적합한 명령 개체를 인스턴스화해야합니다 위와 같은 큰 switch 문을 사용하지 않고? 또는 대기중인 작업을 실행하기위한 다른 패턴이 있습니까?

해결 방법 : 선택한 답을 바탕으로 다음과 같이 해결합니다. 이렇게하면 명령 객체의 지연 인스턴스화가 수행됩니다. 당신은 그것을 제공하기 위해 작업을 요구 고려할 수

public class CommandFactory 
{ 
    private readonly IDictionary<string, Func<ICommand>> _commands; 

    public CommandFactory() 
    { 
     _commands = new Dictionary<string, Func<ICommand>> 
         { 
          {"A",() => new CommandA()}, 
          {"B",() => new CommandB()}, 
          {"C",() => new CommandC()} 
         }; 
    } 

    public ICommand GetCommand(string jobKey) 
    { 
     Func<ICommand> command; 
     _commands.TryGetValue(jobKey.ToUpper(), out command); 
     return command(); 
    } 
}  

Client: 

     var factory = new CommandFactory(); 
     var command = factory.GetCommand(jobKey); 
     command.Execute(); 
+0

이것은 귀하의 모든 명령이 귀하의 공장에 있어야한다는 것을 고려할 때 결함이있는 것으로 보입니다. – KingOfHypocrites

답변

12

대부분의 C# 명령 패턴 구현 : 실행시

편집 귀하의 코멘트에 대한 답변에서

, 당신이 뭔가를 할 수 있습니다 만 인스턴스화 . 이러한 구현은 일반적으로 ICommand 인터페이스를 사용합니다.

public interface ICommand 
{ 
    void Execute(); 
} 

그런 다음 모든 명령 클래스가 강제로 인터페이스를 구현합니다. 이 솔루션에는 문제가 없지만 개인적으로 너무 많은 클래스를 만드는 것을 좋아하지 않으며 대신 .NET 대리자를 사용하는 것을 선호합니다 (Java에서는 대리자가 없습니다). 액션 위임은 보통 하나의 방법 참조를 필요로하는 경우 트릭을 수행합니다

public class Prog 
{ 
    public Prog() 
    { 
     var factory = new CommandFactory(); 
     factory.Register("A",() => new A().DoA);    
     factory.Register("B",() => new B().DoB); 
     factory.Register("C", DoStuff); 

     factory.Execute("A"); 
    } 

    public static void DoStuff() 
    { 
    } 
} 

public class CommandFactory 
{ 
    private readonly IDictionary<string, Action> _commands;  

    public void Register(string commandName, Action action) 
    { 
    _commands.Add(commandName, action); 
    } 

    public Action GetCommand(string commandName) 
    { 
     _commands[commandName]; 
    } 

    public void Execute(string commandName) 
    { 
     GetCommand(commandName)(); 
    } 
} 
public class A 
{ 
    public void DoA() 
    { 
    } 
} 

public class B 
{ 
    public void DoB() 
    { 
    } 
} 

명령 인터페이스와 같은 하나 이상의 방법을 필요로하는 경우 :

public interface ICommand 
{ 
    void Execute(); 
    void Undo(); 
} 

당신은이 같은 래퍼 클래스를 사용할 수 있습니다

public class Command 
{ 
    public Command(Action execute, Action undo) 
    { 
     Execute = execute; 
     Undo = undo; 
    } 

    public Action Execute { get; protected set; } 
    public Action Undo { get; protected set; } 
} 

또는 (는 문제가되지 않는 한)

public class Command 
{ 
    private readonly Action _execute; 
    private readonly Action _undo; 

    public Command(Action execute, Action undo) 
    { 
     _execute = execute; 
     _undo = undo; 
    } 

    public void Execute() 
    { 
     _execute(); 
    } 

    public void Undo() 
    { 
     _undo(); 
    } 
} 

(이미 사용중인 레거시 콘텐츠가있는 경우 ICommand를 구현할 수도 있습니다.인터페이스를 사용하는 경우 팩토리는 Command 클래스 대신 인터페이스를 사용해야합니다.

이렇게 래퍼를 사용하면 지원하려는 각 작업에 대해 명령 클래스를 만들 필요가 없습니다. 다음 예제는 래퍼 클래스를 사용하는 방법을 보여줍니다

public class Prog2 
{ 
    public Prog2() 
    { 
     var factory = new CommandFactory2(); 
     factory.Register("A", new Lazy<Command>(
      ()=> 
       { 
        var a = new A(); 
        return new Command(a.DoA, a.UndoA); 
       })); 

     factory.Register("B", new Lazy<Command>(
      () => 
      { 
       var c = new B(); 
       return new Command(c.DoB, c.DoB); 
      })); 

     factory.Register("C", new Lazy<Command>(
      () => new Command(DoStuff, UndoStuff))); 

     factory.Execute("A"); 
    } 

    public static void DoStuff() 
    { 
    } 

    public static void UndoStuff() 
    { 
    } 
} 

public class CommandFactory2 
{ 
    private readonly IDictionary<string, Lazy<Command>> _commands; 

    public void Register(string commandName, Lazy<Command> lazyCommand) 
    { 
     _commands.Add(commandName, lazyCommand); 
    } 

    public void Register(string commandName, Action execute, Action undo) 
    { 
     _commands.Add(commandName, new Lazy<Command>(() => new Command(execute, undo))); 
    } 

    public Command GetCommand(string commandName) 
    { 
     return _commands[commandName].Value; 
    } 

    public void Execute(string commandName) 
    { 
     GetCommand(commandName).Execute(); 
    } 

    public void Undo(string commandName) 
    { 
     GetCommand(commandName).Undo(); 
    } 
} 


public class A 
{ 
    public void DoA() 
    { 
    } 

    public void UndoA() 
    { 
    } 
} 

public class B 
{ 
    public void DoB() 
    { 
    } 

    public void UndoB() 
    { 
    } 
} 

당신이 하나 이상의 방법 (실행, 실행 취소 등) 경우에도 인터페이스를 구현 할 필요가 없습니다 볼 수 있듯이. Execute 및 Undo 메서드는 다른 클래스에 속할 수 있습니다. 더 자연스럽고 여전히 명령 패턴을 사용할 수있는 방식으로 코드를 자유롭게 구조 할 수 있습니다.

+0

제공된 예제에는 설계 유연성과 재사용 성이 부족합니다. 제공되는 예제는 사용자를보다 하드 코딩 된 클래스를 작성하도록 유도합니다. @ Slade의 예를 들어 보겠습니다. –

+0

디자인의 유연성과 재사용 성이 어떻게 부족한 지 설명하십시오. 위임 서명을 충족시키는 사전에 메소드를 추가 할 수 있습니다. "더 많은 하드 코딩 된 클래스"가 무슨 뜻입니까? –

+0

factory.Register ("C" 디자인에 큰 결함이있는 하드 코딩 중 하나입니다. 하드 코드를 사용하지 마십시오. – Zenwalker

1

자신의 ICommand의의 :

interface IJob 
{ 
    ICommand Command { get; } 
} 

public class JobA : IJob 
{ 
    private readonly ICommand _command = new CommandA(); 
    public ICommand Command { get { return _command; } } 
} 

을 그리고 오히려 jobCode에 전환보다, 당신은 할 수 :

job.Command.Execute(); 
+1

도움이 될지 확실하지 않습니다. 명령 개체를 만들기 위해 전환하는 대신이 작업을 사용하여 올바른 작업 개체를 만드는 스위치가 필요합니다. – RKP

+0

@RKP 나는 당신의'jobCode'가 직업에서 왔다고 가정했다. 당신이 원하는 직업의 유형을 알기 때문에 어떤 직업을 알 수 있는가? –

+0

요점은 다시 ICommand 대신에 IJob을 살펴볼 필요가있는 토론을하게됩니다 :) –

4

당신은을 사용할 수 있습니다 문자/문자를 해당 ICommand 구현에 매핑하려면 Dictionary을 입력하십시오. 당신이 명령 유형을 등록하고 string 기반의 키에 매핑하고 인스턴스화하고 일반적으로 더 실행하도록 할 수 있습니다이 사용

public class CommandFactory 
{ 
    private readonly Dictionary<string, ICommand> mCommands = new Dictionary<string,ICommand>(StringComparer.OrdinalIgnoreCase); 

    public void RegisterCommand<TCommand>(string commandKey) where TCommand : ICommand, new() 
    { 
     // Instantiate the command 
     ICommand command = new TCommand(); 

     // Add to the collection 
     mCommands.Add(commandKey, command); 
    } 

    public void ExecuteCommand(string commandKey) 
    { 
     // See if the command exists 
     ICommand command; 
     if (!mCommands.TryGetValue(commandKey, out command)) 
     { 
      // TODO: Handle invalid command key 
     } 

     // Execute the command 
     command.Execute(); 
    } 
} 

:처럼 뭔가. 명령 유형을 처음 사용할 때 인스턴스화만으로 성능을 향상시킬 수 있습니다. 자바 구현으로 더 많거나 적은 같은

public class CommandDetails<T> where T : ICommand, new() 
{ 
    private ICommand mCommand; 

    public ICommand GetCommand() 
    { 
     if (/* Determine if the command has been instantiated */) 
     { 
      // Instantiate the command 
      mCommand = new T(); 
     } 

     return mCommand; 
    } 
} 

public void ExecuteCommand(...) 
{ 
    // See if the command exists 
    CommandDetails details; 
    // ... 

    // Get the command 
    // Note: If we haven't got the command yet, this will instantiate it for us. 
    ICommand command = details.GetCommand(); 

    // ... 
} 
+0

감사합니다. 이것은 명령 패턴의 고전적인 예이며 모든 명령 객체를 컨테이너 클래스의 컬렉션에 저장하지만이 명령을 사용하는 경우에만 어떻게 인스턴스화 할 수 있도록 향상시킬 수 있습니까? – RKP

+0

@RKP 내 대답을 참조하십시오. –

+0

하지만 어떻게 인스턴스화하지 않고 명령을 등록 (사전에 명령 추가) 할 수 있습니까? 하나의 옵션은 다른 답변에 지정된대로 대리인을 사용하는 것입니다. 더 이상 해결책이 있는지 알고 싶습니다. – RKP