# Интерфейсы Интерфейсы нужны для описания требований к поведению объектов без уточнения типов самих объектов. ```mermaid flowchart TD ObjType1 --> Interface1 ObjType2 --> Interface1 Interface1 --> func1 ObjType2 --> Interface2 Interface2 --> func2 ``` ```golang // Интерфейс может только возвращать строку (ObjType1) type Interface1 interface{ Get()string } // Включает в себя интерфейс на чтение, расширяет отправкой (ObjType2) type Interface2 interface{ Interface1 Write(string)error } ``` `func1` не различает фактические типы `ObjType1` и `ObjType2` -- для неё это один тип `Interface1`. ## Что даёт интерфейс - гарантия контракта поведения объекта; - не нужно изменять существующий алгоритм при передаче объекта нового типа; - создание возможностей тестирования на разных этапах; - скрытие деталей реализации объекта. ## Контракт поведения Контракт поведения -- это гарантия того, что объект передаваемый как параметр вызова обладает нужным поведением. Контракт поведения -- это набор методов и их аргументов с типами для успешной передачи. Разные вызовы требуют разных контрактов (наборов методов), таки образом один и тот же объект может __удовлетворять__ разным интерфейсам, например: ```golang type IWriter interface { Write(fileName string, binData []byte)error } type IReader interface { Read(fileName string)([]bte, error) } type IWriterReader interface { IWriter IReader } func write(wr IWriter){ _ = wr.Write("test.txt") } func read(rd IReader){ _, _ = rd.Read("test.txt") } func rewrite(rw IWriterReader){ binData,_=rw.Read("test.txt") binData = append(binData, []byte("hello\n")) _ = rw.Write("test.txt", binData) } ``` Вызову на чтение -- не интересна возможность записи. Вызову на запись -- не интересна возможность чтения. Вызов на запись и чтение одновременно требует обоих методов. Объект передаваемый во все три вызова может __удовлетворять__ как конкретным интерфейсу, так и всем сразу. Конкретный вызов с требованием интерфейса: - нужное поведение требует; - лишнее поведение проигнорирует и обратиться к такому неописанному поведению из интерфейса невозможно. ## Сохранение алгоритма Алгоритм может быть расположен в вендоринге. Алгоритм сложный, специфичный, обобщённый и покрыт тестами. Как передать туда объект нового типа -- только через интерфейс. Типичный пример: пакет `sort` из стандартной библиотеки: [Описание swap](https://pkg.go.dev/sort) ```golang type Sort interface { Less(adr0, adr1)bool Swap(adr0, adr1) Len()int } ``` _Не важно_ какой тип реализует этот интерфейс -- он будет иметь возможность сортировки _оптимальным_ способом. **Объекты разные -- алгоритм один**. ## Тестирование Интерфейсы пригодятся: - нужна БД, а она недоступна на локальной машине разработчика; - нужно имитировать отсутствие связи с шиной данных; - нужно имитировать внезапный сбой. Пример: ```golang type ILink interface { Get()(string, error) } func Get(link ILink)(string, error) { resp,err:=link.Get() return resp, err } type Link struct{} func (sf *Link)Get()(string, error){ . . . // Вот здесь на реальном клиенте НЕВОЗМОЖНО имитировать обрыв связи return resp, err } type MockLink struct{ IsBadLink_ bool // Устанавливаемый извне признак отсутствия связи для тестов } func (sf *MockLink)Get()(string, error){ if sf.IsLinkBad { return "", fmt.Errorf("MockLink.Get(): IsBadLink_==true") } return "ok", nil } ``` Подставляя во время тестов `MockLink` можно смоделировать любую ситуацию сбоя реального клиента. Подставляя любые структуры мок-клиента БД -- можно смоделировать любую ситуацию с данными в БД (в том числе -- обрыв связи, испорченные данные, неожиданные данные, задержки при перегрузках базы и т.п.) ## Скрытие деталей реализации объекта Это полезное свойство заставляет выделять существенные части объекта и защищать внутренее устройство объекта. Пример: ```golang type ILink interface { Get()(string, error) } func GetInterface(link ILink)(string, error) { resp, err := link.Get() return resp, err } type Link struct{ Url string // Вот это поле при прямом доступе можно легко испортить } func GetInterface(link Link)(string, error) { link.Url = "bad_url" resp, err := link.Get() // Здесь с вероятность 146% будет ошибка return resp, err } ``` В первом случае -- невозможно испортить (намеренно или необдуманно) внутреннее состояние объекта (интерфейс не имеет состояния). Во втором случае -- работа программы будет нарушена неизбежно и последствия ошибки будут распространены на всю программу. ## Как вернуть объект Интерфейс не скрывает до конца объект. Но если вызывающая сторона не знает о реальном типе объекта -- она его не получит. Пример: ```golang func Get(link ILink)(string, error){ switch link.(type){ case Link: ... case LinkProxy: ... case LinkForward: ... default: return "", fmt.Errorf("Get(): unknown type ILink(%#v)", link) } } ``` ## Проблемы интерфейсов - сложнее отлаживать (нужен отладочный вывод или отличный отладсик в IDE); - нужно выработать привычку делать _узкие_ интерфейсы; - можно _случайно_ удовлетворить требованиям интерфейса. **Интерфейс -- это полезный инструмент, позволяющий решать проблемы связности, архитектурного развития, изоляции.**