Go тілі: нөлден өз блогыңызға дейін 13-сабақ (барлығы 50)
Go-дағы интерфейс: құрылысы емес, білігі бойынша келісім
Go курсының он үшінші сабағы. Интерфейс — біліктер тізімі: бір функция кез келген қоймамен жұмыс істейді, тек ол келісімде жазылғанды білсе болды. Іске асыру неге жарияланбай, өзінен-өзі шығады, оны жинау кезінде қалай тексереді және алмастырма қойма тестті дерекқорсыз неге мүмкін етеді.
Не үшін керек
Сіз мақалалар тізімін беретін функция жазып отырсыз. Бүгін олар жадыда, бір айдан кейін дерекқорда, ал тестте олар мүлде болмауы керек. Функцияны үш рет қайта жазасыз ба?
Жоқ. Функцияға алдында қандай қойма тұрғанын білудің қажеті жоқ. Оған сол қойма не істей алатынын білу жеткілікті.
Сіз мұны қолданып та қойдыңыз. Ең алғашқы серверде өңдеуші w http.ResponseWriter алатын — ал w құрылым емес, интерфейс еді: «жазылған байттарды қабылдай аламын». Ар жағында кім тұрғаны — браузер ме, файл ма, тест пе — кодыңызға бәрібір болатын.
Елестетіп көріңіз. Жұмысқа шақыру хабарландыруы. Онда сіз кім екеніңіз, қайдан келгеніңіз жазылмаған. Не істей білуіңіз керегі жазылған: мақаланы қабылдау және тізімді беру. Кітапханашы келе ме, робот па — білсе болды.
Бірден тұтас
Жаңа қалта, go mod init sabaq12, main.go:
package main
import "fmt"
type Article struct {
Title string
Words int
}
func (a Article) String() string {
return fmt.Sprintf("%s (%d сөз)", a.Title, a.Words)
}
type Store interface {
Add(a Article)
All() []Article
}
type MemoryStore struct {
items []Article
}
func (s *MemoryStore) Add(a Article) { s.items = append(s.items, a) }
func (s *MemoryStore) All() []Article { return s.items }
type LastTwoStore struct {
items []Article
}
func (s *LastTwoStore) Add(a Article) {
s.items = append(s.items, a)
if len(s.items) > 2 {
s.items = s.items[len(s.items)-2:]
}
}
func (s *LastTwoStore) All() []Article { return s.items }
func report(name string, s Store) {
s.Add(Article{Title: "Сәлем, әлем", Words: 150})
s.Add(Article{Title: "Дала туралы", Words: 400})
s.Add(Article{Title: "Шаңырақ деген не", Words: 1000})
fmt.Println(name, "—", len(s.All()), "мақала")
for _, a := range s.All() {
fmt.Println(" ", a)
}
}
func main() {
report("жады", &MemoryStore{})
report("соңғы екеуі", &LastTwoStore{})
}
Іске қоспас бұрын — төменге қарамай, оның не басып шығаратынын дауыстап айтыңыз. Содан кейін жіберіп, салыстырыңыз.
жады — 3 мақала
Сәлем, әлем (150 сөз)
Дала туралы (400 сөз)
Шаңырақ деген не (1000 сөз)
соңғы екеуі — 2 мақала
Дала туралы (400 сөз)
Шаңырақ деген не (1000 сөз)
report функциясы бір рет жазылған және өзгерген жоқ. Ал қоймалар — әртүрлі.
Талдау
Интерфейс — өрістер емес, тип қолдайтын әдістер жиыны
type Store interface {
Add(a Article)
All() []Article
}
Құрылымның ішінде өрістер тізіледі — онда не бар. Интерфейстің ішінде әдістер тізіледі — ол не істей алады. Интерфейсте өріс мүлде жоқ.
Store — Article сияқты тип. Бұл түрдегі айнымалыны жариялауға, функцияға беруге, тілімге салуға болады. Тек оның ішінде құрылым емес, «Add пен All-ды білетін бірдеңе» жатады.
Іске асыру жарияланбайды — өзінен-өзі шығады
Бағдарламадан MemoryStore Store-ды іске асыратынын жариялайтын сөзді тауып көріңіз. Ондай сөз онда жоқ.
Go-да керекті әдістердің болуы жеткілікті. Add пен All-ды дәл сол аттармен және қолтаңбамен жазсаңыз — тип жарайды, ал бұл туралы ешкімге хабарлаудың қажеті жоқ.
Елестетіп көріңіз. Ешкім анықтама әкелмейді. Басқа тілдерде «мен бұл келісімді орындаймын» деген қағаз жазу керек. Go-да сізді қағаз көрсеткеніңіз үшін емес, істей білгеніңіз үшін алады.
Осыдан пайдалы салдар шығады: интерфейсті типтерден кейін, тіпті бөтен пакетте жариялауға болады. Сіз бөтен кодтан не керегін жазасыз, ал ол сіз туралы ештеңе білмей-ақ жарай береді.
Бір функция, кез келген қойма
func report(name string, s Store) {
report MemoryStore туралы да, LastTwoStore туралы да білмейді. Ол тек келісімді біледі. Үшінші қойма пайда болса, функцияны өзгертудің қажеті болмайды.
Шақыруға назар аударыңыз: &MemoryStore{}, мекенжай белгісімен. Әдістер сілтегіш-қабылдағышпен жарияланған — func (s *MemoryStore) Add(...), — себебі Add мазмұнды өзгертеді. Демек, келісімге MemoryStore емес, *MemoryStore жауап береді. Бұл өткен сабақтың тікелей жалғасы, әрі мұнда ол алғаш рет міндетті болып тұр.
Жинау кезіндегі тексеру
Қойма әлі ешқайда берілмей тұрғанда, әдіс атындағы қатені байқамай қалу оңай. Келісімді компиляция кезінде тексеретін жол бар:
var _ Store = (*MemoryStore)(nil)
Былай оқылады: «*MemoryStore Store-ға жарайтынына көз жеткіз». Мән керек емес — сондықтан _. Әдіс жетіспесе, жинау өтпейді және тура айтады:
*BrokenStore does not implement Store (missing method All)
Бұл арзан сақтандыру, оны типтің қасына қояды.
fmt.Stringer — сіз қолданып қойған интерфейс
func (a Article) String() string {
return fmt.Sprintf("%s (%d сөз)", a.Title, a.Words)
}
Бір ғана String() string әдісі — және fmt.Println мақаланы фигуралық жақшамен емес, сіздің қалауыңызша баса бастайды. Бұл стандартты кітапханадағы fmt.Stringer интерфейсі: fmt пакеті мәннен String бар ма деп сұрайды да, бар болса шақырады.
Сіз ештеңе қоспадыңыз, ешқайда тіркелмедіңіз. Жай ғана керек аттағы әдісті жаздыңыз.
Интерфейс түріндегі айнымалыда не жатыр
Екі нәрсе: мәннің өзі және оның шын типі. Сондықтан fmt.Printf("%T", s) Store-ды емес, *main.MemoryStore-ды көрсетеді.
Интерфейстің нөлдік мәні — nil: онда мән де, тип те жоқ. Ондайдың әдісін шақыру өткен сабақтан таныс апатқа әкеледі.
Интерфейсті мезгілінен бұрын жасамаңыз
Айларыңызды үнемдейтін ереже: интерфейсті қабылдаңыз, құрылымды қайтарыңыз. Функция өзіне не керегін жариялайды, ал конструктор нақты типті береді.
Іске асыру біреу болып тұрғанда интерфейс ашпаңыз. Келісім таңдау бар жерде керек; таңдау жоқ жерде ол тек оқуға тура келетін бір қабат қосады.
Елестетіп көріңіз. Өлшеу манекені. Ол адам емес, бірақ иығы сол — пиджакты тексеруге болады. Тестегі алмастырма қойма дәл сондай: дерекқор емес, бірақ келісімді орындайды.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жазып жауап беріңіз. Жауаптары — сабақтың соңында.
- Бағдарламада
MemoryStoreStore-ды іске асыратынын қай жерде жариялайды? mainішінде негеMemoryStore{}емес,&MemoryStore{}беріледі?- Бағдарлама онсыз да жұмыс істейтін болса,
var _ Store = (*MemoryStore)(nil)жолы не үшін керек?
Тапсырма
Міндетті. total(s Store) int функциясын жазыңыз — қоймадағы барлық мақаланың сөз қосындысы. Содан кейін main_test.go файлында сол екі әдісі бар өзіңіздің fakeStore алмастырма қоймаңызды жариялап, ішіне 150 және 400 сөздік екі мақала салыңыз да, total 550 қайтаратынын тестпен тексеріңіз. Тестте нағыз қойманы пайдалануға болмайды.
Қалауыңызша.
- Мақалаларды кері ретпен беретін үшінші
ReverseStoreқоймасын қосыңыз.report-ты өзгертуге болмайды. MemoryStore-данAllәдісін алып тастап, компилятордың хабарын толық оқыңыз.MemoryStoreтипінеString()қосып, қойманыfmt.Printlnарқылы басып шығарыңыз.
Блогта қайда тұрады
Интерфейс — блогтың оқу бағдарламасы болудан қалатын жері. Әрі қарай курста Store нағыз болады: алдымен жады, сосын Postgres, ал тестер сол уақыттың бәрінде дерекқорсыз да, желісіз де алмастырмамен жұмыс істей береді. Жазып қойған функцияларыңыз бір жолға да өзгермейді — олар онсыз да тек келісімді біледі.
Жауаптар
Жауаптарды көрсету
- Ешқайда. Go-да іске асыру жарияланбайды: типте керекті әдістердің бәрі сол қолтаңбамен болса, ол интерфейске жарайды. Сондықтан интерфейсті сіз туралы ештеңе білмейтін бөтен тип үшін де сипаттауға болады.
- Себебі
AddпенAllәдістері сілтегіш-қабылдағышпен жарияланған, демекStoreкелісіміне*MemoryStoreтипі жауап береді.MemoryStore-да бұл әдістер ресми түрде жоқ, компилятор бас тартады. - Ол келісімді бірінші қолданғанда емес, жинау кезінде тексереді. Қойма ешқайда берілмей тұрғанда әдіс атындағы қате байқалмай қалады; бұл жол оны бірден ұстайды және жұмыс кезінде түк тұрмайды.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.