Menu
Coddy logo textTech

io.Reader ו-io.Writer

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 73 מתוך 107.

הספרייה הסטנדרטית של Go מציגה עיצוב מבוסס ממשקים באמצעות שני ממשקים בסיסיים: io.Reader ו־io.Writer. הממשקים הפשוטים האלה עומדים בבסיס כל דבר, מפעולות קבצים ועד תקשורת רשת.

הממשק io.Reader דורש רק מתודה אחת:

type Reader interface {
    Read(p []byte) (n int, err error)
}

הוא קורא עד len(p) בתים לתוך הפרוסה ומחזיר כמה בתים נקראו. באופן דומה, io.Writer מגדיר:

type Writer interface {
    Write(p []byte) (n int, err error)
}

כל טיפוס שמממש את המתודות האלה עובד עם כל מערכת הקלט/פלט.

הנה טיפוס מותאם אישית שמממש את io.Reader:

type RepeatReader struct {
    Char  byte
    Count int
    pos   int
}

func (r *RepeatReader) Read(p []byte) (int, error) {
    if r.pos >= r.Count {
        return 0, io.EOF
    }
    n := 0
    for n < len(p) && r.pos < r.Count {
        p[n] = r.Char
        n++
        r.pos++
    }
    return n, nil
}

כעת הקורא המותאם אישית הזה עובד עם כל פונקציה שמצפה ל־io.Reader:

reader := &RepeatReader{Char: 'A', Count: 5}
data, _ := io.ReadAll(reader)
fmt.Println(string(data))  // AAAAA

העיצוב הזה מאפשר לך לכתוב פונקציות שמקבלות io.Reader או io.Writer, כך שהן יוכלו לעבוד עם קבצים, חיבורי רשת, מאגרי נתונים או כל מימוש מותאם אישית.

challenge icon

אתגר

קל

בואו נבנה מעבד זרם טקסט מותאם אישית שמממש את הממשקים io.Reader ו־io.Writer! תיצרו טיפוסים שיוכלו לשנות טקסט בזמן שהוא עובר דרכם, וכך תדגימו כיצד הממשקים הבסיסיים האלה מאפשרים להרכיב פעולות קלט/פלט עוצמתיות.

תארגנו את הקוד בשני קבצים:

  • streams.go: הגדירו את טיפוסי הקורא והכותב המותאמים אישית שלכם.

    צרו struct בשם CountingReader שעוטף מחרוזת ועוקב אחר מספר הבתים שנקראו. צריכים להיות בו שדות עבור מחרוזת המקור ועבור מעקב אחר המיקום. ממשו את המתודה Read(p []byte) (int, error), שקוראת בתים מהמקור אל הפרוסה שסופקה, מעדכנת את המיקום ומחזירה io.EOF כשהמקור מסתיים.

    צרו struct בשם UppercaseWriter שאוסף נתונים שנכתבו וממיר אותם לאותיות רישיות. עליו לשמור את התוצאה המצטברת באופן פנימי. ממשו את המתודה Write(p []byte) (int, error), שממירה את הבתים המתקבלים לאותיות רישיות ושומרת אותם. הוסיפו מתודה Result() string כדי לקבל את הטקסט המצטבר באותיות רישיות.

    צרו פונקציות בנאי NewCountingReader(source string) *CountingReader ו־NewUppercaseWriter() *UppercaseWriter כדי לאתחל כראוי את הטיפוסים שלכם.

  • main.go: הדגימו כיצד טיפוסי הקלט/פלט המותאמים אישית שלכם פועלים עם הספרייה הסטנדרטית.

    קראו מחרוזת קלט, ואז השתמשו ב־CountingReader שלכם עם io.ReadAll כדי לקרוא את כל הנתונים. העבירו את הנתונים האלה דרך UppercaseWriter שלכם באמצעות המתודה Write שלו.

    הדפיסו את התוצאות בפורמט הזה:

    Original: [input]
    Uppercase: [result from writer]
    Bytes read: [total bytes]

הקלט הבא יסופק:

  • שורת טקסט אחת

לדוגמה, עבור:

Hello World

הפלט שלכם צריך להיות:

Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11

ועבור:

Go interfaces are powerful

הפלט שלכם צריך להיות:

Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26

התובנה המרכזית היא שה־CountingReader שלכם עובד בצורה חלקה עם io.ReadAll מפני שהוא מממש את io.Reader. כל פונקציה בסביבת Go שמקבלת io.Reader תעבוד עם הטיפוס המותאם אישית שלכם: זה הכוח של תכנון המבוסס על ממשקים.

נסו בעצמכם

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// קרא את הקלט
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// הסר את תו השורה החדשה בסוף, אם קיים
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: צור CountingReader עם מחרוזת הקלט
	// countingReader := NewCountingReader(input)

	// TODO: השתמש ב-io.ReadAll כדי לקרוא את כל הנתונים מה-CountingReader שלך
	// data, err := io.ReadAll(countingReader)

	// TODO: צור UppercaseWriter
	// upperWriter := NewUppercaseWriter()

	// TODO: כתוב את הנתונים ל-UppercaseWriter שלך
	// upperWriter.Write(data)

	// TODO: הדפס את התוצאות בפורמט הנדרש:
	// מקור: [input]
	// אותיות גדולות: [תוצאה מהכותב]
	// בתים שנקראו: [סך הבתים]

	// מציין מקום למניעת שגיאה על ייבוא שאינו בשימוש - יש להסיר בעת המימוש
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין