2013-03-29 2 views
4

Я начал изучать Go после игры со структурной типизацией на других языках, таких как Scala и OCaml, и я пытаюсь отобразить некоторые из них идиоматические методы между языками. Рассмотрим следующие типыСтруктурная типизация и полиморфизм в Go - Написание метода, который может работать на двух типах с одинаковыми полями

type CoordinatePoint struct { 
    x int 
    y int 
    // Other methods and fields that aren't relevant 
} 

type CartesianPoint struct { 
    x int 
    y int 
    // Other methods and fields that aren't relevant 
} 

Скажем, мы хотели бы написать метод, который работает на обоих этих типов, чтобы вычислить их полярных координат представления, func ConvertXYToPolar(point XYPoint) PolarPoint. Если типы CartesianPoint и CoordinatePoint определяют методы получения и установки для полей x и y, мы можем определить XYPoint как общий интерфейс с этими методами, что позволяет нам работать с обоими типами, но, поскольку это означает, интерфейсы не могут объявлять поля, а только методы.

Исходя из этого, у меня есть несколько вопросов:

  1. Что такое идиоматических способ обработки этого в Go?
  2. Можно ли это сделать без изменения существующих типов?
  3. Можем ли мы сохранить безопасность типа, то есть избегать определения ConvertXYToPolar без использования пустого типа интерфейса в качестве параметра и преобразования вручную?
  4. Если интерфейсы и неявное удовлетворение интерфейсов являются первичными инструментами для полиморфизма в Go, является ли запрет полей в ограничениях интерфейсов?
  5. Являются ли методы getter/setter, обычно определяемые на structs, чтобы обойти это ограничение?
  6. Есть ли веская причина для принятия конструктивного решения не для поддержки полей в определениях интерфейсов?

Я считаю простоту встроенных типов, неявным удовлетворения интерфейса и интерфейс на основе полиморфизма быть очень простым и привлекательным сочетанием методов содействия повторного использования кода и ремонтопригодности, но запрещающее поле в определениях интерфейса делает Гоу возможности структурной типизации несколько ограничены с моей точки зрения. Я пропустил простое решение?

ответ

7

Обычный способ состоит в использовании композиции:

type Point struct { 
    x int 
    y int 
} 

type CoordinatePoint struct { 
    Point 
    other stuff 
} 

type CartesianPoint struct { 
    Point 
    Other methods and fields that aren't relevant 
} 

Go синтаксиса делает этот состав в основном чувствовать себя как наследование на других языках.Например, вы можете сделать это:

cp := CoordinatePoint{} 
cp.x = 3 
log.Println(cp.x) 

И вы можете вызывать функции, принимая Point в качестве параметра с

doAThingWithAPoint(cp.Point) 

Для того, чтобы ваши очки передаются попеременно, вы должны определить интерфейс

type Pointer interface { 
    GetPoint() *Point 
} 
func (cp CoordinatePoint) GetPoint() *Point { 
    return &cp.Point 
} 

Тогда вы могли бы определить функции, принимающие Pointer:

func doSomethingWith(p Pointer) { 
    log.Println(p.GetPoint()) 
} 

Другое решение будет основываться на интерфейс определения GetX, SetX, GetY и SetY, но я лично считаю такой подход намного тяжелее и более многословен, чем это необходимо.

+1

За исключением того, что этот метод не поддерживает полиморфизм и поэтому не удовлетворяет потребностям, изложенным в вопросе, который должен передать либо координатный указатель, либо CartesianPoint для функции, которая вычисляет PolarPoint. Вы не могли, учитывая это решение, написать ConvertXYToPolar как «func (Point) PolarPoint» и принять, например, CartesianPoint. – burfl

+1

@burfl Ответ не был завершен. Я думаю, что ваша озабоченность сейчас решена. –

+0

Хорошие дополнения. Мне очень нравится этот подход. Я бы использовал интерфейс с getX(), setX(), getY() и setY(), но это менее элегантно, чем ваше решение. +1 – burfl

1

Мой первый проект будет выглядеть следующим образом,

package points 

type XYPoint struct { 
    X, Y int64 
} 

type CoordinatePoint struct { 
    XYPoint 
} 

type CartesianPoint struct { 
    XYPoint 
} 

type PolarPoint struct { 
    R, T float64 
} 

type XYToPolarConverter interface { 
    ConvertXYToPolar(point XYPoint) PolarPoint 
} 

func (cp *CoordinatePoint) ConvertXYToPolar(point XYPoint) PolarPoint { 
    pp := PolarPoint{} 
    // ... 
    return pp 
} 

func (cp *CartesianPoint) ConvertXYToPolar(point XYPoint) PolarPoint { 
    pp := PolarPoint{} 
    // ... 
    return pp 
} 
1
  1. Как правило, идиоматических способ заключается в использовании методов получения и установки. Менее удобно? Может быть. Но, по крайней мере, так оно и делается.
  2. Да. В этом суть утиной печати. Любой тип, соответствующий интерфейсу, будет принят без необходимости явно реализовать. EDIT: В комментариях к этому ответу я неверно истолковал этот вопрос. Ответ - нет, вам нужно будет добавить методы для этих структур, чтобы они соответствовали интерфейсу, отличному от interface{}.
  3. Да, используя геттеры и сеттеры.
  4. Возможно. Я вижу, почему геттеры и сеттеры могут восприниматься как менее удобные. Насколько я могу судить, они строго не ограничивают то, что вы можете сделать.
  5. Да. Так я видел это в коде других и в стандартных библиотеках.
+0

В ответ на (2), как бы вы это сделали, не изменяя «CoordinatePoint» и «CartesianPoint»? Я понимаю, что утиная типизация и как это будет сделано. Если структуры уже определены с точки зрения методов getter/setter, но я хотел бы использовать две структуры: _as is_. – Syllepsis

+0

Приношу свои извинения, я тогда неправильно истолковал это.Я взял (2), чтобы означать «не делая что-то явно для реализации интерфейса». Поэтому ответ «нет»; вам придется добавить какой-то метод. Посмотрите на решение @ dystroy. Это очень чисто. – burfl

 Смежные вопросы

  • Нет связанных вопросов^_^