В этой статье мы рассмотрим еще несколько возможностей Go через объектив Ruby. Если вы пропустили это, вы можете найти часть I здесь , хотя вам не нужно читать ее, чтобы понять эту статью.
Мы рассмотрим ответ Go на традиционные стратегии ООП (например, наследование). Go существенно устраняет традиционное наследование, обеспечивая более плавную структуру объекта. Затем мы рассмотрим веб-инфраструктуру Go, которая называется Web.go.
Давай прыгнем.
Ошибка наследования
Особенно в нетривиальных проектах наследование является довольно распространенной практикой. Но наследование может усложнить реализацию даже простых изменений (которые, как мы все знаем, обязательно произойдут), а также создать необходимость дублирования кода.
Например, если у вас есть класс с именем «Horse» и два подкласса «GallopingHorse» и «SadHorse» (возможно, эти два имеют совершенно разные характеристики; не только изменение состояния). Что делать, если у вас есть лошадь, которая грустит и скачет? Это будет означать, что некоторые из этих действий заблокированы в каждом из этих классов.
Есть и другие проблемы с наследованием. Это по своей сути делает ваш код жестким, поскольку включает в себя отношения между типами. Как отмечает часто задаваемые вопросы Голанга:
«Объектно-ориентированное программирование, по крайней мере, на самых известных языках, включает в себя слишком много обсуждений отношений между типами, отношений, которые часто могут быть получены автоматически. Go использует другой подход ».
Итак, что же делает Go?
Он использует интерфейсы. В Ruby нет концепции интерфейсов, но вы можете заставить их работать . По сути, интерфейс похож на контракт. Если класс с именем «Horse» «реализует» интерфейс с именем «Animal», интерфейс Animal говорит, что он должен иметь методы «eat ()» и «sleep ()». В чем смысл, спросите вы? Что ж, давайте разберемся с псевдо-Ruby:
#this is Pseudo-Ruby. We're pretending that Animal is an "interface" #and Horse is a class. def full_day(animal) animal.eat() animal.sleep() end
Прежде всего, мы делаем вид, что на данный момент Ruby откажется запускать этот код, если аргумент метода «full_day» не реализует интерфейс «Animal». Таким образом, у нас может быть метод, который ожидает только то, что удовлетворяет интерфейсу «Animal»; это не особенно заботит, является ли это «Лошадь», «Утка» или «Человек». Поскольку настоящий Ruby является динамически типизированным языком, интерфейсы применимы по-разному; код будет компилироваться в байт-код правильно, независимо от того, есть ли у «animal» методы «есть», «спать». Однако в Go, статически типизированном языке, интерфейс будет проверен во время компиляции.
Достаточно теории. Давайте построим несколько примеров.
животные
Прежде всего, нам нужен какой-то объект. Давайте сделаем себя «лошадью»:
package main type Horse struct { name string } func main() { }
Строка, которая говорит, что type Horse struct
основном говорит: «Я создаю новый тип, назовите его Horse, и это должна быть такая структура ». Структура или «структура» — это, по сути, совокупность структур данных (например, строк, целых, массивов и т. Д.). В Ruby есть очень похожие структуры, за исключением того, что они предоставляют вам методы получения и установки бесплатно. Рассматривая структуру, он отмечает, что структура имеет один фрагмент информации, называемый «именем» Лошади (обратите внимание, что тип данных идет после имени данных в Go).
Теперь давайте определим метод или два на нашей Лошади:
package main import ( "fmt" ) type Horse struct { name string } /* added a couple of methods here */ func (h *Horse) Sleep() { fmt.Println("zzz...") } func (h *Horse) Eat() { fmt.Println("omnomnomnom") } func main() { }
Обратите внимание, как мы определили методы. Они объявляются полностью вне структуры, в отличие от Ruby, где методы объявляются в блоке объявления класса. Кроме того, перед ними (h *Horse)
. Это говорит компилятору, что это методы, которые можно вызывать в классе Horse
а также предоставляет h
, экземпляр Horse, данному методу.
Давайте исправим интерфейс Animal:
package main import ( "fmt" ) /* added an interface */ type Animal interface { Eat() Sleep() } type Horse struct { name string } func (h *Horse) Sleep() { fmt.Println("zzz...") } func (h *Horse) Eat() { fmt.Println("omnomnomnom") } func main() { }
Это всего лишь пара строк кода, но он описывает поведение всех Animal
: они едят и спят.
Интерфейсы в Go явно не реализованы. Если вы писали какой-либо код Java раньше, вы, вероятно, видели что-то вроде этого:
class Horse implements Animal
В Go, пока необходимые методы были написаны, интерфейс реализуется автоматически. Итак, наш объект Horse
уже удовлетворяет интерфейсу Animal
! Прежде чем идти дальше, давайте создадим экземпляр этой Лошади по имени George
:
package main import ( "fmt" ) type Animal interface { Eat() Sleep() } type Horse struct { //changed the name of the field from //"name" to Name //fields that begin with an uppercased letter //are public fields. Name string } func (h *Horse) Sleep() { fmt.Println("zzz...") } func (h *Horse) Eat() { fmt.Println("omnomnomnom") } func main() { /* create an instance */ george := new(Horse) george.Name = "George" george.Sleep() george.Eat() }
Здесь есть пара вещей, на которые стоит обратить внимание. Прежде всего, я изменил поле с именем name
на Name
. Поля, начинающиеся с заглавной буквы, являются открытыми. Рассматривая main
метод, я использовал new
встроенную функцию для создания ссылки на Horse
. Наконец, мы называем его и вызываем методы Eat
и Sleep
. Давайте сделаем это немного интереснее; как насчет метода, который должен работать для всех животных:
func FullDay(a Animal) { a.Eat() a.Sleep() }
Если мы передаем Джорджу FullDay(george)
, мы должны получить тот же вывод, что и раньше. Из всего этого мы можем увидеть, как работают интерфейсы в Go. Вы определяете структуру данных и присоединяете к ней некоторые методы, и интерфейсы, которые она заполняет, будут определены автоматически. Это служит для того, чтобы сделать вашу кодовую базу более гибкой и легко изменяемой при необходимости.
Web.go
Теперь, когда мы хорошо понимаем, как работает абстракция в Go, мы можем взглянуть на Web.go, потрясающую веб-среду для Go.
Прежде всего, вам нужно иметь работающую среду Go, чтобы запустить их в вашей системе. Для этого я бы посоветовал вам клонировать репозиторий примеров . Затем, попав в каталог «webgo-examples /», вы можете запустить его в своей оболочке:
export GOPATH=`pwd` cd src go get github.com/hoisie/web
Это должно дать вам копию фреймворка Web.go. Если вы откроете файл «main / hello-world.go», вы должны увидеть это:
package main import ( "github.com/hoisie/web" ) func hello(val string) string { return "hello " + val } func main() { web.Get("/(.*)", hello) web.Run("0.0.0.0:9999") }
Это простейший вариант использования Web.go; крошечное «привет» приложение. Как видите, Web.go очень похож на Sinatra Руби; один файл с маршрутами и соответствующими методами. Однако маршруты в Web.go указываются с помощью регулярных выражений. В качестве личного предпочтения я нахожу способ Синатры делать вещи намного более прямыми и менее подверженными ошибкам, но есть много людей, которые предпочитают регулярные выражения для их силы.
Если вы собираете и запускаете «hello-world.go» с помощью «go build hello-world.go; ./hello_world ”, вы должны увидеть работающий сервер. Переход к «localhost: 9999» должен вернуть сообщение «hello». Мы можем написать этот же пример немного по-другому (заимствовано из документации по Web.go):
package main import ( "github.com/hoisie/web" ) func hello(ctx *web.Context, val string) { ctx.WriteString("hello " + val) } func main() { web.Get("/(.*)", hello) web.Run("0.0.0.0:9999") }
Обратите внимание, что параметры метода hello
изменились; теперь у нас есть метод контекста, переданный методу. Этот контекстный объект довольно полезен:
package main import ( "github.com/hoisie/web" "fmt" ) func logName(ctx *web.Context, val string) { name := ctx.Params["name"] fmt.Println(name) ctx.WriteString("Hi, " + name) } func main() { web.Get("/log_name?(.*)", logName) web.Run("0.0.0.0:9999") }
Как видите, мы подключили маршрут /log_name
который читает параметр с именем name
, отвечает на запрос и распечатывает имя. Если вы /log_name?name=Whateveryournameis
к /log_name?name=Whateveryournameis
, вы должны получить дружеское приветствие.
Web.go имеет хорошую поддержку статических файлов. Если вы добавите папку с именем static
в каталог веб-приложения, Web.go будет автоматически обслуживать запросы на соответствующие имена файлов (например, «/css/index.css» соответствует «static / css / index.css»).
Есть также довольно крутой набор инструментов, который позволяет нам создавать потоковые ответы. Пример включен в документацию Web.go, но не имеет большого объяснения:
package main import ( "github.com/hoisie/web" "net/http" "strconv" "time" ) func hello(ctx *web.Context, num string) { flusher, _ := ctx.ResponseWriter.(http.Flusher) flusher.Flush() n, _ := strconv.ParseInt(num, 10, 64) for i := int64(0); i < n; i++ { ctx.WriteString("<br>hello world</br>") flusher.Flush() time.Sleep(1e9) } } func main() { web.Get("/([0-9]+)", hello) web.Run("0.0.0.0:9999") }
Если вы запустите это и перейдете к «/ 10», вы получите ответ, который продолжает добавлять «привет мир» на страницу. Как это работает? По сути, вместо отправки полного ответа клиенту (т.е. веб-браузеру) сервер отправляет ему его части. Если вы посмотрите на код, волшебство происходит с объектом «flusher». Мы используем его для того, чтобы «сбрасывать» некоторые из наших данных клиенту, затем мы немного time.Sleep
(со time.Sleep
) и отправляем еще один сброс и так далее. Итак, мы используем flusher
чтобы пошатнуть ответ и разослать его по частям. Конечно, это означает, что серверу может потребоваться много секунд на запрос, но в некоторых (по общему признанию, их мало и они далеко друг от друга; в наши дни WebSockets и Pusher могут сделать подобные вещи), это может быть довольно полезный трюк!
Хотя Web.go довольно фантастический, я не думаю, что он соответствует простоте Синатры; последний чувствует себя быстрее и шустрее (как и Ruby в целом). Я думаю, что большая его часть может быть отнесена к циклу «запись-компиляция-запуск» с Go, которого нет в интерпретируемых языках. Тем не менее, даже с точки зрения самого кода, Go кажется более логичным, чем Ruby и его кузены.
Web.go демонстрирует универсальность Go. Он может использоваться как системный язык (в настоящее время я использую его для работы с распределенными файловыми системами), а также достаточно хорошо для веб-разработки, не говоря уже о повышении производительности.
Вывод
Go это довольно классный язык. По моему мнению, ему удается сохранить некоторые из лучших возможностей динамических языков, оставаясь верным своему системному фону.
В настоящее время я работаю над Go-проектом, связанным с Hadoop. Могу сказать, что, не задумываясь, мой опыт общения с Go был фантастическим в этом отношении. Что касается веб-разработки, мне еще предстоит развернуть крупное приложение с помощью Go, но общее ощущение было приятным. Как я уже упоминал, Web.go не такой быстрый, как Sinatra, но некоторые функции, которые он предлагает, выделяют его в моем наборе инструментов.