الگو State…
9.3.7.1 مقدمه: #
دیزاین پترن State یک دیزاین پترن behavioral است که مبتنی بر Finite State Machine است. ما دیزاین پترن State را در زمینه نمونه ای از Vending Machine توضیح خواهیم داد. برای سادگی، بیایید فرض کنیم که Vending Machine فقط یک نوع کالا یا محصول دارد. همچنین برای سادگی، فرض می کنیم که یک Vending Machine می تواند در 4 حالت(state) مختلف باشد:
- hasItem
- noItem
- itemRequested
- hasMoney
یک Vending Machine خودکار نیز عملکردهای متفاوتی خواهد داشت. دوباره برای سادگی فرض می کنیم که فقط چهار عمل وجود دارد:
- Select the item
- Add the item
- Insert Money
- Dispense Item
9.3.7.2 چه زمانی از این الگو استفاده کنیم: #
- از الگوی طراحی State زمانی استفاده کنید که object می تواند در بسیاری از حالت های (states) مختلف باشد. بسته به درخواست فعلی، object باید وضعیت فعلی خود را تغییر دهد.
- در مثال بالا، Vending Machine می تواند در بسیاری از حالت های مختلف باشد. یک Vending Machine از یک state به حالت دیگر تغییر می کند. فرض کنید Vending Machine در مورد itemRequested است، پس از انجام عمل «hasMoney» به حالت «Insert Money» منتقل میشود.
- از این پترن زمانی استفاده کنید که یک شی بسته به وضعیت فعلی پاسخهای متفاوتی به درخواست یکسان داشته باشد. استفاده از الگوی طراحی states در اینجا از بسیاری از عبارات شرطی جلوگیری می کند
- به عنوان مثال در مورد Vending Machine، اگر کاربری بخواهد کالایی را خریداری کند، اگر آن مورد hasItemState باشد دستگاه ادامه خواهد داد یا اگر در noItemState باشد آن را رد می کند. اگر در اینجا متوجه شدید که Vending Machine خودکار به درخواست خرید یک کالا، بسته به اینکه آیا در hasItemState باشد، دو پاسخ متفاوت می دهد. به فایل vendingMachine.go زیر توجه کنید، هیچ نوع دستور شرطی ندارد. تمام منطق توسط پیاده سازی های concrete state اداره می شود.
9.3.7.3 ## UML Diagram: #
9.3.7.4 ## Mapping: #
جدول زیر mapping از نمودار UML به نمونه اجرایی پیاده سازی واقعی در کد را نشان می دهد.
Context | vendingMachine.go |
State Interface | state.go |
Concrete State 1 | noItemState.go |
Concrete State 2 | hasItemState.go |
Concrete State 3 | itemRequestedState.go |
Concrete State 4 | hasMoneyState.go |
9.3.7.5 ## توضیحات: #
- ما یک رابط(interface) از نوع ‘State’ داریم که signature توابع را تعریف می کند که نشان دهنده عملکرد در زمینه Vending Machine است. در زیر signatureهای توابع عملیاتی وجود دارد
- addItem(int) error
- requestItem() error
- insertMoney(money int) error
- dispenseItem() error
هر یک از پیادهسازیهای concrete state، هر 4 تابع بالا را پیادهسازی میکنند و روی اقدامات مربوط به هر کدام یا به حالت دیگری میروند یا پاسخی تولید میکنند.
هر یک از concrete stateها نیز یک اشاره گر را به object مربوط به Vending Machine فعلی تعبیه(embed) می کند تا انتقال حالت (state transition) در آن object اتفاق بیفتد.
9.3.7.6 ## مثال کاربردی: #
vendingMachine.go
1package main
2
3import "fmt"
4
5type vendingMachine struct {
6 hasItem state
7 itemRequested state
8 hasMoney state
9 noItem state
10
11 currentState state
12
13 itemCount int
14 itemPrice int
15}
16
17func newVendingMachine(itemCount, itemPrice int) *vendingMachine {
18 v := &vendingMachine{
19 itemCount: itemCount,
20 itemPrice: itemPrice,
21 }
22 hasItemState := &hasItemState{
23 vendingMachine: v,
24 }
25 itemRequestedState := &itemRequestedState{
26 vendingMachine: v,
27 }
28 hasMoneyState := &hasMoneyState{
29 vendingMachine: v,
30 }
31 noItemState := &noItemState{
32 vendingMachine: v,
33 }
34
35 v.setState(hasItemState)
36 v.hasItem = hasItemState
37 v.itemRequested = itemRequestedState
38 v.hasMoney = hasMoneyState
39 v.noItem = noItemState
40 return v
41}
42
43func (v *vendingMachine) requestItem() error {
44 return v.currentState.requestItem()
45}
46
47func (v *vendingMachine) addItem(count int) error {
48 return v.currentState.addItem(count)
49}
50
51func (v *vendingMachine) insertMoney(money int) error {
52 return v.currentState.insertMoney(money)
53}
54
55func (v *vendingMachine) dispenseItem() error {
56 return v.currentState.dispenseItem()
57}
58
59func (v *vendingMachine) setState(s state) {
60 v.currentState = s
61}
62
63func (v *vendingMachine) incrementItemCount(count int) {
64 fmt.Printf("Adding %d items\n", count)
65 v.itemCount = v.itemCount + count
66}
state.go
1package main
2
3type state interface {
4 addItem(int) error
5 requestItem() error
6 insertMoney(money int) error
7 dispenseItem() error
8}
noItemState.go
1package main
2
3import "fmt"
4
5type noItemState struct {
6 vendingMachine *vendingMachine
7}
8
9func (i *noItemState) requestItem() error {
10 return fmt.Errorf("Item out of stock")
11}
12
13func (i *noItemState) addItem(count int) error {
14 i.vendingMachine.incrementItemCount(count)
15 i.vendingMachine.setState(i.vendingMachine.hasItem)
16 return nil
17}
18
19func (i *noItemState) insertMoney(money int) error {
20 return fmt.Errorf("Item out of stock")
21}
22func (i *noItemState) dispenseItem() error {
23 return fmt.Errorf("Item out of stock")
24}
hasItemState.go
1package main
2
3import "fmt"
4
5type hasItemState struct {
6 vendingMachine *vendingMachine
7}
8
9func (i *hasItemState) requestItem() error {
10 if i.vendingMachine.itemCount == 0 {
11 i.vendingMachine.setState(i.vendingMachine.noItem)
12 return fmt.Errorf("No item present")
13 }
14 fmt.Printf("Item requestd\n")
15 i.vendingMachine.setState(i.vendingMachine.itemRequested)
16 return nil
17}
18
19func (i *hasItemState) addItem(count int) error {
20 fmt.Printf("%d items added\n", count)
21 i.vendingMachine.incrementItemCount(count)
22 return nil
23}
24
25func (i *hasItemState) insertMoney(money int) error {
26 return fmt.Errorf("Please select item first")
27}
28func (i *hasItemState) dispenseItem() error {
29 return fmt.Errorf("Please select item first")
30}
itemRequestedState.go
1package main
2
3import "fmt"
4
5type itemRequestedState struct {
6 vendingMachine *vendingMachine
7}
8
9func (i *itemRequestedState) requestItem() error {
10 return fmt.Errorf("Item already requested")
11}
12
13func (i *itemRequestedState) addItem(count int) error {
14 return fmt.Errorf("Item Dispense in progress")
15}
16
17func (i *itemRequestedState) insertMoney(money int) error {
18 if money < i.vendingMachine.itemPrice {
19 fmt.Errorf("Inserted money is less. Please insert %d", i.vendingMachine.itemPrice)
20 }
21 fmt.Println("Money entered is ok")
22 i.vendingMachine.setState(i.vendingMachine.hasMoney)
23 return nil
24}
25
26func (i *itemRequestedState) dispenseItem() error {
27 return fmt.Errorf("Please insert money first")
28}
hasMoneyState.go
1package main
2
3import "fmt"
4
5type hasMoneyState struct {
6 vendingMachine *vendingMachine
7}
8
9func (i *hasMoneyState) requestItem() error {
10 return fmt.Errorf("Item dispense in progress")
11}
12
13func (i *hasMoneyState) addItem(count int) error {
14 return fmt.Errorf("Item dispense in progress")
15}
16
17func (i *hasMoneyState) insertMoney(money int) error {
18 return fmt.Errorf("Item out of stock")
19}
20
21func (i *hasMoneyState) dispenseItem() error {
22 fmt.Println("Dispensing Item")
23 i.vendingMachine.itemCount = i.vendingMachine.itemCount - 1
24 if i.vendingMachine.itemCount == 0 {
25 i.vendingMachine.setState(i.vendingMachine.noItem)
26 } else {
27 i.vendingMachine.setState(i.vendingMachine.hasItem)
28 }
29 return nil
30}
main.go
1package main
2
3import (
4 "fmt"
5 "log"
6)
7
8func main() {
9 vendingMachine := newVendingMachine(1, 10)
10 err := vendingMachine.requestItem()
11 if err != nil {
12 log.Fatalf(err.Error())
13 }
14 err = vendingMachine.insertMoney(10)
15 if err != nil {
16 log.Fatalf(err.Error())
17 }
18 err = vendingMachine.dispenseItem()
19 if err != nil {
20 log.Fatalf(err.Error())
21 }
22
23 fmt.Println()
24 err = vendingMachine.addItem(2)
25 if err != nil {
26 log.Fatalf(err.Error())
27 }
28
29 fmt.Println()
30
31 err = vendingMachine.requestItem()
32 if err != nil {
33 log.Fatalf(err.Error())
34 }
35
36 err = vendingMachine.insertMoney(10)
37 if err != nil {
38 log.Fatalf(err.Error())
39 }
40
41 err = vendingMachine.dispenseItem()
42 if err != nil {
43 log.Fatalf(err.Error())
44 }
45}
Output: