#17 golang Интерфейсы - проверка типа type switch
Primary tabs
Forums:
Через интерфейсы в Go реализован полиморфизм.
В отличие от таких языков, как C++, Java и PHP, где типизация всегда явная, в Go при помощи интерфейсов реализована так называемая «утиная типизация».
Что это значит:
- В C++, когда создаете класс, вы явно указываете, что он реализует такой интерфейс: «Вот мои документы, вот я наследовался от этого интерфейса, я его реализую. Я — утка. Я точно знаю, что я — утка».
- В Go, когда вы создаете структуру с методами, она не знает, какому интерфейсу соответствует, нет явного механизма указания, к какому интерфейсу относится тип, вместо этого тут применяется "утиная типизация"
Пример 1 - пример проверки структуры на соответствие интерфейсу
Посмотрим, как это работает в коде. Демонстрация определения интерфейса:
type Payer interface {
pay(int) error // сигнатура метода "оплатить"
}
-- в этом фрагменте кода объявлен интерфейс Payer («плательщик»).
Чтобы соответствовать этому интерфейсу, нужно иметь метод pay («оплатить») который принимает int и возвращает ошибку.
Далее реализуем структуру «кошелёк», у которой есть поле cash, то есть количество денег в этом кошельке, и есть метод «оплатить»:
// структура, дальше мы навесим на нее метода
// и она станет соответствать интерфейсу выше
type Wallet struct {
cash int // количество денег
}
// навещиваем метод на структуру
func (w *Wallet) pay(amount int) error {
if w.cash < amount {
return fmt.Errorf("не хватает денег в кошельке")
}
w.cash -= amount
return nil
}
Обратите внимание: В реализации кошелька нигде нет упоминания того, что он каким-либо образом реализует интерфейс «плательщик», дальше golang "по факту" (утиной типизацией) будет проверять, есть ли соответствие или нет.
Теперь давайте используем всё это в коде:
// наш интерфейс
type Payer interface {
pay(int) error // сигнатура метода "оплатить"
}
// структура, дальше мы навесим на нее метода
// и она станет соответствать интерфейсу выше
type Wallet struct {
cash int // количество денег
}
// навещиваем метод на структуру
func (w *Wallet) pay(amount int) error {
if w.cash < amount {
return fmt.Errorf("не хватает денег в кошельке")
}
w.cash -= amount
return nil
}
func main() {
// Создадим кошелек
VasyaWallet := &Wallet{
cash: 100,
}
// и купим с него что-то на 10 монет
buyIt(VasyaWallet, 10)
fmt.Println("Осталось монет:", VasyaWallet.cash)
}
// Этот метод требует интерфейс "платильщик"
func buyIt(payer Payer, amount int) {
err := payer.pay(amount)
if err != nil {
panic(err)
}
fmt.Printf("Спасибо за покупку через %T\n\n", payer)
}
-- тут мы использовали %T для вывод типа аргумента. Если запустим эту программу, то получим распечатку:
Спасибо за покупку через *main.Wallet Осталось монет: 90
В этом примере была всего одна структура, реализующая интерфейс. Давайте рассмотрим более сложный пример, когда есть несколько структур, которые реализуют интерфейс.
Пример 2 - несколько разных структур, реализуют один интерфейс
// Обший интерфес "Платильщик"
type Payer interface {
pay(int) error // что-то оплачиваем
getBalance() int // узнаем сколько денег еще осталось
}
type Wallet struct {
cash int
}
// навешиваем метод на структуру
func (w *Wallet) pay(amount int) error {
if w.cash < amount {
return fmt.Errorf("не хватает денег в кошельке")
}
w.cash -= amount
return nil
}
func (w *Wallet) getBalance() int {
return w.cash
}
// Структура описывающая банковскую карточку
type Card struct {
Balance int
ValidUntil string
Cardholder string
CVV string
Number string
}
func (c *Card) pay(amount int) error {
if c.Balance < amount {
return fmt.Errorf("не хватает денег на карте")
}
c.Balance -= amount
return nil
}
func (c *Card) getBalance() int {
return c.Balance
}
// Платежные аккаунт в какой-то системе
type KtuPay struct {
money int
ktuId string
}
func (a *KtuPay) pay(amount int) error {
if a.money < amount {
return fmt.Errorf("не хватает денег на аккаунте")
}
a.money -= amount
return nil
}
func (kp *KtuPay) getBalance() int {
return kp.money
}
// Функция для покупки чего-то через любое средство оплаты,
// но оно должно соответствовать интерфейсу Payer
func Buy(p Payer, amount int) {
err := p.pay(amount)
if err != nil {
fmt.Printf("Ошибка при оплате %T: %v\n", p, err)
return
}
fmt.Printf("Спасибо за покупку через %T\n", p)
}
// Произведем оплату разными средствами
func main() {
myWallet := &Wallet{cash: 100}
Buy(myWallet, 10)
fmt.Println("Осталось монет:", myWallet.getBalance())
var myMoney Payer
myMoney = &Card{Balance: 80, Cardholder: "rvasily"}
Buy(myMoney, 10)
fmt.Println("Осталось монет:", myMoney.getBalance())
myMoney = &KtuPay{money: 70}
Buy(myMoney, 10)
fmt.Println("Осталось монет:", myMoney.getBalance())
}-- мы описали три разных платежных средства, которые, тем не менее, соответствуют одному интерфейсу Payer. Если запустить код, то получим распечатку вроде:
Спасибо за покупку через *main.Wallet Осталось монет: 90 Спасибо за покупку через *main.Card Осталось монет: 70 Спасибо за покупку через *main.KtuPay Осталось монет: 60
Определение типа, скрывающегося под интерфейсом - явная проверка на соответствие, получение типа из интерфейса - type switch
Пусть у нас есть переменная какого-то типа, которая объявлена в функции как соответсвующая интерфейсу.
Иногда нужно не просто вызывать какие-то методы интерфейса, но и проверять, какая именно структура/тип данных, удовлетворяющая интерфейсу, поступила на вход.
Для этих целей есть специальная конструкция вида:
switch переменнаяТипаИнтерфейс.(type)
Добавим функцию Buy2() с использованием type switch:
// Обший интерфес "Платильщик"
type Payer interface {
pay(int) error // что-то оплачиваем
getBalance() int // узнаем сколько денег еще осталось
}
type Wallet struct {
cash int
}
// навешиваем метод на структуру
func (w *Wallet) pay(amount int) error {
if w.cash < amount {
return fmt.Errorf("не хватает денег в кошельке")
}
w.cash -= amount
return nil
}
func (w *Wallet) getBalance() int {
return w.cash
}
// Структура описывающая банковскую карточку
type Card struct {
Balance int
ValidUntil string
Cardholder string
CVV string
Number string
}
func (c *Card) pay(amount int) error {
if c.Balance < amount {
return fmt.Errorf("не хватает денег на карте")
}
c.Balance -= amount
return nil
}
func (c *Card) getBalance() int {
return c.Balance
}
// Платежные аккаунт в какой-то системе
type KtuPay struct {
money int
ktuId string
}
func (a *KtuPay) pay(amount int) error {
if a.money < amount {
return fmt.Errorf("не хватает денег на аккаунте")
}
a.money -= amount
return nil
}
func (kp *KtuPay) getBalance() int {
return kp.money
}
// Покупка с проверкой типа платежного средства
func Buy2(p Payer, amount int) {
switch p.(type) {
case *Wallet:
fmt.Println("Оплата наличными")
case *Card:
fmt.Println("Платим карточкой!")
default:
fmt.Println("Новая платежная система!")
}
err := p.pay(amount)
if err != nil {
fmt.Printf("Ошибка при оплате %T: %v\n", p, err)
return
}
fmt.Printf("Спасибо за покупку через %T\n", p)
}
// Произведем оплату разными средствами
func main() {
myWallet := &Wallet{cash: 100}
Buy2(myWallet, 10)
fmt.Println("Осталось монет:", myWallet.getBalance())
var myMoney Payer
myMoney = &Card{Balance: 80, Cardholder: "rvasily"}
Buy2(myMoney, 10)
fmt.Println("Осталось монет:", myMoney.getBalance())
myMoney = &KtuPay{money: 70}
Buy2(myMoney, 10)
fmt.Println("Осталось монет:", myMoney.getBalance())
}
-- тут мы определяем тип переданного значения в коде. Ниже подробнее поговорим о том как получить тип из интерфейса.
Обращение к полям структуры, в случае если аргумент передан как интерфейс
Мы не можем получить поля структуры, если аргумент передавался в функцию с типом интерфейса, в котором методов нет:
type Wallet struct {
cash int
}
// навешиваем метод на структуру
func (w *Wallet) pay(amount int) error {
if w.cash < amount {
return fmt.Errorf("не хватает денег в кошельке")
}
w.cash -= amount
return nil
}
// этот метод не будет входить в интерфейс
func (w *Wallet) sayPrivet() {
fmt.Println("Привет!")
}
// Обший интерфес "Платильщик"
type Payer interface {
pay(int) error // что-то оплачиваем
}
// Обращение к полю и методу, не описываемым интерфейсом
func showCash(p Payer) {
// p.sayPrivet() // не сработает!
// p.cash // не сработает!
wallet, ok := p.(*Wallet) // пробуем преобразовать тип
if ok {
fmt.Println("Осталось монет", wallet.cash)
wallet.sayPrivet()
} else {
fmt.Println("Кажется, это не обычный кошелек, а что-то ещё")
}
}
func main() {
var x Payer = &Wallet{cash: 10}
showCash(x)
}
-- тут стоит заметить, что поля/переменные (в отличии от методов) мы даже при наличии желания не сможем описать в интерфейсе (golang не поддерживает это в принципе), а потому для получения доступа к полям, как в ситуации выше, нам остается только явно преобразовывать тип через конструкцию вида:
wallet, ok := p.(*ТипДанных)
Видео-материалы
- : ВкВидео | Ютуб | Телеграм
- Log in to post comments
- 83 reads
vedro-compota
Thu, 04/16/2026 - 20:13
Permalink
уточнить почему, если
уточнить почему, если ожидаается интерфейс, то передается указатель, а не значение
_____________
матфак вгу и остальная классика =)