На этом шаге мы продолжим рассматривать встраиваивание интерфейсов в Go.
Поддержку всех интерфейсов представленных на предыдущем шаге можно реализовать в любом пользовательском типе.
func (пара *ПараСтрок) ВерхнийРегистр() { пара.первая = strings.ToUpper(пара.первая) пара.вторая = strings.ToUpper(пара.вторая) } func (пара *ПараСтрок) НижнийРегистр() { пара.первая = strings.ToLower(пара.первая) пара.вторая = strings.ToLower(пара.вторая) } func (пара *ПараСтрок) ПерваяПрописная() { пара.первая = НачатьСПрописной(пара.первая) пара.вторая = НачатьСПрописной(пара.вторая) }
В тип ПараСтрок были добавлены методы, реализующие интерфейсы НижнийРегистр, ВерхнийРегистр и ПерваяПрописная. Оба типа, *Part и *ПараСтрок, удовлетворяют требованиям всех рассматриваемых интерфейсов, включая интерфейс ИзменитьРегистр, потому что он встраивает интерфейсы, уже реализуемые типом. Оба они так же реализуют интерфейс fmt.Stringer из стандартной библиотеки. А тип *ПараСтрок дополнительно реализует пользовательский интерфейс Exchanger и интерфейс io.Reader из стандартной библиотеки.
Не всегда требуется включать реализацию всех интерфейсов, например если отказаться от метода ПараСтрок.ПерваяПрописная(), тогда тип *ПараСтрок удовлетворяет требованиям только интерфейсов НижнийРегистр, ВерхнийРегистр, ИзменитьРегистр, Exchanger, fmt.Stringer и io.Reader.
Приведем пример использования этих методов.
пример := Part{1234, " ПРОГРАММЫ НА "} пример.НижнийРегистр() fmt.Print(пример.Name) //программы на строка := ПараСтрок{"GO ", "Qt"} строка.ПерваяПрописная() fmt.Print(строка.первая, строка.вторая) //Go Qt }
Архив с примером можно взять здесь.
Если имеется множество значений и требуется вызвать метод для каждого из них, то можно реализовать следующее решение этой задачи:
for _, x := range []interface{}{&пример, &строка} { x.(ПреобразоватьРегистр).ВерхнийРегистр() // Неконтролируемое приведение типа }
Здесь необходимо использовать указатели на значения, потому что все методы, изменяющие регистр символов, изменяют само значение, относительно которого вызываются, и потому требуют передачи приемников по указателю.
Подход, представленный в этом фрагменте, имеет недостатки:
for _, x := range []interface{}{&пример, &строка} { if x, ok := x.(ВНижнийРегистр); ok { // затеняющая переменная x.НижнийРегистр() } }
В этом фрагменте реализовано более безопасное решение и используется самый специализированный интерфейс, но он выглядит достаточно громоздко.
Проблема – в использовании среза со значениями универсального типа interface{}, а не определенного типа или типов, реализующих определенный интерфейс. Если все, что доступно в данной точке программы, – это срез типа [] interface{}, тогда данное решение можно применять.
for _, x := range []СПрописной{&пример, &строка} { x.ПерваяПрописная() }
Этот фрагмент иллюстрирует самое удачное решение: вместо операции приведения типа для значений универсального типа interface{} здесь срез определен как срез со значениями типа СПрописной – наиболее специализированного интерфейса, достаточного для решения поставленной задачи, а все проверки типов перекладываются на компилятор.
Архив с примером можно взять здесь.
На следующем шаге будем рассматривать структуры в Go.