Ch18. GRASP 패턴을이용한객체설계예제 Professor Seung-Hoon Choi
객체들이책임을가지고협력하는것을어떻게설계할것인가? applying OO Design principles and the UML 책임을할당하고객체들사이의협력을설계하는것은, 설계시에가장중요하고창조적인작업이다. 2/55
유스케이스실체화 특정유스케이스가설계모델내에서어떻게실현될것인가를, 협력하는 객체들의관점에서기술 UML 상호작용다이어그램은, 유스케이스실체화를보여주는일반적인언어이다. 3/55
Business Modeling date... Sale Sample UP Artifact Relationships Domain Model 1 1..* Sales LineItem... quantity... Use-Case Model Process Sale inspiration for names of some software domain objects Requirements starting events to design for, and detailed postcondition to satisfy Cashier Process Sale Use Case Diagram Operation: enteritem( ) Post-conditions: -... Operation Contracts ideas for the postconditions use case names system operations : Register : Cashier 1. Customer arrives... 2.... 3. Cashier enters item identifier. Use Case Text system events make NewSale() enteritem (id, quantity) : System System Sequence Diagrams Design Model functional requirements that must be realized by the objects : ProductCatalog Glossary Supplementary Specification item details, formats, validation non-functional requirements domain rules : Sale Design enteritem (itemid, quantity) d = getproductdescription(itemid) addlineitem( d, quantity )... Register makenewsale() enteritem(...)... * 1... ProductCatalog getproductdescription(...)...
UP 산출물들간의관계 ( 이전슬라이드그림참조 ) 유스케이스는 SSD에서의시스템오퍼레이션들을제안한다. 시스템오퍼레이션은도메인계층의 Controller로들어가는시작메시지가된다. 도메인계층의인터랙션다이어그램은, 객체들이요구되는태스크를달성하기위해 어떻게상호작용하는가 (use case realization) 을보여준다. 5/55
이번 POS 개발 iteration 에서는 ProcessSale 유스케이스의 SSD 에 나타나는다음 4 개의시스템오퍼레이션을설계한다. makenewsale, etc., are the system operations from the SSD each major interaction diagram starts with a system operation going into a domain layer controller object, such as Register makenewsale 1:??? :Register Window objects or GUI widget objects or Web control objects... 시스템이벤트메시지 enteritem endsale :Register :Register 1:??? 1:??? makepayment :Register 1:??? UI LAYER DOMAIN LAYER 6
유스케이스실체화를커뮤니케이션다이어그램을이용하여 보여준다면, 시스템이벤트메시지하나당다이어그램이하나씩 필요하다. ( 그림 17.1) makenewsale() :Register 1:???() enteritem() :Register 1:???() endsale() :Register 1:???() makepayment() :Register 1:???() 7/55
시퀀스다이어그램을사용한다면, 하나의다이어그램에모든 시스템이벤트처리를보여주는것이가능하다. : Register makenewsale create : Sale Window objects or GUI widget objects or Web control objects... : Register : ProductCatalog enteritem(...) desc = getproductdesc( itemid )... UI LAYER DOMAIN LAYER 8
너무복잡해지므로, 각시스템이벤트메시지마다, 하나씩의순차 다이어그램을그려도된다.( 그림 17.3) : Register makenewsale() create() : Sale enteritem (itemid, quantity) : Register spec := getproductspec( itemid ) addlineitem( spec, quantity )... : ProductCatalog : Sale 9/55
약정 (Contract) 과유스케이스실체화 유스케이스텍스트로부터직접유스케이스실체화를설계하는것도가능함 약정이작성된시스템오퍼레이션에대해서는 사후조건 (post-condition) 의상태변화를다루고, 그요구사항을만족시키기위해메시지상호작용을설계한다. 10/55
약정 (Contract) 과유스케이스실체화 예 : enteritem 시스템오퍼레이션에대해 SalesLineItem 인스턴스 생성이라는상태변화를만족시킨다. enteritem(id, qty) :Register 1: makelineitem(...) :Sale 1.1: create(...) :SalesLineItem 11/55
초기화와 Start Up 유스케이스 대부분의시스템은 Start Up 유스케이스와, 응용프로그램시작과관련된 어떤초기화시스템오퍼레이션을가진다. 이 startup 시스템오퍼레이션은실행은제일먼저되지만, 초기화 활동에필요한모든정보를충분히얻기위해, 맨나중에설계한다. 12
고객이구입할물건을들고오면, 출납원은새로운판매를시작할것을요청함 이때, makenewsale 시스템오퍼레이션이일어난다. 약정 CO1: makenewsale 오퍼레이션 : makenewsale( ) 상호참조 : 사용사례 : Process Sale 사전조건 : 없음사후조건 : - Sale 인스턴스 s가생성되었다 ( 인스턴스생성 ) - s가 Register와연관되었다 ( 연관형성 ) - s의속성들이초기화되었다. 13/55
Controller 클래스선택 먼저, 시스템오퍼레이션메시지 makenewsale에대한컨트롤러를선택해야한다. Controller 패턴에의해다음과같은선택이가능하다 전체 시스템, 장치혹은서브시스템을나타내는클래스 Store, Register, POSSystem 등 하나의유스케이스시나리오의모든시스템이벤트들에대한수신자또는처리자를나타내는클래스 ProcessSaleHandler, ProcessSaleSession 14
Controller 클래스선택 ( 계속 ) 단지몇개의시스템오퍼레이션만있고, 외관컨트롤러 (facade controller) 가너무많은책임을맡지않는다면, Register와같은외관컨트롤러를선택하는것이바람직하다. 시스템오퍼레이션이많고, 각컨트롤러클래스를가볍게유지하기위해책임을분산시키고자한다면, 사용사례컨트롤러 (use-case controller) 를 선택하는것이적합하다. 이경우에는 Register 를선택함 :Register makenewsale create :Sale 15
새로운 Sale 생성 Creator 패턴은, 생성되는객체를포함하고기록하는클래스에그객체생성의책임을부여할것을제안한다. 도메인모델을분석하면, Register가 Sale을기록하는것으로생각됨 Register가 Sale을생성시키는책임을가지는후보클래스 Sale은, 여러개의 SalesLineItem 인스턴스를포함하고유지한다. 따라서, Sale을생성할때, 여러개의 SalesLineItem 인스턴스를포함하고유지할컬렉션 (List와같은컨테이너 ) 을 Sale이생성해야한다. 16
by Creator and Controller makenewsale :Register create Register creates a Sale by Creator :Sale by Creator, Sale creates an empty collection (such as a List) which will eventually hold SalesLineItem instances create lineitems : List<SalesLineItem> this execution specification is implied to be within the constructor of the Sale instance Collection 객체 (List 다중객체를의미함 ) 17
출납원이 itemid 와구매하려는물품의양을입력할때, enteritem 시스템오퍼레이션이일어난다. 약정 CO2: enteritem 오퍼레이션 : enteritem(itemid: ItemID, quantity: integer) 상호참조 : 사용사례 : Process Sale 사전조건 : 판매가진행중이다. 사후조건 : - 하나의 SalesLineItem 인스턴스 sli가생성되었다 ( 인스턴스생성 ) - sli가현재의 Sale과연관되었다 ( 연관형성 ) - sli.qunatity가 quantity로되었다 ( 속성수정 ) - itemid가일치하는 ProductDiscription과 sli가연관되었다 ( 연관형성 ) 18
컨트롤러클래스선택 Controller 패턴에의해, enteritem 의경우도 Register 가컨트롤러역할을 한다. 물품설명과가격의화면출력? 출력작업은비-GUI 객체 ( 예를들어, Register나 Sale) 의책임이아니다. 정보를화면에출력하는것이유스케이스에적혀있지만, 현재설계에서는이책임은무시한다. 19
새로운 SalesLineItem 생성 enteritem 약정에서사후조건은 SlaesLineItem의생성, 초기화, 연관을나타낸다. Sale이 SalesLineItem 인스턴스하나를생성하여컬렉션에저장한다. 사후조건을보면, SalesLienItem이생성될때수량 (quantity) 를필요로한다. 따라서, Register는 Sale에게 quantity를보내야하고, Sale은 SalesLineItem 인스턴스를생성할때, 생성자에게이 quantity를보내야한다. 20
ProductDescription 찾기 SalesLineItem 은 itemid 가일치하는 ProductDescription 과연관되어야 한다. 즉, itemid 가일치하는 ProductDescription 을찾아야함을의미한다. 책임명시 : itemid 가일치하는 ProductDescription 을찾을책임은누가 맡아야하는가? 21
ProductDescription 찾기 ( 계속 ) Information Expert 패턴적용 어떤책임을이행하기위해필요한정보를갖는객체가그일을해야한다 누가 ProductDescription 에관해모든것을알고있는가? 도메인모델을분석하면, ProductCatalog가논리적으로 ProductDescription 들을전부포함하고있다. 따라서, ProductCatalog가 ProductDescription 을찾는책임에대한좋은후보가된다. ProductCatalog.getProductDescription ( ) 메소드로구현될것이다. 22
ProductCatalog 에대한 Visibility( 가시성 ) ProductDescription 을요청하기위해누가 ProductCatalog 에게 getproductdescription 메시지를전달할것인가? 가정 : 초기의 Start up 사용사례동안 Register와 ProductCatalog 인스턴스가생성되고 Register객체에서 ProductCatalog 객체로영구적인연결이있다라고가정할수있다. 따라서, Register가 ProductCatalog에게 getproductdescription 메시지를전달하는것이가능하다. 23
Final Design by Controller by Creator enteritem(id, qty) :Register 2: makelineitem(desc, qty) :Sale 1: desc = getproductdesc(id) 2.1: create(desc, qty) by Expert :Product Catalog sl: SalesLineItem 1.1: desc = get(id) 2.2: add(sl) : Map<ProductDescription> lineitems : List<SalesLineItem> add the newly created SalesLineItem instance to the List 24
Partial DCD(Design Class Diagram) 1 catalog... ProductCatalog getproductdesc(...) descriptions {Map} 1..* ProductDescription description : Text price : Money itemid: ItemID... description 1 Sale... Register enteritem(...)... currentsale 1 iscomplete : Boolean time : DateTime makelineitem(...)... lineitems {ordered} 1..* SalesLineItem quantity : Integer... 25
데이터베이스에서 ProductDescription 들을추출하기 POS 최종버젼에서는 ProductDescription 정보들이메모리가아니라 데이터베이스에저장되어야함 그러나, 현재 iteration 에서는고려하지않음 26
출납원이판매가끝났음을나타내는버튼을누르면, endsale 시스템오퍼레이션이일어난다. 약정 CO3: endsale 오퍼레이션 : endsale( ) 상호참조 : 사용사례 : Process Sale 사전조건 : 판매가진행중이다. 사후조건 : - Sale.isComplete이참이되었다 ( 속성수정 ) 27
컨트롤러클래스선택 Controller 패턴에의거, Register 클래스를선택함 Sale.isComplet 속성설정 책임 : 누가 Sale 의 iscomplete 속성을참으로설정할것인가? Information Expert 패턴에의거, Sale 자신이어야한다. 이유 : Sale 이 iscomplete 속성을가지므로 endsale( :Register 1: becomecomplete s :Sale by Controller by Expert 28
판매총액계산 ProcessSale 유스케이스의주요성공시나리오마지막단계 : 7. 시스템은세금을포함한총액을보여준다 누군가가판매총액을계산해야한다. Model-View Separation 원리에의해, 판매총액이화면에어떻게표시될것인가는설계하지않는다. 컨트롤러나생성문제가아니면, Information Expert 패턴을제일먼저고려한다. 29
판매총액계산분석과정 1. 책임을기술한다. 누가판매총액을알책임이있는가? 2. 필요한정보를요약한다. 판매총액은, 모든판매라인아이템의소계의합이다. 판매라인아이템의소계 = 라인아이템의양 * 제품에기재된가격 판매총액계산에필요한정보 Information Expert 구현오퍼레이션 ProductDescription.price ProductDescription ProductDescription.getPrice( ) SalesLineItem.quantity SalesLineItem SalesLineItem.getSubTotal( ) 현재 Sale 의모든 SalesLineItem Sale Sale.getTotal( ) 30
Sale.getTotal Design Sale이 gettotal 메시지를받을때어떤일이일어나는가를상호작용다이어그램으로설계해야한다. gettotal( ) 메시지는누가보내는가? Java JFrame과같은 UI 계층의객체가될것이다. by Expert by Expert UML: note the selector notation to select elements from the lineitems collection tot = gettotal :Sale 1 * [i = 1..n]: st = getsubtotal lineitems[ i ]: SalesLineItem 1.1: pr = getprice :ProductDescription 31
노트기호로메소드를보여줌 쳋 ethod? public void gettotal() { int tot = 0; for each SalesLineItem, sli tot = tot + sli.getsubtotal(); return tot } tot = gettotal :Sale 1 *[ i = 1..n]: st = getsubtotal lineitems[ i ] : SalesLineItem 1.1: pr = getprice :ProductDescription 32
지불을위해고객이건넨현금이액수를출납원이입력할때, makepayment 시스템오퍼레이션이발생한다. 약정 CO4: makepayment 오퍼레이션 : makepayment(amount:money) 상호참조 : 사용사례 : Process Sale 사전조건 : 판매가진행중이다. 사후조건 : - Payment의인스턴스 p 가생성되었다 ( 인스턴스생성 ) - p.amounttendered가 amount로되었다 ( 속성수정 ) - p가현재의 Sale과연관되었다 ( 연관형성 ) - 현재의 Sale이 Store와연관되었다 ( 연관형성 ) ( 완료된판매들의기록에이판매내역을추가하기위하여 ) 33
Payment 생성 Creator 패턴적용 Register 가논리적으로 Payment 를기록 => Register 가후보 Sale 소프트웨어가 Payment 를밀접하게사용 => Sale 이후보 여러가지의설계선택이주어졌을때, 좋은응집도와결합도를가지는 설계를선택하라. 34
Payment 생성 ( 계속 ) Sale 이 Payment 를생성하는것으로선택하면, Register 의일이줄어들고, 결합도가낮아진다. by Controller by Creator and Low Coupling makepayment(cashtendered) :Register 1: makepayment(cashtendered) :Sale 1.1: create(cashtendered) :Payment 35
판매로그기록 (Logging) 판매완료후에, 판매내역을로그에기록해야한다. 책임 : 누가로그기록될판매들을알고, 로그기록을수행할것인가? 재정과밀접한관계가있는 Store가담당하는것이좋다 설계가진행되면서 Store의응집력이약해지면, SalesLeger( 판매원장 ) 라는새로운클래스를도입할수도있다. see the next slide. 36
Sale Sale...... Logs-completed * * Logs-completed...... 1 1 Store SalesLedger... addsale(s : Sale)...... addsale(s : Sale)... Store is responsible for knowing and adding completed Sales. Acceptable in early development cycles if the Store has few responsibilities. SalesLedger is responsible for knowing and adding completed Sales. Suitable when the design grows and the Store becomes uncohesive. 37
판매로그기록 (Logging) note that the Sale instance is named 's' so that it can be referenced as a parameter in messages 2 and 2.1 makepayment(cashtendered) :Register 1: makepayment(cashtendered) s :Sale 2: addsale(s) 1.1: create(cashtendered) by Expert :Store :Payment 2.1: add(s) completedsales: List<Sale> 38
잔액계산 Process Sale 사용사례는, 지불의잔액이영수증에출력되고화면에도출력됨을암시한다. Model-View Separation 원리에의해, 출력방식은신경쓸필요가없다. 책임 : 누가잔액을알책임이있는가? 잔액계산에필요한정보 : 판매총액과고객이건넨금액 정보전문가는 Sale과 Payment 이다. 39
잔액계산 방법 #1: Payment인경우 총액을묻기위해 Sale로의가시성이필요하다. 현재로서는 Payment가 Sale에관하여알지못하므로, 이설계는결합도를높인다. 방법 #2: Sale인경우 고객이건넨금액을묻기위해 Payment로의가시성이필요하다. Sale은 Payment 생산자로서, 이미 Payment에대한가시성을가지고있다. 이접근방법은결합도를높이지않는다. => 이방법을선택함 40
잔액계산 { bal = pmt.amount - s.total } bal = getbalance s :Sale 1: amt = getamount pmt: Payment 2: t = gettotal 41
A more complete DCD Store address : Address name : Text addcompletesale(...) 1 catalog catalog 1 ProductCatalog... getproductdesc(...) descriptions {Map} 1..* ProductDescription description : Text price : Money itemid: ItemID... register... 1 Register endsale() enteritem(...) makenewsale() makepayment(...) currentsale 1 completedsales {ordered} Sale iscomplete : Boolean time : DateTime becomecomplete() makelineitem(...) makepayment(...) gettotal() * lineitems {ordered} 1..* payment 1 SalesLineItem quantity : Integer getsubtotal() Payment amount : Money... description 1 42
2 가지방법 방법 #1. Initializer 객체이용 Java의 main( ) 메소드에서 Initializer 객체의초기화메소드호출 Initializer 객체의초기화메소드안에서 UI 객체와 Domain 객체를생성한후 Domain 객체를 UI 객체에전달함 2. UI 객체가먼저만들어지고 UI 객체가 factory 객체등을이용하여 Domain 객체를얻은후이를 reference한다. 43
일단, UI 객체가 Register 인스턴스에대한연관을가지게되면 UI 객체는 enteritem 이나 endsale 같은시스템이벤트메시지들을 Register 인스턴스에게전달할수있다. presses button Cashier actionperformed( actionevent ) UI Layer :ProcessSale JFrame 1: enteritem(id, qty) system event Domain Layer :Register 44
enteritem 메시지의경우, 각물품들이입력될때윈도우가그 시점까지의합계를보여주어야한다. UI 객체와도메인객체와의메시지전달방식에따라여러가지설계 방법이존재한다. 45
해결책 #1 gettotal 메소드를 Register에추가한다. UI가 gettotal 메시지를 Register에보내고, Register는이메시지를 Sale에전달한다. 장점 : UI에서도메인계층으로의결합도를낮춘다. 단점 : Register 객체의응집력이낮아진다. presses button Cashier actionperformed( actionevent ) UI Layer :ProcessSale JFrame 1: enteritem(id, qty) system event Domain Layer :Register 46
해결책 2: UI가현재 Sale 객체에대한참조를 Register에게요청한다. 그후에, UI가합계 ( 혹은판매와관련된다른정보 ) 가필요하면 Sale에직접메시지를보낸다. 단점 : UI에서도메인계층으로의결합도를높임. 그러나, Sale은안정된객체라면, 안정된객체와의높은결합도는문제가되지않는다. presses button 책에서선택한설계방법 Cashier actionperformed( actionevent ) UI Layer :ProcessSale JFrame 3: t = gettotal 1: enteritem(id, qty) 2 [no sale] : s = getsale : Sale Domain Layer :Register s : Sale 47
UI 계층은도메인논리에대한책임을갖지않는것이좋다. 예 : Java 의윈도우 (ProcessSaleJFrame) 은응용프로그램의논리를 처리하지않고, 도메인객체로메시지를전달하는역할만담당하도록 하는것이좋다. 48
언제초기화를설계하는가? 초기화설계를마지막에해라 대부분의시스템은 Start Up 유스케이스와 응용프로그램시작과관련된어떤초기시스템오퍼레이션을가진다. 이 startup 시스템오퍼레이션이제일먼저실행되지만, 초기화활동에 필요한모든정보를충분히얻기위해, 맨나중에설계한다. 49
어플리케이션을어떻게시작하는가? 초기도메인객체 (initial domain object) 를생성하라 비교 : 도메인객체 : 도메인논리와관련된객체 UI 객체 : 사용자인터페이스와관련된객체 초기도메인객체는생성되고난후, 직접자식도메인객체를생성할책임이있다. 50
어플리케이션을어떻게시작하는가?( 계속 ) main( ) 메소드에서주로초기도메인객체가생성된다. 예 : Store 초기도메인객체가생성됨 => Store 객체가 Register 객체를생성함 Public class Main { public static main(string[ ] args) { Store store = new Store( ); Register register = store.getregister( ); ProessSaleJFrame frame = new ProcessSaleJFrame(register);...... } } 51
POS 응용프로그램의 startup 오퍼레이션 관리자가 POS 시스템을켜고, 소프트웨어를메모리에로드할때 startup 시스템 오퍼레이션이일어난다. 제어는, 초기도메인객체가생성된후 UI 계층 ( 예를들어, Java 의 JFrame) 에남게 된다. Event-driven 방식 52
초기도메인객체선택방법 어떤것이초기도메인객체클래스가되어야하는가? 도메인객체들의포함관계나, 집합관계계층구조상 root 또는 root 에서가장가까운클래스를선택하라. 예 : Register 와같은외관컨트롤러나, 다른객체들모두또는대부분을 포함하는 Store 클래스 예제에서는, Store 클래스선택함 53
Store.create ( 초기도메인객체의생성자 ) 설계 초기화시필요한작업 Store, Register, ProductCatalog, ProductDescription 들이생성되어야한다. ProductCatalog는 ProductDescription들과연관되어야한다. Store는 ProductCatalog와연관되어야한다. Store는 Register와연관되어야한다. Register는 ProductCatalog와연관되어야한다. 54
1.2: loadprodspecs( ) 메소드안에서 1.2.1*: create(id, price, description) 1.2.2*: add(ps) 메시지를차례로호출한다. pass a reference to the ProductCatalog to the Register, so that it has permanent visibility to it create :Store 2: create(pc) :Register by Creator 1: create create an empty collection object 1.1: create pc: ProductCatalog 1.2.2*: put(id, pd) descriptions: Map<ProductDescription> 1.2: loadprodspecs() the * in sequence number indicates the message occurs in a repeating section 1.2.1*: create(id, price, description) pd: ProductDescription 55
역속적인 (persistent) 객체 : ProductDescription ProductDescription 인스턴스는영구저장매체에위치한다. 관계형데이터베이스 객체데이터베이스 관계형데이터베이스사용시, ProductDescription 정보에대한데이터모델링이필요하다. ProductDescription들의테이블 (in DB) ItemID Price Description 1 700 새우깡 2 600 콜라 새우깡 객체 (in Memory) p:productdescription itemid: 1 price: 700 description: 새우깡 56
1.2.1*: create(id, price, description) in 슬라이드 p.55 ProductDescription의생성자이다. id, price, description 인자들의값은 영구저장매체 (Database) 에서읽어온값이다. 이인자들을이용하여 ProductDescription 생성자를호출한다. 57
58 Register id Item Store name address Sale datetime / total CashPayment amounttendered Sales LineItem quantity Cashier id Customer Product Catalog Product Description itemid description price Stocks * Houses 1..* Used-by * Contains 1..* Describes * Captured-on Contained-in 1..* Records-sale-of 0..1 Paid-by Is-for Logscompleted * Works-on 1 1 1 1 1..* 1 1 1 1 1 1 1 0..1 1 1 Ledger Recordsaccountsfor 1 1