Pull to refresh

Comments 17

Sorry, а есть где-то примеры для понимания интерфейсов, например, для логики бэкэнда типового сайта продаж или учетной системы (купил/продал/кому/дата… номенклатура), чтобы увидеть, как они помогают. Люблю животных, но лично мне на их примере не заходит. Возможно, Вам будет удобно привести пример. Я понимаю, что интерфейсы пользуют во всю (web серверы ..), но вот простых примеров, как было бы без них и с ними, — не встречал. Спасибо.
Вот небольшой пример.

Там источник данных реализован в виде интерфейса
Согласен на 100%, вообще ничего не понятно, что автор хотел сказать. Если хоть он сам понял, и то хорошо.

P.S. Не говорю, что я написал бы лучше.
Чтобы понять статью, стоит для начала прочитать оригинал указанный во введении.
Если же вы его прочли и не поняли развитие идеи, попробую все таки пояснить.

  • В оригинальной статье есть раздел «Do This Instead». Здесь в разделе «Поиграем с абстракциями», я пытаюсь показать, что не всегда использование таких обобщенных интерфейсов как `Speaker` дает нужный результат.
  • Раздел «Расширим поведение» показывает пример того, как на основе интерфейса можно создать абстрактный тип данных. Он соотносится с разделом «Abstract Data Types» из оригинальной статьи. В которой в качестве абстрактного типа дан пример из стандартной библиотеки.
  • Разделом «Обсудим размеры интерфейсов» я пытаюсь показать, что «broad interfaces» не всегда являются злом, и не нужно всегда использовать «минимально доступные интерфейсы». Иногде уместнее передать абстрактный тип данных (Animal вместо Speaker).
  • Ну и раздел «Теперь немного про Закон Постеля» соотносится с одноименным разделом из оригинальной статьи. Он говорит о том, что не стоит возводить правило «Accept interfaces, return structs» в абсолют.
К сожалению сложно найти OpenSorce «типовой сайт продаж или учетную систему», да еще и на Go.

Могу напимер порекомендовать взглянуть на код платформы для разработки микросервисов go-micro.

Она построена на базе нескольких абстрактных типов, таких как (Codec, Broker, Registry, Selector, Transport...). В исходном коде платформы существуют одноименные интерфейсы и реализуя их вы можете подменять те или иные компоненты системы.

К примеру интерфейс Broker описывает абстрактный брокер сообщений. Реализуя его вы можете использовать любой брокер в вашем приложении. Примеры реализации брокеров для данной платформы можно найти в репозитории micro/go-plugins
Я, возможно, чего-то не понимаю. Но в ваших примерах вы дрессируете дрессировщика.
Классу дрессировщик не нужно знать особенности реализации выполнения команд классами животных.
Может имело смысл ввести интерфейс типа TrainedAnimal, с одним публичным методом ExecuteCommand и уже в нём для каждого класса решать, что собака по команде «Голос» лает, слон — трубит, кошка — выполняет рандомное действие, а человек этот интерфейс вообще не реализует, ибо не дрессированный.
А о том чтоб не попасть на арену цирка в качестве питомца заботиться должен был класс дрессировщика, например, не выставляя публично свой метод Speak. «Если оно говорит по команде, почему мы не можем показывать его на сцене?»
И тогда весь смысл нагромождения интерфейсов из примера теряется. Если оно говорящее — пусть говорит, если дрессированное — пусть выполняет команды. Дрессировщик работает только с дрессированным, а похвалить может любое говорящее, человеков тоже хвалить можно, вроде.
Классу дрессировщик не нужно знать особенности реализации выполнения команд классами животных...

Жаль, что вы не привели кода, так бы было чуть проще понять. Но в целом идею я уловил. Если я вас правильно понял, вас смущает метод Tamer.Command(...). И вы предлагаете перенести алгоритм из него внутрь TaimedAnimal.ExecuteCommand(...).
Смотрите, на самом деле дресссировщик не знает особенности того как команда выполняется животным. Данный метод описывает алгоритм который должен произвести дрессировщик по отношению к животному, чтобы оно выполнило команду. Попробую пояснить в виде кода.

func (t *Tamer) Command(action int, a Animal) string {
	switch action {
	case ActSit:
                // Подойти к питомцу
                // Стать к нему лицом и наклонится
                // Показать питомцу лакомство
		return a.Sit() // Произнести команду сидеть
	}
	return ""
}


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

func (d Dog) Sit() string {
    // Посмотреть на дрессировщика
    // Выпрямить передние лапы
    // Согнуть задние лапы
    // Повилять хвостом
    return "sit"
}


Надеюсь более менее понятно объяснил?
Я не совсем о том. На уровне тех же ваших абстракций, действие («сидеть») выполняется животным и над животным и участие дрессировщика в процессе нужно только для того чтоб отдать команду.
Ваш вариант вполне имеет право на жизнь, но при изменении количества доступных команд/животных, вы столкнётесь с трудностями.

Представьте, например, что появилась у вас обезьяна в цирке, которая знает команду 'читай газету'. В текущем варианте реализации каждому животному нужно будет добавить пустой метод ReadNewspaper. А если бы мы делали через TrainedAnimal, правки нужны были бы только у дрессированной обезьяны, у остальных default case для всех невыполнимых команд.
Сделать так чтоб по команде «сидеть», обезьяна не тупо садилась, а приносила стул (monkey.BringChair) и начинала читать газету (monkey.ReadNewspaper) в принципе невозможно получается (метод Sit у обезьяны есть, но тут нам не нужен).

Смотрите, на самом деле дресссировщик не знает особенности того как команда выполняется животным.

Как минимум, он знает что команда выполняется только тренированным животным, и проверяет тренированность при каждом вызове команды, эта проверка как раз могла бы быть проведена во время проверки интерфейса и один раз.
Я понимаю, да, можно сделать и так. Здесь возникают trade-off.
эта проверка как раз могла бы быть проведена во время проверки интерфейса и один раз.

А если завтра укротитель захочет все таки «тренировать» не тренированное животное? С абстракцией TamedAnimal, у нас не будет такой возможности получается?

func (t *Tamer) Command(action int, a Animal) string {
       if !a.IsTrained() {
          t.train(a, action)
       }
	...
}
Хм, ну мы сможем создавать декоратор, принимающий не тренированное животное и отдающий тренированное. И управлять «тренированностью» для каждого класса. А не внезапно начать тренировать всех.
Да, я понимаю, просто все это не совсем имеет отношение к сути статьи. Смотрите, я сделаю так, как вы предлагаете.

type TamedAnimal interface {
        Speaker
	Do(command int) string
}

type Tamer struct{}

func (t *Tamer) Speaks() string { return "WAT?" }

func (t *Tamer) Command(action int, a TamedAnimal) string {
	return a.Do(action)
}

func (t *Tamer) Praise(a Speaker) string {
	return a.Speaks()
}


Теперь мы имеем более конкретный абстрактный тип. Я согласен. Тем не менее, это не меняет сути статьи. Суть то в том, чтобы показать, как можно создавать абстрактные типы данных используя интерфейсы Go, а так же показать, что использование слишком общих абстракций не всегда дает нужный/ожидаемый результат.
простите, и вот это предлагается взамен Си и Си++?
:=, *, &? даже начинать учить страшно.
Go предлагается взамен пыхыпы, шарпа и иже с ними, вроде. Он не позиционируется как язык системного программирования. Взамен Си — Rust, но там вам тоже может страшным показаться.
Понял, думал все таки взамен «сложных» С, С++. Взамен PHP и так все это дело усложнить? Какой смысл? Что он дает против PHP? Скорость разработки? Легкость разработки? Судя по этой статье я думаю что вряд ли. Да нет, он мне не кажется страшным :) я так, для красного словца уж использовал что страшно. Начал, но пока только начал учить. Про Rust спасибо, тоже что-то такое слышал, тоже было интересно про него узнать.
Против PHP какую-никакую компилируемость, работу с потоками и т.п. По заверениям разработчиков Go даёт что-то типа «врождённой грамотности», т.е. на нём сложнее написать «плохой» код, но не вникал особо глубоко.
Про Rust ничего лучше их «книжки», вроде, и нет doc.rust-lang.org/stable/book/title-page.html
Рекламятся они, в основном, своей механикой выделения/освобождения памяти + возможностью использовать «бесплатно» абстракции высокого уровня, но там много других плюсов и минусов.
По работе возникла необходимость выучить Го. Основы легко запоминаются за неделю-две. Синтаксис после С и С++ немного непривычный, но в целом отторжения не вызывает, особенно после фич 11-17го стандарта, некоторые из которых в Го есть как часть языка — например, запись типа возвращаемого значения после аргументов функции (как в многих других языках) или возврат нескольких значений из функции и т.д. Единственный сложный момент — переключение мозга с ООП на использование Гошных интерфейсов. Это примерно как переход с С на С++, может даже немного проще.
Имхо, на текущий момент область использования языка ограничена микросервисами/вебом, т.к. нормальных библиотек/врапперов работы с системными вещами пока нет. Не смотря на обилие гитхабовских репозиториев с врапперами половина из них тухлые/нерабочие (я про системные вещи, типа хоткеев, звука, работы с буфером обмена). Но для тех же микросервисов он хорош и гораздо проще С++ в использовании и освоении (в полном объёме).
спасибо. Я так чисто ради интереса начал учить Go, остановился пока в самом зачаточном уровне. Просто работы много, времени мало. А так посмотреть очень хочется на сам язык. Практической пользы только сильно пока не вижу.
Sign up to leave a comment.

Articles