Шаг 92.
Язык программирования Go.
Поддержка встраивания интерфейсов в Go (окончание)

    На этом шаге мы продолжим рассматривать встраиваивание интерфейсов в 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.


Предыдущий шаг Содержание Следующий шаг