TheSpecUIframework:Chapter 03

From 흡혈양파의 번역工房
Jump to navigation Jump to search
요소(elements)의 재사용 및 구성

요소(elements)의 재사용 및 구성

Spec 의 핵심 설계 목표는 구축중인 사용자 인터페이스의 위젯을 이용해서 사용자 인터페이스를 원활하게 재사용 할 수있게 하는 것입니다. 그렇게 해야 사용자 인터페이스를 만들 때 생산성이 크게 향상되기 때문입니다.

재사용에 대한 이 관점은 이전 장에서 실제로 확인할 수 있었습니다. 또한 기본 위젯을 완전한 사용자 인터페이스처럼 사용할 수 있음을 보았습니다. 이 섹션에서는 위젯의 재사용 및 구성에 중점을 두며, 기본적으로 제약은 없다는 것을 보여줍니다. UI 를 구현할 때 유일한 요구 사항은 재사용 될 때 사용자 인터페이스를 매개 변수화하는 방법을 고려해야 하는 것이며, 그 점에서 반드시 따라야 할 구체적인 규칙은 하나뿐입니다.

이 장에서는 이미 정의된 요소를 다시 사용하여 새 UI 를 작성하는 방법을 학습합니다.


첫 번째 요구 사항

그림 3-1 ProtocolBrowser 의 스크린샷

To show how Spec enables the composition and reuse of user interfaces, in this chapter we build the user interface shown in Figure 3-1 as a composition of four parts: Spec 에서 사용자 인터페이스의 구성과 재사용을 가능하게하는 방법을 보여주기 위해, 이 장에서는 그림 3-1 과 같은 사용자 인터페이스를 네 부분으로 구성합니다:

  1. The "WidgetClassList": AbstractWidgetModel 의 하위 클래스를 표시하기위한 ListModel 을 포함하는 위젯.
  2. The "ProtocolMethodList": ListModel 과 프로토콜의 메서드를 표시하기 위해 LabelModel 로 구성된 위젯.
  3. The "ProtocolViewer": 하나의 WidgetClassList 와 두 개의 ProtocolMethodList 의 조합으로, AbstractWidgetModel 의 모든 하위 클래스의 프로토콜 api 및 api-event 의 메서드를 검색.
  4. The "ProtocolBrowser" : ProtocolViewer 를 재사용하며, 레이아웃을 변경하고 TextModel 을 추가하여 메소드의 소스 코드를 참조.


위젯으로 재사용 할 기본 UI 생성하기

이제부터 만들게될 첫 번째 사용자 정의 UI 에는 AbstractWidgetModel 의 모든 하위 클래스 목록이 표시되어야 합니다. 이 UI는 나중에보다 완벽한 UI 를 위한 위젯으로 재사용됩니다. 코드는 다음과 같습니다.(접근자에 대한 코드는 포함되어 있지 않습니다):

First we create a subclass of ComposableModel with one instance variable list which will hold an instance of ListModel.

먼저 ListModel 의 인스턴스를 가지게될 하나의 인스턴스 변수 목록으로 사용하기 위해 ComposableModel 의 하위 클래스를 만듭니다.

ComposableModel subclass: #WidgetClassList
	instanceVariableNames: 'list'
	classVariableNames: ''
	package: 'Spec-BuildUIWithSpec


initializeWidgets 메서드에서 목록을 생성하며, 필요한 클래스를 알파벳 순으로 채웁니다. 또한 창 제목을 추가합니다.

WidgetClassList >> initializeWidgets
	list := self newList.
	list items: (AbstractWidgetModel allSubclasses
		 							 sorted: [:a :b | a name < b name ]).
	self focusOrder add: list.

WidgetClassList >> title
	^'Widgets'


레이아웃에는 목록만 포함됩니다:

WidgetClassList class >> defaultSpec
	^ SpecLayout composed
		add: #list;
		yourself

결과 UI 는 그림 3-2 에서 확인할 수 있습니다.

그림 3-2 WidgetClassList 의 스크린샷


Since this UI will later be used together with other widgets to provide a more complete user interface, some actions will need to occur when a list item is clicked. However, we cannot know beforehand what all these possible actions will be everywhere that it will be reused. The best solution therefore is to place this responsibility on the reuser of the widget. Every time this UI is reused as a widget, it will be configured by the reuser. To allow this, we add a configuration method name "whenSelectedItemChanged:", in the "api" protocol:

이 UI 는 나중에 다른 위젯과 함께 사용되어 보다 완벽한 사용자 인터페이스를 제공하게 되기 때문에, 목록 항목을 클릭 할 때 일부 동작(action)이 발생되어야 합니다. 하지만 가능성 있는 모든 행동이 재사용 될 수 있는 모든 곳에서 어떻게 필요하게 될지 미리 알 수는 없습니다. 그러므로 가장 좋은 해결책은 이 책임[1]을 위젯을 재사용 하게될 사람에게 맡기는 것입니다. 이 UI 는 위젯으로 재사용 될 때마다 재사용 하게될 사용자가 구성하도록 합니다.

재사용에 대한 행동의 구성을 허용하기 위해 api 프로토콜에서 구성 메서드를 whenSelectedItemChanged: 라는 이름으로 추가합니다.

WidgetClassList >> whenSelectedItemChanged: aBlock
	list whenSelectedItemChanged: aBlock


이제 이 위젯을 재사용하는 사람은, 선택한 항목이 변경 될 때마다 실행되어야할 블록을 매개 변수화 할 수 있습니다.

Gnome3 notice header.png
Spec 위젯을 재사용할 때의 유일한 규칙은 UI 의 모든 일반(public)적인 설정 메서드는 api 프로토콜에 포함되어야 한다는 것입니다. 이 규칙은 위젯의 재사용자가 파라미터화 하는 방법을 더 쉽게 발견할 수 있도록 하기위한 것입니다. 이 장의 끝 부분에 있는 3.6 절에서 일반 설정 api 주제에 대해 설명합니다.
Gnome3 notice footer.png


두 개의 기본 위젯을 재사용 가능한 UI 로 결합

이렇게 만들어진 UI 는 주어진 프로토콜의 모든 메서드 목록을 보여줄 것이며, list 와 label 이라는 두 개의 위젯을 결합합니다. 재사용을 고려하면 앞의 UI 와 차이는 없습니다. 왜냐하면 위젯으로서 UI 를 재사용 할 때 그 UI 에 포함 된 위젯의 개수(및 위치 포함)에 대한 영향을 받지 않기 때문입니다. 크고 복잡한 UI 는 간단한 위젯과 동일한 방식으로 재사용됩니다.

ComposableModel subclass: #ProtocolMethodList
	instanceVariableNames: 'label methods'
	classVariableNames: ''
	package: 'Spec-BuildUIWithSpec'


The ==initializeWidgets== method for this UI is quite straightforward. We specify the default label text as 'protocol', which will be changed when the widget is reused. We also give this UI a title.

The initializeWidgets method for this UI is quite straightforward. We specify the default label text as 'protocol', which will be changed when the widget is reused. We also give this UI a title.

이 UI의 initializeWidgets 메소드는 매우 간단합니다. 기본 lable 의 텍스트를 'protocol' 로 지정합니다. 이 프로토콜은 위젯이 다시 사용될 때 변경됩니다. 또한 이 UI 에 제목을 지정합니다.

ProtocolMethodList >> initializeWidgets
	methods := self newList.
	methods displayBlock: [ :m | m selector ].
	label :=  self newLabel.
	label label: 'Protocol'.
	self focusOrder add: methods.
ProtocolMethodList >> title
	^ 'Protocol widget'

레이아웃 코드는 고정 높이 lable 이 맨 위에 있으며, 남아있는 모든 공간을 목록이 차지하게 되는 열을 만듭니다. (레이아웃에 대한 자세한 내용은 3-3 장을 참조하십시오.)

ProtocolMethodList class >> defaultSpec
	^ SpecColumnLayout composed
			add: #label height: self toolbarHeight;
			add: #methods;
			yourself

이 UI 는 ProtocolMethodList new openWithSpec 를 평가(evaluating)해서 확인할 수 있습니다. 그림 3-3 에서와 같이 목록은 비어 있습니다. 항목을 별도로 설정하지 않았으므로 이것은 정상입니다.

그림 3-3 ProtocolMethodList 의 스크린샷

예를 들어 메소드 목록을 채우고 프로토콜의 이름을 지정하는 등의 경우에는 프로토콜 메소드 목록을 구성해야 합니다. 이러한 작업들을 허용하기 위해 "api" 프로토콜에 여러가지 설정 메서드를 추가합니다.

ProtocolMethodList >> items: aCollection
	methods items: aCollection
ProtocolMethodList >> label: aText
	label label: aText
ProtocolMethodList >> resetSelection
	methods resetSelection
ProtocolMethodList >> whenSelectedItemChanged: aBlock
	methods whenSelectedItemChanged: aBlock
Gnome3 notice header.png
메서드를 추가하는것 외에는 아무 것도 하지 않아도 됩니다. 메서드와 label 모두 접근자를 통해 접근 할 수 있기 때문에, 이 위젯의 ​​재사용자는 간단하게 가져온 다음 직접 설정하면 됩니다. 이 두 가지 대안은 3.6 절에서 논의하게될 설계 결정을 반영하고 있습니다.
Gnome3 notice footer.png


3 개의 위젯과 상호 작용을 관리하기

구축하게될 세 번째 사용자 인터페이스는, 두 개의 이전 사용자 인터페이스를 구성 요소 사용하게 됩니다. 두 종류의 위젯은 api 프로토콜의 메소드를 호출하여 구성되는데, 사용자 정의 UI 구성과 시스템 위젯 구성간에 차이가 없다는것을 알게 될 것입니다.

이 UI 는 WidgetClassList 와 두 개의 ProtocolMethodList 로 구성될 것이며, WidgetClassList 에서 model 클래스가 선택되면 프로토콜 api 및 api-events 의 메서드는 각각 두 개의 ProtocolMethodList 위젯에 표시될 것입니다.

ComposableModel subclass: #ProtocolViewer
	instanceVariableNames: 'models api events'
	classVariableNames: ''
	package: 'Spec-BuildUIWithSpec'


initializeWidgets 메소드는 instantiate: 를 사용하여 위젯을 인스턴스화 하고 ProtocolMethodList 클래스의 다른 매개 변수화(parametrization) 메소드 중 일부를 보여줍니다.

ProtocolViewer >> initializeWidgets
	models := self instantiate: WidgetClassList.
	api := self instantiate: ProtocolMethodList.
	events := self instantiate: ProtocolMethodList.

	api label: 'api'.
	events label: 'api-events'.

	self focusOrder add: models; add: api; add: events.
ProtocolViewer >> title
	^ 'Protocol viewer'


서로 다른 위젯 간의 상호 작용을 설명하기 위해 initializePresenter 메소드를 정의합니다. 클래스를 선택하면 메소드 목록에서 선택된 항목이 재설정되며, 두 메소드 목록이 채워집니다. 또한 하나의 메서드 목록에서 메서드를 선택하면 다른 목록의 선택(selection)은 다시 설정됩니다.

ProtocolViewer >> initializePresenter
	models whenSelectedItemChanged: [ :class |
		api resetSelection.
		events resetSelection.
		class
			ifNil: [ api items: #(). events items: #() ]
			ifNotNil: [
				api items: (self methodsIn: class for: 'api').
				events items: (self methodsIn: class for: 'api-events') ] ].

	api whenSelectedItemChanged: [ :method |
 		method ifNotNil: [ events resetSelection ] ].
	events whenSelectedItemChanged: [ :method |
		method ifNotNil: [ api resetSelection ] ].
ProtocolViewer >> methodsIn: class for: protocol
	^ (class methodsInProtocol: protocol) sorted:
	  		 [ :a :b | a selector < b selector ].


마지막으로 레이아웃은 하위 위젯을 하나의 열에 배치하며, 모든 하위 위젯은 동일한 크기의 공간을 사용합니다.

ProtocolViewer class >> defaultSpec
	^ SpecColumnLayout composed
			add: #models; add: #api; add: #events;
			yourself


이전과 마찬가지로 결과는 다음과 같은 코드 조긱 "ProtocolViewer new openWithSpec" 을 실행하면 볼 수 있으며 결과는 그림 3-4 에서 확인할 수 있습니다. 이 사용자 인터페이스는 기능적으로 클래스를 클릭하면 해당 클래스의 api 및 api-events 프로토콜의 메소드가 표시됩니다.

그림 3-4 ProtocolViewer 의 모습


두 번째 사용자 인터페이스와 마찬가지로이 UI 를 다시 사용할 때에는 설정을 하게할 필요가 있을지도 모릅니다. 여기서 관련된 설정이라면, 세 가지 목록 중 하나에서 선택 변경이 발생했을 때 수행 할 작업이 됩니다. api 프로토콜에 다음 세 가지 메소드를 추가합니다.

ProtocolViewer >> whenClassChanged: aBlock
	models whenSelectedItemChanged: aBlock
ProtocolViewer >> whenEventChanged: aBlock
	events whenSelectedItemChanged: aBlock
ProtocolViewer >> whenAPIChanged: aBlock
	api whenSelectedItemChanged: aBlock
Gnome3 notice header.png
앞에서 살펴봤던 공통 설정 API 메소드와 비교할 때, 이 메소드는 의미론적 정보를 구성 API 에 추가하고 있습니다. 클래스, api 또는 api-events 의 목록 항목이 변경되었을 때 무엇을 해야할지를 설정합니다. 이런 방식은 거의 틀림없이 하위 위젯에 액세스 할 수 있게 하는 것보다 더 명확하게 커스터마이징 API 를 전달할 수 있습니다.
Gnome3 notice footer.png


재사용 된 위젯의 레이아웃 변경하기

때로는 기존의 UI 를 위젯으로 다시 사용하려는 경우, 해당 UI 의 레이아웃이 사용자의 필요에 적합하지 않은 경우가 있습니다. Spec은 그 위젯의 레이아웃을 오버라이드하여 그러한 UI 를 재사용 할 수있게 해줍니다. 여기서 이 내용을 확인할 수 있습니다.


마지막 사용자 인터페이스는 ProtocolViewer 를 다른 레이아웃으로 재사용하고, 텍스트 영역을 추가하여 선택된 메서드의 소스 코드를 편집합니다.

ComposableModel subclass: #ProtocolBrowser
	instanceVariableNames: 'text viewer'
	classVariableNames: ''
	package: 'Spec-BuildUIWithSpec'
ProtocolBrowser >> initializeWidgets
	text := self instantiate: TextModel.
	viewer := self instantiate: ProtocolViewer.
	text
		aboutToStyle: true;
		isCodeCompletionAllowed: true.
	self focusOrder
		add: viewer;
		add: text.
ProtocolBrowser >> title
	^'Spec Protocol Browser'


텍스트 필드는 소스 코드를 표시하도록 구성되어 있습니다:

  • aboutToStyle: true 구문 강조를 가능하게합니다.
  • isCodeCompletionAllowed: 코드 완성을 가능하게합니다.

"initalizePresenter"메서드는 텍스트 영역이 목록의 선택 영역에 반응하도록 만드는 데 사용됩니다. 메소드를 선택하면 텍스트 영역의 내용을 업데이트하여 선택한 메소드의 소스 코드를 표시합니다.

ProtocolBrowser >> initializePresenter
	viewer whenClassChanged: [ :class | text behavior: class ].
	viewer whenAPIChanged: [ :item |
		item
			ifNotNil: [ text text: item sourceCode ] ].
	viewer whenEventChanged: [ :item |
		item
			ifNotNil: [ text text: item sourceCode ] ]


퍼즐의 마지막 부분은 우리가 재사용하고있는 다른 위젯의 레이아웃입니다. 이 UI에서는 열과 행을 결합합니다. 첫 번째 행은 "viewer" 위젯의 내부 위젯을 다른 레이아웃으로 다시 사용하기 때문에 특별합니다. 이를 위해 여기서는 전송되어야 하는 접근자 메시지의 시퀀스를(심볼의 배열로) 지정합니다: 예를 들어 클래스 목록의 경우 먼저 "viewer" 메시지와 "model" 메시지를 차례로 선택합니다.

ProtocolBrowser class >> defaultSpec
	^ SpecLayout composed newColumn: [:col |
			col newRow: [ :row |
				row add: #(viewer models);
					 	newColumn: [ :col2 |
							col2 add: #(viewer api);
								   add: #(viewer events) ] ];
					  add: #text];
			yourself


Gnome3 notice header.png
재사용중인 위젯의 내부 위젯을 배치하려면 (위젯 대신 전체를 위젯으로 사용), 심볼 배열: 내부 위젯에 도착하기 위해 전송해야하는 접근자의 시퀀스를 나타냅니다.
Gnome3 notice footer.png


이것으로 이 장의 마지막 예제를 마칩니다. 마지막 예제의 결과는 그림 3-1 의 첫 번째 그림에서 볼 수 있습니다.


공통 설정 API 에 대한 고려 사항

이 장에서는 위젯의 공용 구성 API에서 메소드를 정의하는 몇 가지 방법을 살펴 보았습니다. 여기서 설 메소드 구현은, 단순히 내부 위젯에 위임되지만 UI 의 내부 논리에 따라 구성은 물론 더 복잡해 질 수 있습니다.


내부 위젯에 단순히 위임하는 메소드의 경우, api 프로토콜에서 이 메소드를 메소드로서 정의하는 것이 합리적인지의 여부가 문제입니다. 이건 근본적으로 프로그래머가 설계한 결과입니다. 그러한 메소드[2]를 별도로 가지지 않으면 위젯을 보다 가볍게 구현할 수는 있지만, 캡슐화를 중단하는 것에 대한 명확한 목적이 되지는 못합니다.


과거 비용의 경우 3.3 절의 프로토콜 방법 목록에 예제가 있습니다. 거기에 정의된 세 가지 메소드는 클래스, api 또는 api-events 목록 항목이 변경되었을 때 수행 할 작업에 대해 신경 쓰는 사용자에게 알려줍니다. 이 장의 다른 예제들에 대해서도 근본적으로 봤을때는 마찬가지입니다. api 프로토콜의 각 메소드는 목적을 재사용자에게 전달합니다. 이러한 방식은 우리가 이 위젯이 구성 될 것이라고 예상 가능한 방식입니다. 이렇게 선언 된 메소드가 없으면, 이 위젯을 효과적으로 재사용 할 수 있도록 수행 할 수 있는 작업을 사용자에게 명확하지 않게 알려줍니다.


후자의 비용으로, 위젯의 재사용자가 내부 객체 (인스턴스 변수)에 직접 메시지를 보낸다면 캡슐화를 깨뜨리는 의미가 됩니다. 따라서 직접 메시지를 보내게 되면, 더 이상 인스턴스 변수를 더 나은 이름으로 바꾸거나 사용 된 위젯 종류를 변경하여 UI 의 내부를 자유롭게 변경할 수 없습니다. 이런 방식의 변경으로 인해 위젯의 재사용자는 중단 될 수 있으며, 따라서 향후이 위젯을 발전시킬 수 있는 방법은 심각하게 제한됩니다. 결국 공개 API 를 정의하고 이 API 의 기능이 동일하게 유지되도록 위젯의 향후 버전에서도 보장하는 것이 보다 안전한 방법입니다.


이런 이유들 때문에, 결국 UI 를 재사용하고 이후까지 UI 를 개선하는 방법을 고려하는 것은 중요합니다. UI 의 재사용을 쉽게 해주는 추가 메서드를 작성하는 방법을 사용하지 않으면 이후로 UI 를 개선하는 것은 어렵게 될 것입니다.


결론

이 장에서는 Spec 의 핵심인 기존 UI를 위젯으로 원활하게 재사용하는 기능에 대해 논의했습니다. 이 기능은 UI 제작자에게 큰 비용을 요구하지 않습니다. 유일하게 고민해야 하는 부분은 UI 를 사용자 정의 할 수 있는 방법(또는 사용자 정의 할 수 있어야 함)이며 공통 사용자 정의 방법은 api 프로토콜에 배치해야합니다.


상당한 비용을 소모하지 않으면서 복잡한 위젯을 재사용하는 것은, UI 의 작성 과정에 있어서 중요한 생산성 향상이 되기 때문에, 이 부분은 Spec 의 핵심 설계 목표였습니다. 우선 기존의 중요하지 않은 위젯을 재사용 할 수 있다는 점에서, 두 번째로 명확한 인터페이스를 통해 일관되고 보다 쉽게 ​​관리 할 수있는 하위 부품으로 UI 를 구성 할 수 있기 때문에 생산성의 향상이 가능해 집니다. 따라서 UI 를 이러한 하위 구성 요소로 생각하고 모듈 식으로 구성하여 생산성을 높일 것을 권장합니다.


Notes

  1. 행동을 정의해야할 책임
  2. 설정을 위한 메서드