Интерфейс в Go не содержит полей, а также не позволяет определять методы внутри него. Любой тип должен реализовывать все методы интерфейса, чтобы иметь тип этого интерфейса. Существуют ситуации, когда полезно иметь реализацию метода по умолчанию, а также поля по умолчанию в Go. Прежде чем понять как это можно сделать давайте сначала разберёмся с требованиями для абстрактного класса:
- Абстрактный класс должен иметь поля по умолчанию
- Абстрактный класс должен иметь метод по умолчанию
- Не должно быть возможности непосредственно создать экземпляр абстрактного класса
Мы будем использовать комбинацию интерфейса (абстрактный интерфейс) и структуры (абстрактный конкретный тип). С их помощью можно будет реализовать функционал абстрактного класса. Смотри программу ниже:
package main
import "fmt"
// Абстрактный интерфейс
type iAlpha interface {
work()
common()
}
// Абстрактный конкретный тип
type alpha struct {
name string
}
func (a *alpha) common() {
fmt.Println("common called")
}
// Реализуем тип
type beta struct {
alpha
}
func (b *beta) work() {
fmt.Println("work called")
fmt.Printf("name is %s\n", b.name)
b.common()
}
func main() {
a := alpha{
name: "test",
}
b := &beta{
alpha: a,
}
b.work()
}
Результат в терминале:
go run abstractClass/example1/program1.go
work called
name is test
common called
В вышеприведенной программе:
- мы создали абстрактный интерфейс
iAlpha
, абстрактную конкретную структуруalpha
и структуру, в которой они реализованы,beta
. - Структура
alpha
встроена вbeta
- Структура
beta
имеет доступ к полю по умолчаниюname
- Структура
beta
имеет доступ к методу по умолчаниюcommon
- Невозможно использовать непосредственно структуру
alpha
не получится, поскольку она поддерживает только один из методов интерфейсаiAlpha
.
Таким образом, все требования удовлетворены, но существует одно ограничение
вышеупомянутого способа. Невозможно вызвать метод work
из common
в alpha
.
По сути нет способа вызвать неопределенный метод абстрактного интерфейса из
методов по умолчанию абстрактного конкретного типа. Однако это можно обойти
следующим образом:
package main
import "fmt"
// Абстрактный интерфейс
type iAlpha interface {
work()
common()
}
// Абстрактный конкретный тип
type alpha struct {
name string
work func()
}
func (a *alpha) common() {
fmt.Println("common called")
a.work()
}
// Реализуем тип
type beta struct {
alpha
}
func (b *beta) work() {
fmt.Println("work called")
fmt.Printf("name is %s\n", b.name)
}
func main() {
a := alpha{
name: "test",
}
b := &beta{
alpha: a,
}
b.alpha.work = b.work
b.common()
}
Результат в терминале:
go run abstractClass/example2/program2.go
common called
work called
name is test
В вышеприведенной программе:
- Мы создали новое поле
work
типаfunc
вalpha
- Мы присвоили методу
work
изalpha
методwork
изbeta
Единственная проблема в приведенной выше программе заключается в том, что можно
непосредственно создать экземпляр структуры alpha
и задав определение для
метода work
она будет удовлетворять типу iAlpha
. Это нарушает пункт 3 требований
к абстрактному классу. Попробуем исправить эту проблему. Приведенная ниже программа
решает обе проблемы:
package main
import "fmt"
// Абстрактный интерфейс
type iAlpha interface {
work()
common(iAlpha)
}
// Абстрактный конкретный тип
type alpha struct {
name string
}
func (a *alpha) common(i iAlpha) {
fmt.Println("common called")
i.work()
}
// Реализуем тип
type beta struct {
alpha
}
func (b *beta) work() {
fmt.Println("work called")
fmt.Printf("name is %s\n", b.name)
}
func main() {
a := alpha{
name: "test",
}
b := &beta{
alpha: a,
}
b.common(b)
}
Результат в терминале:
go run abstractClass/example3/program3.go
common called
work called
Name is test
В ней:
- Все методы по умолчанию принимают в качестве аргумента интерфейс
iAlpha
. Все неопределенные в структуреalpha
методы будут вызываться, используя этот аргумент в методах по умолчанию.
Приведенная выше программа удовлетворяет всем трём требованиям абстрактного класса. Это один из способов имитации абстрактного класса в Go.