이 글은 Modifying EMF model using Commands를 제 관점에서 해석한 것에 지나지 않습니다. 혹시 문제가 된다면, 즉시 삭제 하도록 하겠습니다. 조금더 자세한 내용을 알고 싶거나, 오역으로 인해서 잘 이해가 가지 않는다면 아래의 레퍼런스를 참조하시기 바랍니다.
Title : Modifying EMF model using Commands
Reference : http://help.eclipse.org/ganymede/topic/org.eclipse.emf.doc/references/overview/EMF.Edit.html
개요
EMF.edit는 Command framework를 제공한다. Command는 Redo와 Redo기능을 자동으로 구현하여 준다. 조금 유식한 말로 표현하자면, EMF는 Command-based editing of model을 지원한다. 조금 어려운 말로 들리 겠지만, 쉽게 말하자면 Model를 수정하는 것을 command(명령)을 통해서 하자는 말이다. 이러한 전략은 쉽게 Command의 실행을 취소 할 수 있고, 정형적으로 Model을 관리 할 있다는 것이다. 덕분에, Redo와 Undo를 별도의 노력없이 쉽게 구현 할 수 있는 장점이 있다.
Editing domains
EMF에서 제공되는 Command framework를 이해하기 위해서는 먼저 Editig domain을 이해해야 한다. Editing domain은 EMF model을 수정할 때 사용된다. 다시말해서 Commad frame work를 사용하려면, EMF모델을 수정할 때 꼭 Editing domain을 통해서 해야 한다는 말이다.
AdpaterFactoryEditingDomain이라는 Class가 있는데, 이것은 ItemProviderAdapterFactory를 통해서 구현이 되어 있다. 이 Calss는 아래와 같은 2가지 Service를 제공한다.
1. Command를 위한 factory (A factory for commands)
2. Generic EMF.edit command(Add, Remove, Copy, etc…)를 위한 Provier
다시 말해서, Editing domain은 모델의 수정 및 쓰기를 위한 Provider(Write provider)이다. 이러한 관점에서 본다면 content provider와 read provider는 읽기를 위한 Provider(Read provider)가 된다.

모델 수정하기(Modifying a model)
모델을 수정하는 간단한 아래의 예제를 살펴보자. 모델을 수정하는 상황은 다음과 같다.
Company class는 Department class와 One-to-many(1…*) reference를 가지고 있다. 이떄, Company class에서 Department Class에 대한 참조를 삭제 하기 위해서는 아래와 같은 간단한 코드를 생각 할 수 있다 .
Department d = …
Company c = …
c.getDepartments().remove(d);
이 코드는 매우 간단하지만, 수정을 한다는 것 외에는 다른 기능을 제공하지 못한다. 반면에 다음과 같은 방법을 통해서 모델을 수정하여 보자.
Deaprtment d = …
Company c = …
EdintingDomain ed = …
RemoveCommand cmd =
new RemoveCommand(ed, c, CompanyPackage.eINSTANCE.getCompany_Departments(), d);
ed.getCommandStack().excute(cmd);
위와 같은 방법으로 Model을 수정하면 다음과 같은 이점이 있다.
1. Ed.getCommandStack.undo()를 호출 함으로써 수정한 내용을 취소(undo)할 수 있다.
2. CommandStack을 보면, model이 수정되었는지를 쉽게 판단 할 수 있다. 따라서 쉽게 save, undo등을 활성화 시킬 수 있다.
3. Cmd.cnaExecute()를 통해서 쉽게 command가 실행 가능한지 판단할 수 있다.
Editing domain을 이용하여 Command만들기
이전의 예제에서는 new를 호출 함으로써 RemoveCommand를 생성하였다. 이 방법은 작동은 하지만 재사용적 측면에서 본다면 비효율적이다. 이방법은 Company와 Department class에서만 제대로 동작한다. 이러한 방법 대신에, EditingDomain을 이용하여 객체(Object)의 종류에 상관 없이 삭제 할 수 있는 RemoveCommand를 만들어 보자.
EditingDomain은 createCommand()라는 command를 생성할수 있는 Factory method를 제공한다. 이 함수의 인터페이스는 다음과 같다.
Public interface EditingDomain
{
…
Command createCommand(Class commandClass, commandParameter CommandParameter);
…
}
이 함수를 사용하여 Command 객체를 생성하기 위해서는 먼저 CommandParameter 객체를 만들어야 한다. CommandParameter는 Command를 실행하기 위해 필요한 Parameter에 대한 정보를 가지고 있다. 다음으로 Create()함수를 호출 하게 되면 commandClass와 parameter를 전달 해주게 된다.
이 부분이 잘 이해가 가지 않을 것이다. 조금 더 자세히 설명을 하자면, Command 객체를 생성하기 위해서는 commadClass와 CommandParameter가 필요하다. 다음과 같은 상황을 가정하여 보자. RemoveCommand class를 만들기 위해서 RemoveCommand().create(editingDomain, parameter)를 호출 하게 되면, EditingDomain에서 createCommaand()를 호출하게 된다. 이 때, RemoveCommand가 commandClass가 되는 것이고, parameter가 commandParamer가 되는 것이다. 여기서 이해가 잘 가지 않는 사람은 다음 이 글의 다음 섹션인 [EditingDomain에서 createCommand()를 어떻게 처리 할까?]를 참조 하기 바란다.
이러한 방법을 통해서 removeCommand를 만들게 되면 아래와 같은 방법으로 모델을 수정 할 수 있다.
Department d = …
EditingDomain ed = …
Command cmd = RemoveCommand.create(ed, d);
Ed.getCommandStack().excute(cmd);
단순히 코드만을 본다면 별로 달라진게 없어 보인다. 하지만, 실제로는 매우 큰 변화가 있었다. 일전의 방법은 3개(ed를 포함하면 4개)의 parameter를 필요했던 것에 반하여, 이 방법은 1개의 parameter만을 필요로한다. 또한, RemoveCommand는 객체의 종류에 상관없이 사용 가능 하게 되었다.
EditingDomain에서 createCommand()를 어떻게 처리 할까?
createCommand()가 어떻게 동작하는지 알아 보기 위해서 RemoveCommand.create()를 추적해보자.
public static Command create(EditingDomain domain, Object value)
{
return domain.createCommand(
RemoveCommand.class,
new CommandParameter(null, null, Collections.singleton(value)));
}
위의 code를 살펴보면, RemoveCommand.create()는 단지 editing domain의 createCommand()를 호출 해주는 mapper의 역할 밖에 하지 않는다.
위의 코드를 수행하면 AdpaterFactoryEditingDomain이 요청을 받아서 item provider로 정보들을 넘겨 준다. 참고로 이 과정은 standard delegatin pattern을 사용하였다.
public Command createCommand(Class commandClass, CommandParameter commandParameter)
{
Object owner = ... //get the owner object for the command
IEditingDomainItemProvider adapter =
(IEditingDomainItemProvider)
adapterFactory.adapt(owner, IEditingDomainItemProvider.class);
return adapter.createCommand(owner, this, commandClass, commandParameter);
}
위의 코드를 보면 owner라는 객체가 새롭게 등장 하였다. Owner는 item provider에 접근하기 위해서 사용된다. 예제에서 owner는 company object가 된다. Editing domain은 삭제될 객체의 item provider의 getParent() 함수를 사용하여 owner의 객체를 얻을 수 있다.
최종적으로 createCommand()는 삭제될 객체의 parent(owner)의 item provider에서 처리 된다. 우리의 예제를 살펴보면 Company의 item provider에서 처리 된다. CompanyItemProvier는 다음과 같은 방법으로 createCommand()를 구현 한다.
public class CompanyItemProvider ...
{
...
public Command createCommand(final Object object, ..., Class commandClass, ...)
{
if (commandClass == RemoveCommand.class)
{
return new RemoveCommand(object,
CompanyPackage.eINSTANCE.getCompany_Departments(),
commandParameter.getCollection());
}
...
}
}
이와 같은 방법으로 commandCreate()가 작동하게 된다. 하지만 더 좋은 방법이 있다!!
모든 item provider class는 EMF.edit가 제공하는 ItemProviderAdapter를 확장하여 구현된다. ItemProviderAdapter에는 대부분의 표준 command class를 위한 createCommand()가 구현되어 있다. 이것은 item provider의 subclass에 구현 되어있는 몇가지 간단한 함수를 호출 한다. 이것은 Template Method design pattern을 사용하였다.
예제에서 CompanyItemProvider는 아래의 함수만을 구현함으로써 RemoverCommand를 작동하게 할 수 있다.
public Collection getChildrenFeatures(Object object)
{
return Collections.singleton(CompanyPackage.eINSTANCE.getCompany_Departments());
}
이 코드를 보면 알겠지만, 한 개 또는 더 많은 객체들을 리턴한다. 예제에서는 단지 department의 레퍼런스만을 리턴한다. 이 함수가 호출 된 후, createCommand()의 기본 구현(default implementation)은 단지 어떤 객체를 removeCommand가 사용할 지만을 정해주면 된다.
여기 까지 딱 반적었네요. Overriding Commands, Model change notification, composed adapter factories의 3개의 섹션이 더 남아 있는데 이건 시간되는 데로 정리 해서 올리도록 하겠습니다. 허접하게 번역해서 올렷는데, 도움이 되면 좋겠네요.. ^^