Что такое изучение исходного кода? интерфейс, сочетание

Go

Всем привет, меня зовут Се Вэй, я программист.

Сегодняшняя тема: интерфейсно-ориентированное, композиционное программирование.

Как программисты, все надеются написать общий и расширяемый код, обычно полагаясь на эти знания, чтобы направлять разработку, опираясь на шаблоны проектирования. Например, объектно-ориентированные возможности: инкапсуляция, абстракция, полиморфизм, наследование.

Чтобы написать более общий код, с одной стороны, вам нужно потратить достаточно времени на его создание, а с другой стороны, вам нужно попрактиковаться и изучить его самостоятельно. Когда вы кодируете, держите в голове контрольный список:

  • писать читаемый код
  • Пишите код, соответствующий шаблонам проектирования

Как написать более общий код на Go?

Один — интерфейс, а другой — комбинация.

В Go нет концепции наследования, что устраняет недостатки, заключающиеся в том, что «наследование» может привести к слишком большому количеству уровней Вместо этого рекомендуется использовать форму комбинирования для достижения эффекта «наследования».

Возьмем простой пример:


type Languager interface {
	Can(string) string
}

type Someone struct {
	Language string
}

func (s Someone) Can(name string) string {
	return fmt.Sprintf("%s can program with %s", name, s.Language)
}

func Program(L Languager, name string) {
	log.Println(L.Can(name))
}


func main() {
	b := Someone{Language: "go"}
	Program(b, "谢谢")

}
>>2019/12/26 11:10:55 谢谢 can program with go

Определен интерфейс: Languager имеет метод Can, а структура Someone имеет метод Can (параметры и возвращаемые значения одинаковые), мы говорим: Кто-то реализует интерфейс Languager.

Интерфейс представляет собой комбинацию серии «протоколов», описывающих его абстрактные возможности, а конкретная реализация зависит от конкретных методов структуры.

type OtherOne struct {
	Speaker string
}

func (o OtherOne) Can(name string) string {
	return fmt.Sprintf("%s can speake %s", name, o.Speaker)
}

func main(){
	b := Someone{Language: "go"}
	Program(b, "谢谢")
	o := OtherOne{Speaker: "English"}
	Program(o, "不客气")
}
>>2019/12/26 11:24:39 谢谢 can program with go
>>2019/12/26 11:24:39 不客气 can speake English

Чей-то реальный метод (Can) описывается на уровне «программирования», а реальный метод OtherOne (Can) описывается на уровне «языка». Но оба являются описаниями возможности, и оба реализуют интерфейс Languager.

Сосредоточившись на примерах на уровне «программирования», существует много языков программирования, так что вы думаете, что лучше разработать более полный и унифицированный интерфейс? Или лучше разработать интерфейс с одной ответственностью?

Выберите подход к проектированию с единой ответственностью

Что тут сказать? Хочешь все, ничего не получай.

type Gopher interface {
	Program(string) string
}

type Student struct {
	Name string
}

func (S Student) Program(language string) string {
	return fmt.Sprintf("%s 会写 %s,叫他 Gopher。", S.Name, language)
}

func Go(body Gopher) {
	log.Println(body.Program("Go"))
}

type PHPer interface {
	Do(string) string
}

type Teacher struct {
	Name string
}

func (T Teacher) Do(language string) string {
	return fmt.Sprintf("%s 会教 %s,叫他 PHPer。", T.Name, language)
}

func Php(body PHPer) {
	log.Println(body.Do("Php"))
}

type Pythoner interface {
	Run(string) string
}

type Roommate struct {
	Name string
}

func (R Roommate) Run(language string) string {
	return fmt.Sprintf("%s 会学 %s,叫她 Pythoner。", R.Name, language)
}

func Python(body Pythoner) {
	log.Println(body.Run("Python"))
}

func main(){
	s := Student{Name: "谢小路"}
	t := Teacher{Name: "谢小人"}
	r := Roommate{Name: "谢小甲"}

	Go(s)
	Php(t)
	Python(r)
}
>>2019/12/26 12:19:36 谢小路 会写 Go,叫他 Gopher。
>>2019/12/26 12:19:36 谢小人 会教 Php,叫他 PHPer。
>>2019/12/26 12:19:36 谢小甲 会学 Python,叫她 Pythoner。

Сочетание способностей:

type Gopher interface {
	Program(string) string
}

type Student struct {
	Name string
}

func (S Student) Program(language string) string {
	return fmt.Sprintf("%s 会写 %s,叫他 Gopher。", S.Name, language)
}

func (S Student) Run(language string) string {
	return fmt.Sprintf("%s 也会写 %s", S.Name, language)
}

func Go(body Gopher) {
	log.Println(body.Program("Go"))
}

type PHPer interface {
	Do(string) string
}

type Teacher struct {
	Name string
}

func (T Teacher) Do(language string) string {
	return fmt.Sprintf("%s 会教 %s,叫他 PHPer。", T.Name, language)
}

func Php(body PHPer) {
	log.Println(body.Do("Php"))
}

type Pythoner interface {
	Run(string) string
}

type Roommate struct {
	Name string
}

func (R Roommate) Run(language string) string {
	return fmt.Sprintf("%s 会学 %s,叫她 Pythoner。", R.Name, language)
}

func Python(body Pythoner) {
	log.Println(body.Run("Python"))
}

type AwesomeDeveloper interface {
	Gopher
	Pythoner
}

func Development(a AwesomeDeveloper) {
	log.Println(a.Program("go"))
	log.Println(a.Run("python"))
}

func main(){

	s := Student{Name: "谢小路"}
	t := Teacher{Name: "谢小人"}
	r := Roommate{Name: "谢小甲"}

	Go(s)
	Php(t)
	Python(r)

	Development(s)
}

>>2019/12/26 12:24:31 谢小路 会写 Go,叫他 Gopher。
>>2019/12/26 12:24:31 谢小人 会教 Php,叫他 PHPer。
>>2019/12/26 12:24:31 谢小甲 会学 Python,叫她 Pythoner。
>>2019/12/26 12:24:31 谢小路 会写 go,叫他 Gopher。
>>2019/12/26 12:24:31 谢小路 也会写 python

Метод проектирования с единой ответственностью можно комбинировать для создания дополнительных «возможностей», таких как два или более языков программирования, как в примере AwesomeDeveloper.

Как можно видеть:Интерфейс — это набор протоколов, которые описывают его возможности и не реализованы.Интерфейс может быть реализован несколькими структурами, и одна и та же структура может реализовывать несколько интерфейсов.

Во встроенной библиотеке есть много примеров использования интерфейса, напримерioБиблиотеки, определения: Reader, Writer, Closer, Seeker... Конкретные реализации разбросаны по разным библиотекам.

io.png

Каковы преимущества этого подхода? Расслоение (или изоляция).

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

Тем не менее, это немного абстрактно, давайте найдем конкретный пример:go-elasticsearch

Всем известно, что elasticsearch — это поисковая система с открытым исходным кодом, которая предоставляет внешнему миру богатый интерфейс RESTful. Сотни из них. Поэтому, если вы хотите написать клиентскую библиотеку, сталкиваясь с таким количеством интерфейсов RESTful, с одной стороны, вам нужно подумать, как ее организовать, а с другой стороны, как справиться с изменениями интерфейса API, вызванными непрерывной итерацией. самого elasticsearch.

Вызов RESTful API — это не что иное, как несколько действий:

  • Построить параметры запроса: такие как URL, HEADER, Method и т. д.
  • Инициировать сетевой запрос: например, http.Get
  • Информация об ответе организации: ответ

Исходя из этого, в официальном исходном коде есть дизайн интерфейса:

// 描述其 Do 能力
type Request interface {
	Do(ctx context.Context, transport Transport) (*Response, error)
}

// 描述其 Perform 能力
type Transport interface {
	Perform(*http.Request) (*http.Response, error)
}

//  自定义的响应信息
type Response struct {
	StatusCode int
	Header     http.Header
	Body       io.ReadCloser
}

Чиновник также делится на три уровня структуры организационного кода:

1. Уровень интерфейса API esapi

Главное, что делает этот слой, это: организует все параметры запроса API, ответы и т. д. Но на самом деле он не инициирует сетевой запрос, а просто заимствует возможности интерфейса Transport.

Извлеките один из интерфейсов, чтобы просмотреть исходный код:

curl http://localhost:9200/_cat/health

>>1577337625 05:20:25 es-clustername green 3 3 24 11 0 0 0 0 - 100.0%

Конкретный исходный код:esapi/api.cat.health.go

type CatHealth func(o ...func(*CatHealthRequest)) (*Response, error)

type CatHealthRequest struct {
    ...
}

func (r CatHealthRequest) Do(ctx context.Context, transport Transport) (*Response, error) {
	var (
		method string
		path   strings.Builder
		params map[string]string
	)

	method = "GET"

	path.Grow(len("/_cat/health"))
	path.WriteString("/_cat/health")

	params = make(map[string]string)

	if r.Format != "" {
		params["format"] = r.Format
	}

	if len(r.H) > 0 {
		params["h"] = strings.Join(r.H, ",")
	}

	if r.Help != nil {
		params["help"] = strconv.FormatBool(*r.Help)
	}

	if len(r.S) > 0 {
		params["s"] = strings.Join(r.S, ",")
	}

	if r.Time != "" {
		params["time"] = r.Time
	}

	if r.Ts != nil {
		params["ts"] = strconv.FormatBool(*r.Ts)
	}

	if r.V != nil {
		params["v"] = strconv.FormatBool(*r.V)
	}

	if r.Pretty {
		params["pretty"] = "true"
	}

	if r.Human {
		params["human"] = "true"
	}

	if r.ErrorTrace {
		params["error_trace"] = "true"
	}

	if len(r.FilterPath) > 0 {
		params["filter_path"] = strings.Join(r.FilterPath, ",")
	}

	req, err := newRequest(method, path.String(), nil)
	if err != nil {
		return nil, err
	}

	if len(params) > 0 {
		q := req.URL.Query()
		for k, v := range params {
			q.Set(k, v)
		}
		req.URL.RawQuery = q.Encode()
	}

	if len(r.Header) > 0 {
		if len(req.Header) == 0 {
			req.Header = r.Header
		} else {
			for k, vv := range r.Header {
				for _, v := range vv {
					req.Header.Add(k, v)
				}
			}
		}
	}

	if ctx != nil {
		req = req.WithContext(ctx)
	}

	res, err := transport.Perform(req)
	if err != nil {
		return nil, err
	}

	response := Response{
		StatusCode: res.StatusCode,
		Body:       res.Body,
		Header:     res.Header,
	}

	return &response, nil
}

Метод Do выглядит очень длинным, но на самом деле он делает только три вещи:

  • Параметры запроса организации
  • обратиться с просьбой
  • Информация об ответе организации

Среди них шаг запроса — заимствование возможности Perform из Transport, а результирующий res реорганизуется в настраиваемый Response.

Тогда должно быть место для реальной реализации возможности транспорта Perform, чтобы фактически инициировать сетевой запрос.

Наконец, все запросы RESTful объединяются:esapi/api._.go

type API struct {
	Cat        *Cat
	Cluster    *Cluster
	Indices    *Indices
    ...
}

type Cat struct {
	Aliases      CatAliases
	Allocation   CatAllocation
	Count        CatCount
	Fielddata    CatFielddata
	Health       CatHealth
    ...
}

func New(t Transport) *API {
	return &API{
		Bulk:                                          newBulkFunc(t),
        ...
}

2. кибертранспортный уровень

Этот уровень в основном описывает возможность соединения и передачи. То есть настройка соединения с es кластером и реализация собственно сетевого запроса.

type Interface interface {
	Perform(*http.Request) (*http.Response, error)
}

type Client struct {
    ...
	transport http.RoundTripper
    ...
}

func (c *Client) Perform(req *http.Request) (*http.Response, error) {
        ...
    	start := time.Now().UTC()
		res, err = c.transport.RoundTrip(req)
		dur := time.Since(start)
        ...

    
}

Правильно, настоящий сетевой запрос делает http.RoundTripper, по сути, http.RoundTripper тоже интерфейс.

type RoundTripper interface {
	RoundTrip(*Request) (*Response, error)
}

При инициализации клиента используется реализация http.RoundTripper по умолчанию: http.DefaultTransport

func New(cfg Config) *Client {
	if cfg.Transport == nil {
		cfg.Transport = http.DefaultTransport
	}
    ...
}

Определенный таким образом клиент реализует как интерфейс интерфейса, так и интерфейс транспорта. Хотя оба описывают одни и те же возможности.

Итак, между этими двумя слоями нет никакой зависимости, так как же они взаимодействуют?

func (r CatHealthRequest) Do(ctx context.Context, transport Transport) (*Response, error)

Метод Do каждого запроса принимает параметр Transport, создает экземпляр клиента уровня estransport и передает созданный экземпляр клиента в качестве параметра методу Do. Но между ними нет связующей связи.

3. Слой эластичного поиска

Определите вышестоящий клиентский уровень. API уровня esapi и интерфейс уровня estransport объединены.

type Client struct {
	*esapi.API // Embeds the API methods
	Transport  estransport.Interface
}

func NewClient(cfg Config) (*Client, error) {
    ...
	tp := estransport.New(estransport.Config{
        ...

		Transport:          cfg.Transport,
        ...
	})

	client := &Client{Transport: tp, API: esapi.New(tp)}
}

Почему это так? Очевидно, что уровень esapi и уровень estransport могут выполнить эту задачу?

Проще говоря: при совместном использовании esapi и estransport конечный результат вызова выглядит следующим образом:

			req := esapi.IndexRequest{
				Index:      "test",
				DocumentID: strconv.Itoa(i + 1),
				Body:       strings.NewReader(b.String()),
				Refresh:    "true",
			}

			// Perform the request with the client.
			res, err := req.Do(context.Background(), es)

После наличия слоя elasticsearch способ вызова выглядит следующим образом:

	es, err := elasticsearch.NewDefaultClient()
	es.Cat.Health()

Проще говоря: апстрим выставляет пользователю меньше информации, что удобно для их использования, и не дает пользователю узнать больше подробностей о реализации.Рекомендуется использовать второй способ.

На самом деле этот метод реализации также прост: он заключается в инкапсуляции метода Do запроса в тип функции.

type CatHealth func(o ...func(*CatHealthRequest)) (*Response, error)

func newCatHealthFunc(t Transport) CatHealth {
	return func(o ...func(*CatHealthRequest)) (*Response, error) {
		var r = CatHealthRequest{}
		for _, f := range o {
			f(&r)
		}
		return r.Do(r.ctx, t)
	}
}

type Cat struct {
    ...
	Health       CatHealth
    ...

}

Исходя из этого, трехуровневая модель elasticsearch, вероятно, выглядит так: на самом деле внутри используется множество программных идей для интерфейса и комбинации. Читатели могут исследовать и исследовать исходный код.

Это закончилось после прочтения?

Нет, я хочу учиться на подобных идеях и реализовывать их самостоятельно, поэтому у меня есть этот проект:cartooncharts, смотрите конкретную реализацию js:chart.xkcd

Нисходящий уровень: сосредоточьтесь на детальном уровне реализации

Определите интерфейс:charts.go

type ChartsInterface interface {
	Plot(t Transport) func(w http.ResponseWriter, r *http.Request)
	Save(string, Transport) bool
	Render(t Transport) func(w http.ResponseWriter, r *http.Request)
}

type Transport interface {
	Execute(w http.ResponseWriter, r *http.Request, v interface{})
	Read(name string) ([]byte, error)
}

Некоторый тип реализации диаграммы:

type BarRequest struct {
	WithTitle
	WithXLabel
	WithYLabel
	WithDataCollection
	WithOption
}

func (bar BarRequest) Plot(t Transport) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		v := struct {
			Type      string
			Interface BarRequest
		}{
			Type:      barStackedType,
			Interface: bar,
		}
		t.Execute(w, r, v)
	}
}

Конкретной реализации нет, просто заимствует способность Execute транспорта.

Транспортный уровень: сосредоточьтесь на слое рендеринга шаблона.

type Template struct {
	Path string
}

func (T Template) Read(name string) ([]byte, error) {
	box := packr.New(name, T.Path)
	b, e := box.Find(name)
	if e != nil {
		log.Println("template read fail", e.Error())
		return nil, e
	}
	return b, nil
}

func (T Template) Execute(w http.ResponseWriter, r *http.Request, v interface{}) {
	t := template.New("")
	text, e := T.Read("plot.html")
	if e != nil {
		log.Println("template read fail")
		return
	}
	tt, e := t.Parse(string(text))
	if e != nil {
		log.Println("template parse fail")
		return
	}
	tt.Execute(w, v)
}
type Interface interface {
	Execute(w http.ResponseWriter, r *http.Request, v interface{})
	Read(name string) ([]byte, error)
}

type ChartsTransport struct {
	Template Interface
	Charts   *cartoon.Charts
}

func (C ChartsTransport) Execute(w http.ResponseWriter, r *http.Request, v interface{}) {
	C.Template.Execute(w, r, v)
}
func (C ChartsTransport) Read(name string) ([]byte, error) {
	return C.Template.Read(name)
}

func NewChartsTransport() *ChartsTransport {
	t := Template{Path: "./template"}
	return &ChartsTransport{
		Template: t,
		Charts:   cartoon.NewCharts(t),
	}
}

Восходящий слой: краткий внешний слой экспозиции

type CartoonCharts struct {
	*cartoontransport.ChartsTransport
}

func NewCartoonCharts() *CartoonCharts {
	return &CartoonCharts{cartoontransport.NewChartsTransport()}
}

Пример:

package main

import (
	"github.com/wuxiaoxiaoshen/cartooncharts"
	"log"
	"net/http"
)

var charts *cartooncharts.CartoonCharts

func init() {
	charts = cartooncharts.NewCartoonCharts()
}

func ExampleBar() {
	bar := charts.Charts.Bar("github stars VS patron number",
		charts.Charts.Bar.WithDataLabels([]interface{}{"github stars", "patrons"}),
		charts.Charts.Bar.WithDataDataSets("", []interface{}{100, 2}),
		charts.Charts.Bar.WithOptions("yTickCount", 2),
	)
	http.HandleFunc("/bar", bar)
}
func ExampleXY() {
	type point struct {
		X interface{} `json:"x"`
		Y interface{} `json:"y"`
	}
	xy := charts.Charts.XY("Pokemon farms",
		charts.Charts.XY.WithXLabel("Coodinate"),
		charts.Charts.XY.WithYLabel("Count"),
		charts.Charts.XY.WithDataDataSets("Pikachu", []interface{}{point{3, 10}, point{4, 122}, point{10, 100}, point{1, 2}, point{2, 4}}),
		charts.Charts.XY.WithDataDataSets("Squirtle", []interface{}{point{3, 122}, point{4, 212}, point{-3, 100}, point{1, 1}, point{1.5, 12}}),
		charts.Charts.XY.WithOptions("xTickCount", 5),
		charts.Charts.XY.WithOptions("yTickCount", 5),
		charts.Charts.XY.WithOptions("legendPosition", "chartXkcd.config.positionType.upRight"),
		charts.Charts.XY.WithOptions("showLine", false),
		charts.Charts.XY.WithOptions("timeFormat", "undefined"),
		charts.Charts.XY.WithOptions("dotSize", 1),
	)
	http.HandleFunc("/xy", xy)
}
func ExampleStackedBar() {
	stackedBar := charts.Charts.StackedBar("Issues and PR Submissions",
		charts.Charts.StackedBar.WithXLabel("Month"),
		charts.Charts.StackedBar.WithYLabel("Count"),
		charts.Charts.StackedBar.WithDataLabels([]interface{}{"Jan", "Feb", "Mar", "April", "May"}),
		charts.Charts.StackedBar.WithDataDataSets("Issues", []interface{}{12, 19, 11, 29, 17}),
		charts.Charts.StackedBar.WithDataDataSets("PRs", []interface{}{3, 5, 2, 4, 1}),
		charts.Charts.StackedBar.WithDataDataSets("Merges", []interface{}{2, 3, 0, 1, 1}),
	)
	http.HandleFunc("/stackedBar", stackedBar)
}
func ExampleLine() {
	line := charts.Charts.Line("Monthly income of an indie developer",
		charts.Charts.Line.WithXLabel("Month"),
		charts.Charts.Line.WithYLabel("$ Dollars"),
		charts.Charts.Line.WithDataLabels([]interface{}{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}),
		charts.Charts.Line.WithDataDataSets("Plan", []interface{}{30, 70, 200, 300, 500, 800, 1500, 2900, 5000, 8000}),
		charts.Charts.Line.WithDataDataSets("Reality", []interface{}{0, 1, 30, 70, 80, 100, 50, 80, 40, 150}),
		charts.Charts.Line.WithOptions("yTickCount", 3),
		charts.Charts.Line.WithOptions("legendPosition", "chartXkcd.config.positionType.upLeft"),
	)
	http.HandleFunc("/line", line)

}
func ExamplePie() {
	pie := charts.Charts.Pie("What Tim made of",
		charts.Charts.Pie.WithDataLabels([]interface{}{"a", "b", "e", "f", "g"}),
		charts.Charts.Pie.WithDataDataSets("", []interface{}{500, 200, 80, 90, 100}),
		charts.Charts.Pie.WithOptions("innerRadius", 0.5),
		charts.Charts.Pie.WithOptions("legendPosition", "chartXkcd.config.positionType.upRight"),
	)
	http.HandleFunc("/pie", pie)
}
func ExampleRadar() {
	radar := charts.Charts.Radar("Letters in random words",
		charts.Charts.Radar.WithDataLabels([]interface{}{"c", "h", "a", "r", "t"}),
		charts.Charts.Radar.WithDataDataSets("ccharrrt", []interface{}{2, 1, 1, 3, 1}),
		charts.Charts.Radar.WithDataDataSets("chhaart", []interface{}{1, 2, 2, 1, 1}),
		charts.Charts.Radar.WithOptions("showLegend", true),
		charts.Charts.Radar.WithOptions("dotSize", 0.8),
		charts.Charts.Radar.WithOptions("showLabels", true),
		charts.Charts.Radar.WithOptions("legendPosition", "chartXkcd.config.positionType.upRight"),
	)
	http.HandleFunc("/radar", radar)
}

func main() {

	ExampleBar()
	ExampleXY()
	ExampleStackedBar()
	ExampleLine()
	ExamplePie()
	ExampleRadar()
	log.Fatal(http.ListenAndServe(":9090", nil))
}

Выдержан единый стиль.

результат:

WX20191226-141700.png

Ссылаться на:GitHub.com/Ву Сяосяо сказал…

выйди из класса!