실행 취소 엔진의 디자인 패턴


117

토목 엔진 응용 프로그램을위한 구조 모델링 도구를 작성 중입니다. 나는 전체 건물을 나타내는 하나의 거대한 모델 클래스를 가지고 있는데, 여기에는 노드, 라인 요소, 부하 등의 컬렉션도 포함되며 커스텀 클래스이기도합니다.

모델을 수정할 때마다 딥 카피를 저장하는 실행 취소 엔진을 이미 코딩했습니다. 이제는 다르게 코딩 할 수 있을지 생각하기 시작했습니다. 딥 카피를 저장하는 대신 해당 리버스 수정자를 사용하여 각 수정 자 작업 목록을 저장할 수 있습니다. 따라서 현재 모델에 역방향 수정자를 적용하여 실행 취소하거나 수정자를 다시 실행할 수 있습니다.

객체 속성 등을 변경하는 간단한 명령을 어떻게 수행할지 상상할 수 있습니다.하지만 복잡한 명령은 어떻습니까? 모델에 새 노드 개체를 삽입하고 새 노드에 대한 참조를 유지하는 일부 선 개체를 추가하는 것과 같습니다.

그것을 구현하는 방법은 무엇입니까?


"Undo Algorthim"주석을 추가하면 "Undo Algorithm"을 검색하여 찾을 수 있습니까? 그게 제가 검색 한 결과 중복으로 닫힌 것을 발견했습니다.
Peter Turner

hay, 저는 또한 우리가 개발하고있는 응용 프로그램에서 실행 취소 / 다시 실행을 개발하고 싶습니다. 우리는 QT4 프레임 워크를 사용하고 있으며 복잡한 실행 취소 / 다시 실행 작업이 많이 필요합니다 .. 궁금한 것은, Command-Pattern을 사용하여 성공 했습니까?
Ashika Umanga Umagiliya 2010

2
@umanga : 효과가 있었지만 쉽지는 않았습니다. 가장 어려운 부분은 참조를 추적하는 것이 었습니다. 예를 들어, Frame 객체가 삭제되면 자식 객체 : 노드, 그것에 작용하는로드 및 다른 많은 사용자 할당이 실행 취소 될 때 다시 삽입되도록 유지되어야했습니다. 그러나 이러한 자식 개체 중 일부는 다른 개체와 공유되었으며 실행 취소 / 다시 실행 논리가 매우 복잡해졌습니다. 모델이 그다지 크지 않다면, 저는 memento 접근 방식을 유지할 것입니다. 구현하기가 훨씬 쉽습니다.
Ozgur Ozcitak 2010 년

이것은 작업하기에 재미있는 문제입니다. 소스 코드 저장소가 svn (커밋 간의 차이를 유지함)과 같이 어떻게 수행하는지 생각해보십시오.
Alex

답변:


88

내가 본 대부분의 예제는이를 위해 Command-Pattern 의 변형을 사용합니다 . 실행 취소 할 수있는 모든 사용자 작업은 작업을 실행하고 롤백하기위한 모든 정보가 포함 된 자체 명령 인스턴스를 가져옵니다. 그런 다음 실행 된 모든 명령 목록을 유지 관리하고 하나씩 롤백 할 수 있습니다.


4
이것은 기본적으로 Cocoa의 실행 취소 엔진 인 NSUndoManager가 작동하는 방식입니다.
2008 년

33

OP가 의미하는 크기와 범위의 모델을 다룰 때 memento와 command 모두 실용적이지 않다고 생각합니다. 그들은 작동하지만 유지하고 확장하는 것은 많은 작업이 될 것입니다.

이러한 유형의 문제 에 대해서는 모델에 포함 된 모든 개체 에 대한 차등 체크 포인트를 지원하기 위해 데이터 모델을 지원해야한다고 생각합니다 . 나는 이것을 한 번 해봤고 매우 매끄럽게 작동했습니다. 해야 할 가장 큰 일은 모델에서 포인터 나 참조를 직접 사용하지 않는 것입니다.

다른 객체에 대한 모든 참조는 정수와 같은 식별자를 사용합니다. 개체가 필요할 때마다 테이블에서 개체의 현재 정의를 조회합니다. 이 테이블에는 이전 버전이 모두 포함 된 각 개체에 대한 연결 목록과 해당 버전이 활성화 된 체크 포인트에 대한 정보가 포함되어 있습니다.

실행 취소 / 다시 실행을 구현하는 것은 간단합니다. 작업을 수행하고 새 체크 포인트를 설정합니다. 모든 객체 버전을 이전 체크 포인트로 롤백합니다.

코드에서 약간의 규율이 필요하지만 많은 장점이 있습니다. 모델 상태의 차등 저장을 수행하기 때문에 깊은 복사본이 필요하지 않습니다. 사용하려는 메모리 양 ( CAD 모델과 같은 경우 매우 중요 함)을 다시 실행 또는 사용 된 메모리의 수로 범위 지정할 수 있습니다 . 실행 취소 / 다시 실행을 구현하기 위해 아무것도 할 필요가 없기 때문에 모델에서 작동하는 기능에 대해 확장 성이 뛰어나고 유지 관리가 적습니다.


1
파일 형식으로 데이터베이스 (예 : sqlite)를 사용하는 경우 거의 자동으로 수행 될 수 있습니다.
Martin Beckett

4
모델 변경으로 인한 종속성을 추적하여이를 보강하면 잠재적으로 실행 취소 트리 시스템을 가질 수 있습니다 (예 : 대들보의 너비를 변경 한 다음 별도의 구성 요소에서 작업을 수행하면 돌아와서 실행 취소 할 수 있습니다.) 거더는 다른 물건을 잃지 않고 변경됩니다). 이에 대한 UI는 약간 다루기 어려울 수 있지만 기존의 선형 실행 취소보다 훨씬 강력합니다.
Sumudu Fernando

이 ID의 대 포인터 아이디어를 더 설명 할 수 있습니까? 확실히 포인터 / 메모리 주소가 id만큼 잘 작동합니까?
paulm

@paulm : 기본적으로 실제 데이터는 (id, version)에 의해 인덱싱됩니다. 포인터는 객체의 특정 버전을 참조하지만 객체의 현재 상태를 참조하려고하므로 (id, version)이 아닌 id로 주소를 지정하려고합니다. 당신은 수있는 구조 조정 당신은 (버전 => 데이터) 테이블에 대한 포인터를 저장하고 단지마다 최신의 선택,하지만 당신이 데이터 muddies 문제 약간을 지속 할 때 피해 지역에 경향이 있고,에 어렵게 만든다 그래서 일종의 일반적인 쿼리를 수행하므로 일반적으로 수행되는 방식이 아닙니다.
Chris Morgan

17

GoF를 사용하는 경우 Memento 패턴은 특히 실행 취소를 처리합니다.


7
실제로 이것은 그의 초기 접근 방식을 다룹니다. 그는 대안적인 접근 방식을 요구하고 있습니다. 초기는 각 단계에 대한 전체 상태를 저장하고 후자는 "diff"만 저장합니다.
Andrei Rînea 2010

15

다른 사람들이 언급했듯이 명령 패턴은 실행 취소 / 다시 실행을 구현하는 매우 강력한 방법입니다. 그러나 명령 패턴에 대해 언급하고 싶은 중요한 이점이 있습니다.

명령 패턴을 사용하여 실행 취소 / 다시 실행을 구현할 때 데이터에 대해 수행 된 작업을 어느 정도 추상화하여 대량의 중복 코드를 방지하고 실행 취소 / 다시 실행 시스템에서 이러한 작업을 활용할 수 있습니다. 예를 들어 텍스트 편집기에서 잘라 내기 및 붙여 넣기는 클립 보드 관리를 제외한 보완 명령입니다. 즉, 잘라내기에 대한 실행 취소 작업은 붙여 넣기이고 붙여 넣기에 대한 실행 취소 작업은 잘라 내기입니다. 이것은 텍스트를 입력하고 삭제하는 것과 같은 훨씬 더 간단한 작업에 적용됩니다.

여기서 핵심은 실행 취소 / 다시 실행 시스템을 편집기의 기본 명령 시스템으로 사용할 수 있다는 것입니다. "실행 취소 개체 만들기, 문서 수정"과 같은 시스템을 작성하는 대신 "실행 취소 개체를 만들고 문서를 수정하기 위해 실행 취소 개체에 대해 다시 실행 작업을 실행"할 수 있습니다.

이제 많은 사람들이 "음, 명령 패턴의 요점이 아닌가?"라고 스스로 생각하고 있습니다. 예,하지만 두 세트의 명령이있는 명령 시스템을 너무 많이 보았습니다. 하나는 즉시 작업용이고 다른 하나는 실행 취소 / 다시 실행 용입니다. 즉각적인 작업 및 실행 취소 / 다시 실행에 특정한 명령이 없다는 것은 아니지만 중복을 줄이면 코드를보다 쉽게 ​​유지 관리 할 수 ​​있습니다.


1
내가 생각 적이 paste으로 cut^ -1.
Lenar Hoyt 2013


7

CSLA 가 적용되는 경우 일 수 있습니다 . Windows Forms 응용 프로그램의 개체에 대한 복잡한 실행 취소 지원을 제공하도록 설계되었습니다.


6

저는 Memento 패턴을 사용하여 복잡한 실행 취소 시스템을 성공적으로 구현했습니다. 매우 쉽고 자연스럽게 Redo 프레임 워크를 제공하는 이점도 있습니다. 더 미묘한 이점은 통합 작업이 단일 실행 취소에도 포함될 수 있다는 것입니다.

요컨대, 당신은 두 개의 유품 개체 스택을 가지고 있습니다. 하나는 실행 취소 용이고 다른 하나는 다시 실행 용입니다. 모든 작업은 새로운 메모를 생성하며 이상적으로는 모델, 문서 (또는 기타)의 상태를 변경하기위한 호출이 될 것입니다. 이것은 실행 취소 스택에 추가됩니다. 실행 취소 작업을 수행 할 때 Memento 개체에서 실행 취소 작업을 실행하여 모델을 다시 변경하는 것 외에도 실행 취소 스택에서 개체를 꺼내 다시 실행 스택으로 밀어 넣습니다.

문서의 상태를 변경하는 방법이 구현되는 방법은 구현에 따라 완전히 다릅니다. 단순히 API 호출 (예 : ChangeColour (r, g, b))을 수행 할 수있는 경우 해당 상태를 가져오고 저장하는 쿼리를 앞에 추가합니다. 그러나 패턴은 또한 딥 카피, 메모리 스냅 샷, 임시 파일 생성 등을 지원합니다. 단순한 가상 메서드 구현이므로 모든 것이 사용자에게 달려 있습니다.

집계 작업 (예 : 사용자 Shift- 삭제, 이름 바꾸기, 속성 변경과 같은 작업을 수행 할 객체로드 선택)을 수행하기 위해 코드는 단일 메모로 새 실행 취소 스택을 생성하고이를 실제 작업에 전달합니다. 개별 작업을 추가합니다. 따라서 작업 메서드는 (a) 걱정할 전역 스택을 가질 필요가 없으며 (b) 격리에서 실행 되든 하나의 집계 작업의 일부로 실행 되든 동일하게 코딩 될 수 있습니다.

많은 실행 취소 시스템은 메모리에만 있지만 원하는 경우 실행 취소 스택을 유지할 수 있습니다.


5

애자일 개발 책에서 명령 패턴에 대해 읽었습니다. 잠재적 인 가능성이 있습니까?

모든 명령이 명령 인터페이스 (Execute () 메서드가 있음)를 구현하도록 할 수 있습니다. 실행 취소를 원하는 경우 실행 취소 메서드를 추가 할 수 있습니다.

여기에 더 많은 정보


4

저는 Mendelt Siebenga 와 함께 명령 패턴을 사용해야한다는 사실을 알고 있습니다. 당신이 사용한 패턴은 시간이 지남에 따라 매우 낭비 될 수있는 Memento 패턴이었습니다.

메모리 집약적 인 응용 프로그램에서 작업하고 있으므로 실행 취소 엔진이 차지할 수있는 메모리 양, 저장되는 실행 취소 수준 또는 유지 될 일부 저장소를 지정할 수 있어야합니다. 이렇게하지 않으면 머지 않아 메모리 부족으로 인한 오류에 직면하게됩니다.

선택한 프로그래밍 언어 / 프레임 워크에서 실행 취소를위한 모델을 이미 생성 한 프레임 워크가 있는지 확인하는 것이 좋습니다. 새로운 것을 발명하는 것은 좋지만 실제 시나리오에서 이미 작성, 디버깅 및 테스트 한 것을 취하는 것이 좋습니다. 작성중인 내용을 추가하면 사람들이 알고있는 프레임 워크를 추천 할 수 있습니다.


3

Codeplex 프로젝트 :

고전적인 명령 디자인 패턴을 기반으로 응용 프로그램에 실행 취소 / 다시 실행 기능을 추가하는 간단한 프레임 워크입니다. 병합 작업, 중첩 된 트랜잭션, 지연된 실행 (최상위 트랜잭션 커밋 실행) 및 가능한 비선형 실행 취소 기록 (다시 실행할 여러 작업을 선택할 수 있음)을 지원합니다.


2

내가 읽은 대부분의 예제는 command 또는 memento 패턴을 사용하여 수행합니다. 그러나 단순한 deque-structure를 사용하면 디자인 패턴 없이도 할 수 있습니다 .


데크에 무엇을 넣으시겠습니까?

제 경우에는 실행 취소 / 다시 실행 기능을 원하는 작업의 현재 상태를 입력했습니다. 두 개의 데크 (실행 취소 / 다시 실행)를 사용하여 실행 취소 대기열 (첫 번째 항목 팝업)에서 실행 취소를 수행하고이를 다시 실행 대기열에 삽입합니다. 대기열에서 항목의 수가 선호하는 크기를 초과하면 꼬리 항목을 팝합니다.
Patrik Svensson

2
당신이 실제로 설명하는 IS 디자인 패턴 :). 이 접근 방식의 문제점은 상태가 많은 메모리를 차지할 때입니다. 수십 개의 상태 버전을 유지하면 실용적이지 않거나 불가능 해집니다.
Igor Brejc 2009-06-17

또는 정상 및 실행 취소 작업을 나타내는 클로저 쌍을 저장할 수 있습니다.
Xwtek

2

실행 취소를 처리하는 현명한 방법은 소프트웨어를 다중 사용자 공동 작업에도 적합하게 만들며 데이터 구조 의 운영 변환 을 구현하는 것 입니다.

이 개념은 그다지 인기가 없지만 잘 정의되고 유용합니다. 정의가 너무 추상적으로 보인다면 이 프로젝트 는 JSON 객체에 대한 운영 변환이 Javascript로 정의되고 구현되는 방법에 대한 성공적인 예입니다.



1

객체의 전체 상태를 저장하고 복원하는 편리한 양식을 위해 파일로드를 재사용하고 "객체"에 대한 직렬화 코드를 저장했습니다. 어떤 작업이 수행되었는지에 대한 정보와 직렬화 된 데이터에서 수집 한 정보가 충분하지 않은 경우 해당 작업을 실행 취소하는 방법에 대한 힌트와 함께 이러한 직렬화 된 개체를 실행 취소 스택에 푸시합니다. 실행 취소 및 다시 실행은 이론상 한 개체를 다른 개체로 대체하는 경우가 많습니다.

이상한 실행 취소 다시 실행 시퀀스 (더 안전한 실행 취소 인식 "식별자"로 업데이트되지 않은 위치)를 수행 할 때 수정되지 않은 개체에 대한 포인터 (C ++)로 인해 많은 버그가 발생했습니다. 이 지역의 버그는 종종 ... 음 ... 흥미 롭습니다.

일부 작업은 크기 조정, 물건 이동과 같은 속도 / 리소스 사용에 대한 특별한 경우가 될 수 있습니다.

다중 선택은 몇 가지 흥미로운 합병증도 제공합니다. 다행히 코드에 이미 그룹화 개념이 있습니다. 하위 항목에 대한 Kristopher Johnson의 의견은 우리가하는 일과 매우 유사합니다.


이것은 모델의 크기가 커짐에 따라 점점 더 작동하지 않는 것처럼 들립니다.
Warren P

어떤 방식으로? 이 접근 방식은 각 개체에 새로운 "사물"이 추가 될 때 변경없이 계속 작동합니다. 직렬화 된 형태의 객체가 크기가 커짐에 따라 성능이 문제가 될 수 있지만 이는 큰 문제가 아니 었습니다. 이 시스템은 20 년 이상 지속적으로 개발되었으며 1000 명의 사용자가 사용하고 있습니다.
Aardvark

1

나는 페그 점프 퍼즐 게임을위한 솔버를 작성할 때 이것을해야했다. 각 이동을 수행하거나 취소 할 수있는 충분한 정보를 보유한 Command 개체를 만들었습니다. 제 경우에는 시작 위치와 각 동작의 방향을 저장하는 것만 큼 간단했습니다. 그런 다음이 모든 개체를 스택에 저장하여 프로그램이 역 추적하는 동안 필요한만큼의 동작을 쉽게 취소 할 수 있도록했습니다.


1

PostSharp에서 실행 취소 / 다시 실행 패턴의 기성 구현을 시도 할 수 있습니다. https://www.postsharp.net/model/undo-redo

패턴을 직접 구현하지 않고도 애플리케이션에 실행 취소 / 다시 실행 기능을 추가 할 수 있습니다. Recordable 패턴을 사용하여 모델의 변경 사항을 추적하고 PostSharp에서도 구현되는 INotifyPropertyChanged 패턴과 함께 작동합니다.

UI 컨트롤이 제공되며 각 작업의 이름과 세분성을 결정할 수 있습니다.


0

저는 한 번은 명령에 의해 변경된 모든 응용 프로그램 모델 (예 : CDocument ... 우리가 MFC를 사용하고 있었음)에 대한 모든 변경 사항이 모델 내에서 유지되는 내부 데이터베이스의 필드를 업데이트하여 명령이 끝날 때까지 유지되는 응용 프로그램에서 작업했습니다. 따라서 각 작업에 대해 별도의 실행 취소 / 다시 실행 코드를 작성할 필요가 없었습니다. 실행 취소 스택은 레코드가 변경 될 때마다 (각 명령의 끝에서) 기본 키, 필드 이름 및 이전 값을 기억했습니다.


0

디자인 패턴 (GoF, 1994)의 첫 번째 섹션에는 실행 취소 / 다시 실행을 디자인 패턴으로 구현하는 사용 사례가 있습니다.


0

초기 아이디어를 수행 할 수 있습니다.

영구 데이터 구조를 사용 하고 이전 상태에 대한 참조 목록을 유지하십시오 . (그러나 이는 상태 클래스의 모든 데이터가 변경 불가능하고 그에 대한 모든 작업이 새 버전을 반환하는 경우에만 실제로 작동합니다 .-- 그러나 새 버전은 딥 카피 일 필요가 없습니다. 변경된 부분을 '복사 -쓰기 '.)


0

여기서 명령 패턴이 매우 유용하다는 것을 알았습니다. 여러 역방향 명령을 구현하는 대신 API의 두 번째 인스턴스에서 지연된 실행으로 롤백을 사용하고 있습니다.

이 접근 방식은 구현 노력이 적고 유지 관리가 쉬운 경우 합리적으로 보입니다 (그리고 두 번째 인스턴스를위한 추가 메모리를 감당할 수 있음).

예를 보려면 여기를 참조하십시오 : https://github.com/thilo20/Undo/


-1

나는이 당신에게 어떤 쓸모가 될 것입니다 알고하지 않습니다,하지만 난 내 프로젝트 중 하나에 비슷한해야 할 일을했을 때, 나는에서 UndoEngine을 다운로드 결국 http://www.undomadeeasy.com - 멋진 엔진을 그리고 나는 보닛 아래에 무엇이 있는지에 대해별로 신경 쓰지 않았습니다. 그냥 효과가있었습니다.


솔루션을 제공 할 자신이있는 경우에만 답변으로 귀하의 의견을 게시하십시오! 그렇지 않으면 질문 아래에 댓글로 게시하는 것을 선호합니다! (지금 그렇게 할 수 없다면! 좋은 평판을 얻을 때까지 기다려주십시오)
InfantPro'Aravind

-1

제 생각에는 UNDO / REDO는 두 가지 방식으로 광범위하게 구현 될 수 있습니다. 1. 명령 수준 (명령 수준 실행 취소 / 다시 실행이라고 함) 2. 문서 수준 (전역 실행 취소 / 다시 실행이라고 함)

명령 수준 : 많은 답변이 지적했듯이 이것은 Memento 패턴을 사용하여 효율적으로 달성됩니다. 명령이 작업 저널 화도 지원하는 경우 다시 실행이 쉽게 지원됩니다.

제한 사항 : 명령 범위를 벗어나면 실행 취소 / 다시 실행이 불가능하므로 문서 수준 (글로벌) 실행 취소 / 다시 실행으로 이어집니다.

많은 메모리 공간을 포함하는 모델에 적합하기 때문에 귀하의 사례는 전역 실행 취소 / 다시 실행에 적합하다고 생각합니다. 또한 선택적으로 실행 취소 / 다시 실행하는 데 적합합니다. 두 가지 기본 유형이 있습니다.

  1. 모든 메모리 실행 취소 / 다시 실행
  2. 개체 수준 실행 취소 다시 실행

"모든 메모리 실행 취소 / 다시 실행"에서 전체 메모리는 연결된 데이터 (예 : 트리, 목록 또는 그래프)로 처리되고 메모리는 OS가 아닌 응용 프로그램에서 관리됩니다. 따라서 C ++의 경우 new 및 delete 연산자가 오버로드되어보다 구체적인 구조를 포함하여 a. 노드가 수정 된 경우 b. 데이터 보유 및 지우기 등, 기본적으로 전체 메모리를 복사하여 (메모리 할당이 이미 고급 알고리즘을 사용하여 응용 프로그램에 의해 최적화되고 관리된다고 가정) 스택에 저장하는 것입니다. 메모리의 복사본이 요청되면 얕은 또는 깊은 복사본이 있어야하는 필요성에 따라 트리 구조가 복사됩니다. 수정 된 변수에 대해서만 전체 복사가 작성됩니다. 모든 변수는 사용자 지정 할당을 사용하여 할당되므로 응용 프로그램은 필요한 경우 삭제할시기를 최종 결정합니다. 실행 취소 / 다시 실행을 분할해야하는 경우 작업 세트를 프로그래밍 방식으로 선택적으로 실행 취소 / 다시 실행해야하는 경우 상황이 매우 흥미로워집니다. 이 경우, 해당 새 변수, 삭제 된 변수 또는 수정 된 변수에만 플래그가 주어 지므로 실행 취소 / 다시 실행은 해당 메모리 만 실행 취소 / 다시 실행합니다. 개체 내에서 부분 실행 취소 / 다시 실행을 수행해야하는 경우 상황이 더욱 흥미로워집니다. 이러한 경우 "방문자 패턴"이라는 새로운 아이디어가 사용됩니다. "개체 수준 실행 취소 / 다시 실행"이라고합니다. 또는 삭제 된 변수 또는 수정 된 변수에는 플래그가 주어 지므로 실행 취소 / 다시 실행은 해당 메모리 만 실행 취소 / 다시 실행합니다. 개체 내부에서 부분 실행 취소 / 다시 실행을 수행해야하는 경우 상황이 더욱 흥미로워집니다. 이러한 경우 "방문자 패턴"이라는 새로운 아이디어가 사용됩니다. "개체 수준 실행 취소 / 다시 실행"이라고합니다. 또는 삭제 된 변수 또는 수정 된 변수에는 플래그가 주어 지므로 실행 취소 / 다시 실행은 해당 메모리 만 실행 취소 / 다시 실행합니다. 개체 내부에서 부분 실행 취소 / 다시 실행을 수행해야하는 경우 상황이 더욱 흥미로워집니다. 이러한 경우 "방문자 패턴"이라는 새로운 아이디어가 사용됩니다. "개체 수준 실행 취소 / 다시 실행"이라고합니다.

  1. 개체 수준 실행 취소 / 다시 실행 : 실행 취소 / 다시 실행 알림이 호출되면 모든 개체는 스트리밍 작업을 구현합니다. 여기서 스 트리머는 개체에서 프로그래밍 된 이전 데이터 / 새 데이터를 가져옵니다. 방해받지 않는 데이터는 방해받지 않습니다. 모든 개체는 스 트리머를 인수로 가져오고 UNDo / Redo 호출 내에서 개체의 데이터를 스트리밍 / 언 스트림합니다.

1과 2 모두 1. BeforeUndo () 2. AfterUndo () 3. BeforeRedo () 4. AfterRedo ()와 같은 메서드를 가질 수 있습니다. 이러한 메서드는 모든 개체가 특정 작업을 얻기 위해 이러한 메서드도 구현하도록 기본 실행 취소 / 다시 실행 명령 (컨텍스트 명령이 아님)에 게시되어야합니다.

좋은 전략은 1과 2의 하이브리드를 만드는 것입니다. 장점은 이러한 방법 (1 & 2) 자체가 명령 패턴을 사용한다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.