9.3.3 الگو Iterator

9.3.3 الگو Iterator

9.3.3.1-هدف #

الگوي طراحی تکرارکننده (Iterator) یک الگوی رفتاری است که به شما امکان می دهد عناصر یک مجموعه را بدون نمایش ساختار درونی آن (فهرست، پشته، درخت و غیره) پیمایش کنید.

9.3.3.2-مشکل #

مجموعه ها (collections) یکی از پرکاربردترین انواع داده در برنامه نویسی هستند. با این وجود، یک مجموعه فقط یک ظرف برای گروهی از اشیاء است.

iterator-problem1 (انواع مختلف از collections)

اکثر مجموعه ها عناصر خود را در لیست های ساده ذخیره می کنند. با این حال، برخی از آنها بر اساس پشته‌ها، درختان، نمودارها و سایر ساختارهای داده پیچیده ساخته شده‌اند.

اما مهم نیست که یک مجموعه چگونه ساختار یافته باشد، باید روشی برای دسترسی به عناصر آن ارائه دهد تا کدهای دیگر بتوانند از این عناصر استفاده کنند. باید راهی برای مرور هر عنصر از مجموعه بدون دسترسی مکرر به عناصر مشابه وجود داشته باشد.

اگر مجموعه ای مبتنی بر لیست دارید، این کار ممکن است آسان به نظر برسد. شما فقط روی تمام عناصر حلقه می زنید. اما چگونه عناصر یک ساختار داده پیچیده مانند درخت را به طور متوالی طی کنید؟ به عنوان مثال، ممکن است یک روز فقط با پیمایش عمق اول (depth-first) یک درخت مشکلی نداشته باشید. اما روز بعد ممکن است به پیمایش عرض اول (breadth-first) نیاز داشته باشید. و هفته بعد، ممکن است به چیز دیگری مانند دسترسی تصادفی به عناصر درخت نیاز داشته باشید.

iterator-problem2 (از یک مجموعه می‌توان به چند روش مختلف عبور کرد.)

افزودن الگوریتم های پیمایش بیشتر و بیشتر به مجموعه به تدریج مسئولیت اصلی آن که ذخیره کارآمد داده است را تحت الشعاع قرار می دهد. علاوه بر این، برخی الگوریتم‌ها ممکن است برای یک برنامه خاص طراحی شده باشند، بنابراین گنجاندن آنها در یک کلاس عمومی مجموعه عجیب خواهد بود.

از طرف دیگر، کد کلاینت که قرار است با مجموعه‌های مختلفی کار کند، حتی ممکن است اهمیتی ندهد که آنها عناصر خود را چگونه ذخیره می‌کنند. با این حال، از آنجایی که همه مجموعه‌ها روش‌های مختلفی برای دسترسی به عناصر خود ارائه می‌دهند، شما چاره‌ای جز اتصال کد خود به کلاس‌های مجموعه خاص ندارید.

9.3.3.3-راه‌حل #

ایده اصلی الگوی تکرارکننده (Iterator) این است که رفتار پیمایش یک مجموعه را به یک شیء جداگانه به نام «تکرارکننده» (Iterator) استخراج کند.

iterator-solution1 (تکرار کننده ها الگوریتم های پیمایش مختلفی را پیاده سازی می کنند. چندین شی تکرارکننده می‌توانند همزمان از یک مجموعه عبور کنند.)

یک شیء تکرارکننده علاوه بر پیاده سازی الگوریتم پیمایش، تمام جزئیات پیمایش مانند موقعیت فعلی و تعداد عناصر باقی مانده تا انتها را دربرمی گیرد. به همین دلیل، چندین تکرارکننده می توانند به طور همزمان و مستقل از یکدیگر، یک مجموعه را طی کنند.

معمولا تکرارکننده‌ها یک روش اصلی برای دریافت عناصر مجموعه ارائه می دهند. کاربر می تواند این روش را تا زمانی که چیزی برنگردد اجرا کند، به این معنی که تکرارکننده تمام عناصر را پیمایش کرده است.

همه تکرارکننده‌ها باید یک رابط مشترک را پیاده سازی کنند. این کار باعث می شود کد کلاینت با هر نوع مجموعه یا هر الگوریتم پیمایشی سازگار باشد، به شرطی که یک تکرارکننده مناسب وجود داشته باشد. اگر به روش خاصی برای پیمایش یک مجموعه نیاز دارید، فقط یک کلاس تکرارکننده جدید بدون نیاز به تغییر مجموعه یا مشتری ایجاد کنید.

9.3.3.4-نمونه واقعی #

iterator-comic-1-en

تصمیم می‌گیرید برای چند روز به شهر رم در ایتالیا سفر کنید و از تمام مناظر و جاذبه های اصلی آن دیدن کنید. اما هنگامی که به آنجا رسیدید، ممکن است زمان زیادی را برای قدم زدن در مسیرهای پر پیچ و خم تلف کنید، بدون اینکه حتی بتوانید نماد باستانی مثل کولوسئوم را پیدا کنید.

از طرف دیگر، می توانید یک اپلیکیشن راهنمای مجازی برای گوشی هوشمند خود بخرید و از آن برای مسیریابی استفاده کنید. این کار هوشمند و ارزان است و می توانید تا هر زمان که بخواهید در مکان‌های جالبی توقف و دیدن کنید.

یک راه حل دیگر این است که بخشی از بودجه سفر را صرف استخدام یک راهنمای محلی کنید که شهر را مثل کف دستش بشناسد. راهنما می‌تواند تور را مطابق با سلیقه شما تنظیم کند، هر جاذبه‌ای را به شما نشان دهد و داستان‌های هیجان‌انگیزی تعریف کند. این حتی سرگرم‌کننده‌تر خواهد بود، اما افسوس، گران‌تر هم خواهد بود.

همه این گزینه‌ها - مسیرهای تصادفی که در ذهن شما به وجود می‌آیند، راهنمای هوشمند گوشی هوشمند یا راهنمای انسانی - به عنوان تکرارکننده‌هایی بر روی مجموعه عظیم مناظر و جاذبه‌های واقع در رم عمل می‌کنند.

9.3.3.5-مثال #

الگوی طراحی تکرارکننده (Iterator) #

الگوی طراحی تکرارکننده (Iterator) یک الگوی رفتاری است که به شما امکان می دهد عناصر یک مجموعه را بدون نمایش ساختار درونی آن (فهرست، پشته، درخت و غیره) پیمایش کنید.

اجزای اصلی:

  • رابط تکرارکننده (Iterator Interface): این اینترفیس عملیات اصلی برای پیمایش در یک مجموعه را تعریف می کند، به طور معمول شامل متد (Method) هایی مانند hasNext() برای بررسی وجود عناصر بیشتر و getNext() برای بازیابی عنصر بعدی.
  • رابط مجموعه (Collection Interface): این اینترفیس خود مجموعه را نشان می دهد. ممکن است متدی مانند createIterator() را اعلام کند که یک شیء تکرارکننده خاص برای نوع مجموعه را برمی‌گرداند.
  • تکرارکننده خاص (Concrete Iterator): این کلاسی است که رابط Iterator را برای یک نوع مجموعه خاص پیاده سازی می کند. این حالت تکرار (مانند موقعیت فعلی) را حفظ می کند و متد هایی مانند hasNext() و getNext() را ارائه می دهد که خاص ساختار مجموعه است.
  • مجموعه خاص (Concrete Collection): این کلاسی است که اینترفیس یا رابط Collection را برای یک ساختار داده خاص (مانند لیست، درخت) پیاده سازی می کند. این متد createIterator() را ارائه می دهد که یک شیء تکرارکننده خاص را برای پیمایش عناصر آن برمی گرداند.

مزایا:

  • جداسازی: منطق تکرار را از خود مجموعه جدا می کند و اتصالات ضعیف را ارتقا می دهد و کد را انعطاف پذیرتر و قابل استفاده مجدد می کند.
  • چندین پیمایش: امکان پیمایش همزمان در یک مجموعه با استفاده از تکرارکننده های مختلف، به طور بالقوه با الگوریتم های پیمایش مختلف را فراهم می کند.
  • اصل بسته-باز: پیاده سازی های جدید تکرارکننده را می توان برای نیازهای مختلف پیمایش بدون تغییر در کلاس های مجموعه اضافه کرد.

با استفاده از تکرارکننده‌‌ها، می توانید کدی بنویسید که با مجموعه های مختلف بدون وابستگی به ساختارهای خاص آنها کار می کند. این امر به ترویج کدی تمیزتر و قابل نگهداری تر کمک می کند.

9.3.3.6-Mapping #

جدول زیر نگاشت بین بازیگران نمودار UML و بازیگران پیاده‌سازی واقعی را در “مثال” زیر نشان می‌دهد:

Collectioncollection.go
Concrete CollectionuserCollection.go
Iteratormac.go
Concrete Iterator 1userIterator.go
Clientmain.go

9.3.3.7-پیاده سازی #

collection.go

1package main
2
3type collection interface {
4    createIterator() iterator
5}

userCollection.go

 1package main
 2
 3type userCollection struct {
 4    users []*user
 5}
 6
 7func (u *userCollection) createIterator() iterator {
 8    return &userIterator{
 9        users: u.users,
10    }
11}

iterator.go

1package main
2
3type iterator interface {
4    hasNext() bool
5    getNext() *user
6}

userIterator.go

 1package main
 2
 3type userIterator struct {
 4    index int
 5    users []*user
 6}
 7
 8func (u *userIterator) hasNext() bool {
 9    if u.index < len(u.users) {
10        return true
11    }
12    return false
13}
14
15func (u *userIterator) getNext() *user {
16    if u.hasNext() {
17        user := u.users[u.index]
18        u.index++
19        return user
20    }
21    return nil
22}

user.go

1package main
2
3type user struct {
4    name string
5    age  int
6}

main.go

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    user1 := &user{
 7        name: "a",
 8        age:  30,
 9    }
10    user2 := &user{
11        name: "b",
12        age:  20,
13    }
14    userCollection := &userCollection{
15        users: []*user{user1, user2},
16    }
17    iterator := userCollection.createIterator()
18    for iterator.hasNext() {
19        user := iterator.getNext()
20        fmt.Printf("User is %+v\n", user)
21    }
22}

Output:

1User is &{name:a age:30}
2User is &{name:b age:20}

Full Working Code: #

 1package main
 2
 3import "fmt"
 4
 5type collection interface {
 6    createIterator() iterator
 7}
 8
 9type userCollection struct {
10    users []*user
11}
12
13func (u *userCollection) createIterator() iterator {
14    return &userIterator{
15        users: u.users,
16    }
17}
18
19type iterator interface {
20    hasNext() bool
21    getNext() *user
22}
23
24type userIterator struct {
25    index int
26    users []*user
27}
28
29func (u *userIterator) hasNext() bool {
30    if u.index < len(u.users) {
31        return true
32    }
33    return false
34}
35
36func (u *userIterator) getNext() *user {
37    if u.hasNext() {
38        user := u.users[u.index]
39        u.index++
40        return user
41    }
42    return nil
43}
44
45type user struct {
46    name string
47    age  int
48}
49
50func main() {
51    user1 := &user{
52        name: "a",
53        age:  30,
54    }
55    user2 := &user{
56        name: "b",
57        age:  20,
58    }
59    userCollection := &userCollection{
60        users: []*user{user1, user2},
61    }
62    iterator := userCollection.createIterator()
63    for iterator.hasNext() {
64        user := iterator.getNext()
65        fmt.Printf("User is %+v\n", user)
66    }
67}

Output:

1User is &{name:a age:30}
2User is &{name:b age:20}