суббота, 27 июля 2019 г.

ООП в Golang: public vs. private (публичный против частного)

Пакет - это самая маленькая единица частной инкапсуляции в Go.

  • Все идентификаторы, определенные в пакете, видны во всем этом пакете.
  • При импорте пакета вы можете получить доступ только к его экспортированным идентификаторам.
  • Идентификатор экспортируется, если он начинается с заглавной буквы.

Экспортируемые и неэкспортированные идентификаторы используются для описания открытого интерфейса пакета и для защиты от определенных ошибок программирования.

Предупреждение. Неэкспортированные идентификаторы не являются мерой безопасности и не скрывают и не защищают какую-либо информацию.

Пример. В следующем пакете единственными экспортируемыми идентификаторами являются StopWatch и Start.

package timer

import "time"

// StopWatch - это простая утилита часов.
// Его нулевое значение - это бездействующие часы с общим временем 0.
type StopWatch struct {
    start   time.Time
    total   time.Duration
    running bool
}

// Start включает часы.
func (s *StopWatch) Start() {
    if !s.running {
        s.start = time.Now()
        s.running = true
    }
}

StopWatch и его экспортируемые методы могут быть импортированы и использованы в другом пакете.

package main

import "timer"

func main() {
    clock := new(timer.StopWatch)
    clock.Start()
    if clock.running { // Недопустимо
        // …
    }
}

../main.go:8:15: clock.running undefined (cannot refer to unexported field or method clock.running)


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


пятница, 26 июля 2019 г.

Пакет Gorilla mux

В этом посте представлена подборка статей о пакете Gorilla mux, используемом для маршрутизации веб-запросов.

  1. Пакет Gorilla mux для обработки веб-запросов
  2. Пакет Gorilla mux: маршруты, субмаршруты
  3. Пакет Gorilla mux: построение зарегистрированных URL
  4. Пакет Gorilla mux: добавление промежуточного программного обеспечения (middleware)

ООП в Golang: методы не для классов, но для типов

В Go нет классов, но вы можете определять методы для типов.

Вы можете определить методы для любого типа, объявленного в определении типа.

Метод - это функция с дополнительным аргументом получателя. Получатель находится между ключевым словом func и именем метода. В следующем примере метод HasGarage связан с типом House. Получатель метода назван p.

type House struct {
    garage bool
}

func (p *House) HasGarage() bool { return p.garage }

func main() {
    house := new(House)
    fmt.Println(house.HasGarage()) // Печатает "false" (нулевое значение)
}

Преобразования и методы

Если вы преобразуете значение в другой тип, новое значение будет иметь методы нового типа, но не старого.

type MyInt int

func (m MyInt) Positive() bool { return m > 0 }

func main() {
    var m MyInt = 2
    m = m * m // Операторы базового типа все еще применяются.

    fmt.Println(m.Positive())        // Печатает "true"
    fmt.Println(MyInt(3).Positive()) // Печатает "true"

    var n int
    n = int(m) // Требуется преобразование.
    n = m      // Недопустимо
}

../main.go:14:4: cannot use m (type MyInt) as type int in assignment

В Go идиоматично преобразовывать тип выражения для доступа к определенному методу.

var n int64 = 12345
fmt.Println(n)                // 12345
fmt.Println(time.Duration(n)) // 12.345µs

(Основной тип time.Duration - int64, а тип time.Duration имеет метод String, который возвращает продолжительность, отформатированную как время.)


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


четверг, 25 июля 2019 г.

Объектно-ориентированное программирование с наследованием в Golang

Наследование в традиционных объектно-ориентированных языках предлагает три функции в одном. Когда Cat наследуется от Animal:

  • класс Cat повторно использует код из класса Animal,
  • переменная x типа Animal может ссылаться либо к Cat, либо к Animal,
  • x.Eat() выберет метод Eat в зависимости от того, к какому типу относится объект x.

В объектно-ориентированном жаргоне эти функции известны как повторное использование кода (code reuse), полиморфизм и динамическая диспетчеризация (dynamic dispatch).

Все они доступны в Go, используя отдельные конструкции:

  • композиция (composition) и встраивание (embedding) обеспечивают повторное использование кода,
  • интерфейсы заботятся о полиморфизме и динамической диспетчеризации.

Повторное использование кода посредством композиции (composition)

Не беспокойтесь об иерархиях типов при запуске нового проекта Go - позже легко внедрить полиморфизм и динамическую диспетчеризацию.

Если Cat требуются некоторые или все функциональные возможности Animal, просто используйте композицию (composition).

type Animal struct {
    // …
}

type Cat struct {
    fluffy Animal
    // …
}

Это дает вам полную свободу использования Animal части вашей Cat по мере необходимости.

Повторное использование кода посредством встраивания (embedding)

Если класс Cat наследует точное поведение Animal, такой подход может привести к утомительному написанию повторяющегося кода.

type Animal struct {
    // …
}

func (a *Animal) Eat()   { … }
func (a *Animal) Sleep() { … }
func (a *Animal) Walk() { … }

type Cat struct {
    fluffy Animal
    // …
}

func (a *Cat) Eat()   { a.fluffy.Eat() }
func (a *Cat) Sleep() { a.fluffy.Sleep() }
func (a *Cat) Walk()  { a.fluffy.Walk() }

Этот шаблон написания кода известен как делегирование.

Go использует встраивание для подобных ситуаций. Объявление структуры Cat и ее трех методов можно сократить до:

type Cat struct {
    Animal
    // …
}

Полиморфизм и динамическая диспетчеризация с интерфейсами

Держите свои интерфейсы короткими и вводите их только при необходимости.

В дальнейшем ваш проект мог бы включать больше животных. На этом этапе вы можете ввести полиморфизм и динамическую диспетчеризацию с использованием интерфейсов.

Если вам нужно усыпить всех своих питомцев, вы можете определить интерфейс Sleeper.

type Sleeper interface {
    Sleep()
}

func main() {
    pets := []Sleeper{new(Cat), new(Dog)}
    for _, x := range pets {
        x.Sleep()
    }
}

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


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


Пакет Gorilla mux: добавление промежуточного программного обеспечения (middleware)

Mux поддерживает добавление промежуточного программного обеспечения (middleware) к маршрутизатору (Router), который выполняется в порядке их добавления, если найдено совпадение, включая его подчиненные маршрутизаторы. Промежуточное программное обеспечение - это (как правило) небольшие куски кода, которые принимают один запрос, что-то с ним делают и передают другому промежуточному программному обеспечению или конечному обработчику. Некоторыми распространенными вариантами использования промежуточного программного обеспечения являются ведение журнала запросов, манипулирование заголовками или перехват ResponseWriter.

type MiddlewareFunc func(http.Handler) http.Handler

Как правило, возвращаемый обработчик является замыканием, которое делает что-то с переданными ему http.ResponseWriter и http.Request, а затем вызывает обработчик, переданный в качестве параметра в MiddlewareFunc (замыкания могут обращаться к переменным из контекста, в котором они созданы).

Простое базовое промежуточное программное обеспечение, которое регистрирует URI обрабатываемого запроса, может быть записано как:

func simpleMw(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Делаем работу здесь
        log.Println(r.RequestURI)
        // Вызов следующего обработчика, 
        // который может быть другим промежуточным программным 
        // обеспечением в цепочке или конечным обработчиком.
        next.ServeHTTP(w, r)
    })
}

Промежуточное программное обеспечение (middleware) может быть добавлено к маршрутизатору с помощью Router.Use():

r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(simpleMw)

Более сложное промежуточное программное обеспечение для аутентификации, которое сопоставляет токен сессии с пользователями, может быть записано как:

// Определим нашу структуру
type authenticationMiddleware struct {
    tokenUsers map[string]string
}

// Инициализируем где-нибудь
func (amw *authenticationMiddleware) Populate() {
    amw.tokenUsers["00000000"] = "user0"
    amw.tokenUsers["aaaaaaaa"] = "userA"
    amw.tokenUsers["05f717e5"] = "randomUser"
    amw.tokenUsers["deadbeef"] = "user0"
}

// Функция промежуточного программного обеспечения, 
// которая будет вызываться для каждого запроса
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Session-Token")

        if user, found := amw.tokenUsers[token]; found {
            // Мы нашли токен в нашей карте
            log.Printf("Authenticated user %s\n", user)
            next.ServeHTTP(w, r)
        } else {
            http.Error(w, "Forbidden", http.StatusForbidden)
        }
    })
}

r := mux.NewRouter()
r.HandleFunc("/", handler)

amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
amw.Populate()

r.Use(amw.Middleware)

Примечание. Цепочка обработчиков будет остановлена, если ваше промежуточное программное обеспечение не вызовет next.ServeHTTP() с соответствующими параметрами. Это можно использовать для отмены запроса, если этого хочет автор промежуточного программного обеспечения.

Пример использования middleware с Gorilla mux:

package mux_test

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

// Определим нашу структуру
type authenticationMiddleware struct {
    tokenUsers map[string]string
}
// Инициализируем где-нибудь
func (amw *authenticationMiddleware) Populate() {
    amw.tokenUsers["00000000"] = "user0"
    amw.tokenUsers["aaaaaaaa"] = "userA"
    amw.tokenUsers["05f717e5"] = "randomUser"
    amw.tokenUsers["deadbeef"] = "user0"
}

// Функция промежуточного программного обеспечения, которая будет вызываться для каждого запроса
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Session-Token")

        if user, found := amw.tokenUsers[token]; found {
            // Мы нашли токен в нашей карте
            log.Printf("Authenticated user %s\n", user)
            next.ServeHTTP(w, r)
        } else {
            http.Error(w, "Forbidden", http.StatusForbidden)
        }
    })
}

func Example_authenticationMiddleware() {
    r := mux.NewRouter()
    r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // Делаем что-то здесь
    })
    amw := authenticationMiddleware{make(map[string]string)}
    amw.Populate()
    r.Use(amw.Middleware)
}


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


Пакет Gorilla mux: построение зарегистрированных URL

В этом посте рассмотрим, как построить зарегистрированные URL с пакетом Gorilla mux.

Маршруты могут быть названы. Все маршруты, которые определяют имя, могут иметь свои построенные URL-адреса. Мы определяем имя, вызывая Name() на маршруте. Например:

r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
  Name("article")

Чтобы построить URL, получите маршрут и вызовите метод URL(), передав последовательность пар ключ/значение для переменных маршрута. Для предыдущего маршрута мы бы сделали:

url, err := r.Get("article").URL("category", "technology", "id", "42")

... и результатом будет url.URL со следующим путем:

"/articles/technology/42"

Это также работает для переменных значения хоста и запроса:

r := mux.NewRouter()
r.Host("{subdomain}.domain.com").
  Path("/articles/{category}/{id:[0-9]+}").
  Queries("filter", "{filter}").
  HandlerFunc(ArticleHandler).
  Name("article")

// url.String() будет "http://news.domain.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42",
                                 "filter", "gorilla")

Все переменные, определенные в маршруте, являются обязательными, и их значения должны соответствовать соответствующим шаблонам. Эти требования гарантируют, что сгенерированный URL всегда будет соответствовать зарегистрированному маршруту - единственное исключение - явно определенные маршруты "только для сборки" ("build-only"), которые никогда не совпадают.

Также существует поддержка регулярных выражений для сопоставления заголовков в маршруте. Например, мы могли бы сделать:

r.HeadersRegexp("Content-Type", "application/(text|json)")

... и маршрут будет соответствовать обоим запросам с Content-Type `application/json`, а также `application/text`

Существует также способ построения только хоста или пути URL для маршрута: для этого используйте методы URLHost() или URLPath(). Для предыдущего маршрута мы бы сделали:

// "http://news.domain.com/"
host, err := r.Get("article").URLHost("subdomain", "news")

// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")

И если вы используете подчиненные маршрутизаторы (subrouters), хост и путь, определенные отдельно, также могут быть построены:

r := mux.NewRouter()
s := r.Host("{subdomain}.domain.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
  HandlerFunc(ArticleHandler).
  Name("article")

// "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
                                 "category", "technology",
                                 "id", "42")


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


вторник, 23 июля 2019 г.

Пакет Gorilla mux: маршруты, субмаршруты

Маршруты также могут быть ограничены доменом или поддоменом. Просто определите шаблон хоста для сопоставления. Они также могут иметь переменные:

r := mux.NewRouter()
// Соответствует только если домен "www.example.com".
r.Host("www.example.com")
// Соответствует динамическому поддомену.
r.Host("{subdomain:[a-z]+}.domain.com")

Есть несколько других совпадений, которые можно добавить. Чтобы соответствовать префиксам пути:

r.PathPrefix("/products/")

... или методам HTTP:

r.Methods("GET", "POST")

... или схеме URL:

r.Schemes("https")

... или значению заголовка:

r.Headers("X-Requested-With", "XMLHttpRequest")

... или значению запроса:

r.Queries("key", "value")

... или использовать пользовательскую функцию соответствия:

r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
    return r.ProtoMajor == 0
})

... и, наконец, можно объединить несколько сопоставителей в одном маршруте:

r.HandleFunc("/products", ProductsHandler).
  Host("www.example.com").
  Methods("GET").
  Schemes("http")

Задавать одни и те же условия сопоставления снова и снова может быть скучно, поэтому есть способ сгруппировать несколько маршрутов с одинаковыми требованиями. Он называется "субмаршрутизацией" (subrouting).

Например, допустим, есть несколько URL-адресов, которые должны совпадать только в том случае, если хостом является "www.example.com". Создайте маршрут для этого хоста и получите от него "субмаршрутизатор" ("subrouter"):

r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()

Затем зарегистрируйте маршруты в субмаршрутизаторе(subrouter):

s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)

Три URL-пути, которые мы зарегистрировали выше, будут проверяться только в том случае, если домен является "www.example.com", поскольку сначала проверяется подчиненный маршрутизатор (subrouter). Это не только удобно, но и оптимизирует сопоставление запросов. Вы можете создавать субмаршрутизаторы (subrouters), комбинирующие любые сопоставления атрибутов, принятые маршрутом (route).

Субмаршрутизаторы (Subrouters) могут использоваться для создания "пространств имен" ("namespaces") домена или пути: вы определяете субмаршрутизаторы в одном месте, а затем части приложения могут регистрировать свои пути относительно данного субмаршрутизатора.

Есть еще одна вещь о субмаршрутах (subroutes). Когда у подчиненного маршрутизатора (subrouter) есть префикс пути, внутренние маршруты используют его в качестве основы для своих путей:

r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()

// "/products/"
s.HandleFunc("/", ProductsHandler)

// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)

// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)

Обратите внимание, что путь к PathPrefix() представляет собой "подстановочный знак" ("wildcard"): вызов PathPrefix("/static/").Handler(...) означает, что обработчику будет передан любой запрос, соответствующий "/static/*". Это позволяет легко обслуживать статические файлы с помощью mux:

func main() {
    var dir string

    flag.StringVar(&dir, "dir", ".", "каталог для обслуживания файлов. По умолчанию текущий каталог")
    flag.Parse()
    r := mux.NewRouter()

    // Это будет обслуживать файлы по адресу 
    // http://localhost:8000/static/
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))

    srv := &http.Server{
        Handler:      r,
        Addr:         "127.0.0.1:8000",
        // Хорошая практика: 
        // применять таймауты для серверов, 
        // которые вы создаете!
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Fatal(srv.ListenAndServe())
}


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