суббота, 26 января 2019 г.

Эффективный Go: интерфейсы и методы

Поскольку почти все может иметь прикрепленные методы, почти все может удовлетворить интерфейс. Один иллюстративный пример находится в http пакете, который определяет интерфейс Handler. Любой объект, который реализует Handler, может обслуживать HTTP-запросы.

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

ResponseWriter сам по себе является интерфейсом, обеспечивающим доступ к методам, необходимым для возврата ответа клиенту. Эти методы включают стандартный метод Write, поэтому http.ResponseWriter можно использовать везде, где io.Writer может быть использован. Request - это структура, содержащая проанализированное представление запроса от клиента.

Для краткости давайте проигнорируем POST и предположим, что HTTP-запросы всегда GET; это упрощение не влияет на то, как обработчики настраиваются. Вот тривиальная, но полная реализация обработчика для подсчета, сколько раз страница посещена.

// Простой счетчик сервера.
type Counter struct {
    n int
}

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, 
                              req *http.Request) {
    ctr.n++
    fmt.Fprintf(w, "counter = %d\n", ctr.n)
}

(Продолжая тему печати, обратите внимание, как Fprintf может печатать на http.ResponseWriter.) Для справки, вот как подключить такой сервер к узлу в дереве URL.

import "net/http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)

Но зачем делать Counter структурой? Целое число - это все, что нужно. (Получатель должен быть указателем, чтобы приращение было видно вызывающей стороне.)

// Упрощенный счетчик сервера.
type Counter int

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, 
                              req *http.Request) {
    *ctr++
    fmt.Fprintf(w, "counter = %d\n", *ctr)
}

Что делать, если ваша программа имеет некоторое внутреннее состояние, которое необходимо уведомить о том, что страница была посещена? Свяжите канал с веб-страницей.

// Канал, который отправляет оповещение на каждый визит.
// (Вероятно вы звхотите, чтобы канал был буферизированным)
type Chan chan *http.Request

func (ch Chan) ServeHTTP(w http.ResponseWriter, 
                         req *http.Request) {
    ch <- req
    fmt.Fprint(w, "notification sent")
}

Наконец, скажем, мы хотели представить в /args аргументы используемые при вызове бинарного файла сервера. Легко написать функцию для вывода аргументов.

func ArgServer() {
    fmt.Println(os.Args)
}

Как мы превращаем это в HTTP-сервер? Мы могли бы сделать ArgServer методом некоторого типа, значение которого мы игнорируем, но есть более чистый способ. Поскольку мы можем определить метод для любого типа, кроме указателей и интерфейсов, мы можем написать метод для функции. Пакет http содержит этот код:

// Тип HandlerFunc - это адаптер, позволяющий использовать
// обычные функции как обработчики HTTP. 
// Если f является функцией
// с соответствующей подписью, HandlerFunc(f) является
// Handler объекта, который вызывает f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP вызывает f(w, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, 
                               req *Request) {
    f(w, req)
}

HandlerFunc - это тип с методом, ServeHTTP, поэтому значения этого типа могут обслуживать HTTP-запросы. Посмотрите на реализацию метода: получатель - это функция, f, и метод вызывает f. Это может показаться странным, но это не так уж отличается от, скажем, случая когда приемник является каналом, а метод отправляется по каналу.

Чтобы превратить ArgServer в HTTP-сервер, мы сначала изменим его, чтобы он имел правильную сигнатуру.

// Argument сервер.
func ArgServer(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, os.Args)
}

ArgServer теперь имеет ту же сигнатуру, что и HandlerFunc, поэтому он может быть преобразован в этот тип для доступа к его методам, как мы конвертировали Sequence в IntSlice для доступа к IntSlice.Sort. Код для установки сервера лаконичен:

http.Handle("/args", http.HandlerFunc(ArgServer))

Когда кто-то посещает страницу /args, обработчик, установленный на этой странице, имеет значение ArgServer и тип HandlerFunc. HTTP-сервер будет вызывать метод ServeHTTP этого типа, с ArgServer в качестве получателя, который в свою очередь вызовет ArgServer(с помощью вызова f(w, req) внутри HandlerFunc.ServeHTTP). Аргументы будут отображены.

В этом разделе мы сделали HTTP-сервер из структуры, целого числа, канала и функции, все потому, что интерфейсы - это просто наборы методов, которые могут быть определены для (почти) любого типа.


Читайте также:


Комментариев нет:

Отправить комментарий