Go: Interfejsy jako parametry, konkretne typy jako wynik

Przesiadając się z Java na Go, chyba najtrudniej było mi się zaakceptować przysłowie: „Accept Interfaces, Return Concrete Types”.

Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values

Cytat za: https://github.com/golang/go/wiki/CodeReviewComments#interfaces

W Java’ie jest to normalne, że klasa interfejsu musi być znana po obu stronach. Dlatego też to dostarczający definiuje interfejs, a użytkownik go importuje i używa.

Go jest inne. Nie ma składni implements stąd interfejs dostarczającemu nie jest do niczego potrzebny. Może on zwrócić strukturę i już. Nawet prywatną. Użytkownik natomiast powinien oczekiwać interfejsu. Co powinien zrobić, jeżeli takiego interfejsu nie ma? Zdefiniować. Jeżeli tylko struktura będzie go implementować, można będzie użyć metody.

Przykład dostarczającego:

package db

type Store struct {
  db *sql.DB
}

// NewDB initialise the DB
func NewDB() *Store { ... }
// Insert item
func (s *Store) Insert(item User) error { ... }
// Get item by id
func (s *Store) Get(id int) (User, error) { ... }

oraz użytkownik:

package user

type Saver interface {
  Insert(user User) error
}

type Service struct {
  Saver // Accepting interface here!
}

func (s Service) Register(f Form) error {
  // ...
}

a także klej:

package app

import (
  "example.org/user"
  "example.org/db"
)

func Configure() {
  us := user.Service{db.NewDB()}
  // ...
}

Zupełnie płynnie. Jeżeli dostarczający doda jakieś metody do zwracanej struktury, użytkownik nic nie musi robić. Inaczej gdyby zwracał interfejs. Użytkownik będzie musiał zaimplementować nowe metody w testach.

Oczywistym jest też, że nie zawsze można taką regułę stosować. W przypadku gdy mamy kilka różnych implementacji, np. używając wzorca Strategy, będziemy musieli zwrócić interfejs.