Menu
Coddy logo textTech

Zasady spełniania interfejsów

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 43 z 107.

Chociaż Go używa typowania kaczego do niejawnego spełniania interfejsów, istnieją konkretne zasady określające, czy dany typ rzeczywiście spełnia interfejs. Zrozumienie tych zasad pomaga uniknąć subtelnych błędów.

Najważniejsza zasada dotyczy odbiorców wskaźnikowych. Jeśli metoda jest zdefiniowana z odbiorcą wskaźnikowym, interfejs spełnia tylko wskaźnik do tego typu, a nie sama wartość:

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // odbiornik wskaźnikowy
    return "Saved: " + d.Name
}

func Process(s Saver) {
    fmt.Println(s.Save())
}

Ponieważ Save() ma odbiorcę wskaźnikowego, tylko *Document spełnia interfejs Saver:

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // działa - wskaźnik spełnia wymagania interfejsu
    // Process(doc)  // błąd kompilacji - wartość nie spełnia wymagań interfejsu
}

Jednak sytuacja odwrotna jest bardziej elastyczna. Jeśli metoda ma odbiornik wartości, interfejs spełniają zarówno typ wartości, jak i typ wskaźnika. Go automatycznie dereferencjonuje wskaźniki podczas wywoływania metod z odbiornikiem wartości:

func (d Document) Info() string {  // odbiornik przez wartość
    return d.Name
}

// Zarówno Document, jak i *Document spełniają wymagania interfejsu wymagającego Info()

Ta asymetria istnieje, ponieważ Go zawsze może uzyskać wartość ze wskaźnika (przez dereferencję), ale nie zawsze może uzyskać wskaźnik z wartości (wartość może nie mieć adresu). Pamiętanie o tej zasadzie zapobiega mylącym błędom kompilatora podczas pracy z interfejsami.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system konfiguracji, który pokaże, jak odbiorniki wskaźnikowe i wartościowe wpływają na spełnianie interfejsu. Utworzysz typy, w przypadku których wybór odbiornika określa, czy z interfejsem można używać wartości, wskaźników, czy obu tych rodzajów.

Rozmieścisz kod w trzech plikach:

  • config.go: Zdefiniuj interfejs Configurable, który wymaga dwóch metod: GetValue() string i SetValue(string). Następnie utwórz dwa typy konfiguracji:
    • ReadOnlyConfig z polem Value: użyj odbiornika wartościowego dla GetValue() (zwraca Value) oraz odbiornika wskaźnikowego dla SetValue() (aktualizuje Value)
    • Setting z polem Data: użyj odbiorników wartościowych dla obu metod (GetValue zwraca Data, a SetValue tylko wyświetla "Cannot modify", nie wprowadzając żadnych zmian)
  • processor.go: Utwórz funkcję o nazwie ProcessConfig, która przyjmuje Configurable i nową wartość typu string. Powinna wyświetlić bieżącą wartość za pomocą GetValue(), wywołać SetValue() z nową wartością, a następnie ponownie wyświetlić wartość, aby pokazać ewentualne zmiany.
  • main.go: Odczytaj szczegóły konfiguracji z wejścia i pokaż zasady spełniania interfejsu:
    1. Utwórz ReadOnlyConfig i przekaż do ProcessConfig wskaźnik (jest to wymagane, ponieważ SetValue ma odbiornik wskaźnikowy)
    2. Utwórz Setting i przekaż do ProcessConfig bezpośrednio wartość (działa, ponieważ obie metody mają odbiorniki wartościowe)

Podane zostaną następujące dane wejściowe:

  • Wiersz 1: Wartość początkowa dla ReadOnlyConfig
  • Wiersz 2: Nowa wartość do ustawienia dla ReadOnlyConfig
  • Wiersz 3: Wartość początkowa dla Setting
  • Wiersz 4: Nowa wartość, którą należy spróbować ustawić dla Setting

Funkcja ProcessConfig powinna wyświetlać dane w następującym formacie:

Current: [value]
Current: [value after SetValue]

Na przykład dla danych debug, production, localhost i remote wynik powinien wyglądać następująco:

Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost

Zwróć uwagę, że wartość ReadOnlyConfig rzeczywiście się zmienia (ponieważ przekazaliśmy wskaźnik), natomiast Setting pozostaje bez zmian (jego SetValue z odbiornikiem wartościowym nie może zmodyfikować oryginału). Najważniejszy wniosek jest taki, że same wartości ReadOnlyConfig nie spełniają wymagań interfejsu Configurable — spełniają je tylko wskaźniki, podczas gdy wartości Setting można przekazywać bezpośrednio, ponieważ wszystkie jego metody używają odbiorników wartościowych.

Spróbuj swoich sił

package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Odczytaj dane wejściowe
	scanner.Scan()
	rocInitial := scanner.Text()
	scanner.Scan()
	rocNew := scanner.Text()
	scanner.Scan()
	settingInitial := scanner.Text()
	scanner.Scan()
	settingNew := scanner.Text()
	
	// TODO: Utwórz ReadOnlyConfig z wartością rocInitial
	// TODO: Przekaż WSKAŹNIK do ProcessConfig (jest to wymagane, ponieważ SetValue ma odbiornik wskaźnikowy)
	
	// TODO: Utwórz Setting z wartością settingInitial
	// TODO: Przekaż WARTOŚĆ bezpośrednio do ProcessConfig (to działa, ponieważ obie metody mają odbiorniki wartościowe)
	
	// Użyj tych zmiennych, aby uniknąć błędów związanych z nieużywanymi zmiennymi
	_ = rocInitial
	_ = rocNew
	_ = settingInitial
	_ = settingNew
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online