9.4.14 الگو Bridge Channel

9.4.14 الگو Bridge Channel

9.4.14.1 توضیحات #

الگوی Bridge Channel یکی از الگوهای ساده اما بسیار مفید در زبان Go است که امکان اتصال و انتقال داده بین دو یا چند کانال مستقل را فراهم می‌کند. این الگو زمانی کاربرد دارد که بخواهید داده‌های تولیدشده در یک goroutine یا زیرسیستم را پس از دریافت، به کانال دیگری هدایت کنید؛ به عبارتی، مانند یک پل (bridge) عمل می‌کنید که داده‌ها را از یک کانال ورودی گرفته و به کانال خروجی منتقل می‌نماید.

در عمل، یک goroutine بین دو کانال قرار می‌گیرد: یکی برای دریافت (مثلاً in <-chan T) و دیگری برای ارسال (out chan<- T). این goroutine یک حلقه ساده for با range in اجرا می‌کند و هر مقداری که از کانال ورودی دریافت کند را بلافاصله به کانال خروجی می‌فرستد. این تکنیک برای decoupling بین تولیدکننده و مصرف‌کننده عالی است و در سناریوهایی مانند اتصال چند مرحله pipeline، تغییر مسیر داده‌ها، فیلترینگ داده‌ها یا حتی انتقال بین کانال‌هایی با ویژگی‌های متفاوت (مثلاً بافر متفاوت یا ownership مختلف) بسیار کاربرد دارد.

از مزایای این الگو می‌توان به سادگی در پیاده‌سازی، انعطاف‌پذیری بالا، و جداسازی مسئولیت‌ها اشاره کرد. این الگو به خصوص در سیستم‌هایی که نیاز به پردازش یا هدایت جریان‌های داده بین چند بخش یا ماژول دارند، بسیار مؤثر است و به افزایش خوانایی و maintainability کد کمک می‌کند. در صورت نیاز می‌توان عملیات اضافی مثل تبدیل داده، اعتبارسنجی یا لاگ‌گیری را نیز داخل goroutine پل انجام داد تا ساختار تمیزتر باقی بماند.

9.4.14.2 دیاگرام #

flowchart LR A[Producer / Input Source] B[Input Channel] C[Bridge Goroutine] D[Output Channel] E[Consumer / Output Sink] A --> B B --> C C --> D D --> E style B fill:#e2f0fc,stroke:#377dbf,stroke-width:2px style D fill:#e2f0fc,stroke:#377dbf,stroke-width:2px style C fill:#fff0cc,stroke:#e69e00,stroke-width:2px style A fill:#e6ffe6,stroke:#42b983,stroke-width:2px style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px

9.4.17.3 نمونه کد #

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7// bridge بین input و output قرار می‌گیرد و داده‌ها را منتقل می‌کند.
 8func bridge(input <-chan int, output chan<- int) {
 9	for val := range input {
10		output <- val
11	}
12	close(output)
13}
14
15func main() {
16	input := make(chan int)
17	output := make(chan int)
18
19	// اجرای پل انتقال داده در یک goroutine جدا
20	go bridge(input, output)
21
22	// ارسال چند داده به کانال input
23	go func() {
24		for i := 1; i <= 3; i++ {
25			input <- i
26		}
27		close(input)
28	}()
29
30	// دریافت داده‌ها از کانال output
31	for val := range output {
32		fmt.Println("Received:", val)
33	}
34}
1$ go run main.go
2Received: 1
3Received: 2
4Received: 3

در این مثال، ما پیاده‌سازی بهبودیافته‌ای از الگوی Bridge Channel در زبان Go را مشاهده می‌کنیم؛ الگویی که هدف آن اتصال دو کانال (input و output) از طریق یک goroutine واسط (bridge) است. این واسط به صورت شفاف داده‌ها را از یک کانال می‌خواند و به کانال دیگری منتقل می‌کند، به‌گونه‌ای که بخش‌های تولید (Producer) و مصرف (Consumer) بتوانند بدون وابستگی مستقیم به یکدیگر کار کنند.

در ابتدای برنامه، دو کانال input و output تعریف می‌شوند. سپس تابعی به نام bridge ایجاد شده که به عنوان واسطه عمل می‌کند. این تابع در یک goroutine اجرا شده و با استفاده از یک حلقه for val := range input، تا زمانی که کانال input باز است، مقادیر را دریافت می‌کند و آن‌ها را بلافاصله در کانال output می‌نویسد. پس از بسته شدن input، تابع bridge نیز با بستن output خاتمه می‌یابد؛ این یک الگوی بسیار ایمن و idiomatic در Go برای جلوگیری از goroutine leak است.

در بخش main، یک goroutine دیگر وظیفه ارسال داده به input را بر عهده دارد. در اینجا، مقادیر ۱ تا ۳ به input ارسال شده و سپس کانال بسته می‌شود. در انتها، از یک حلقه for val := range output استفاده شده تا داده‌های منتقل‌شده به output دریافت و چاپ شوند. این ساختار به گونه‌ای طراحی شده که پس از اتمام پردازش، برنامه به صورت تمیز و بدون بلاک شدن به پایان می‌رسد.

این مدل نه‌تنها پایه‌ای برای سیستم‌های streaming و pipeline است، بلکه برای ساختاردهی بهتر به معماری‌های همزمان، decoupling اجزا، و افزایش انعطاف‌پذیری و توسعه‌پذیری کد بسیار مناسب است.

9.4.18.4 کاربردها #

  • انتقال داده بین مراحل مختلف در خط لوله‌های داده
  • اتصال میان دو زیرسیستم که از لحاظ طراحی جدا شده‌اند
  • بافر کردن بین تولیدکننده سریع و مصرف‌کننده کند (یا بالعکس)
  • تغییر مسیر جریان داده‌ها (مثلاً برای logging یا debug)
  • کنترل جریان بین سرویس‌های مختلف در معماری microservice