Pull to refresh

Comments 43

Авторский стиль, я не более чем переводчик.
type Person struct {
        Name string
}

func (p *Person) Intro() string {
        return p.Name
}

type Woman struct {
        Person
}

func (w *Woman) Intro() string {
        return "Mrs. " + w.Person.Intro()
}

А что тут можно назвать наследованием?
Как-то очень все у вас мутно написано.
Тут тип Woman «наследуется» от типа Person и соответственно имеет поле Name и метод Intro, который затем «переопределяется». Статья не ставит целью разъяснить синтаксис Go, так что для совершенно не знакомого с языком человека действительно может быть мутновато.
Наследование типов это немного не то, о чем вас постоянно спрашивают.
Ну во-первых спрашивают не меня, а автора статьи, а во-вторых считаю что ответ на поставленный вопрос в статье содержится. Для полноты конечно стоило бы ещё рассказать об интерфейсах, но это уже другая тема.
Прошу прощения, не сразу увидел, что это перевод.
Композиция из Go (в варианте без звездочки) эквивалентна наследованию без виртуальных методов. Я не вижу различий между ними.
Вопрос терминологии, Go удаётся очень удачно мимикрировать под привычные термины и конструкции в мозгу программиста, хотя под капотом там используются отличные от привычных механизмы, знание которых позволяет делать очень интересные и удобные штуки. Об этом как раз и рассказывает статья.
В качестве примера, можно привести вот такое «двойное наследование»:
Код
type A struct {
    x int
}

func (self *A) F1() () {
	fmt.Println("I'm A: ", self.x)
    return
}

type B struct {
    y int
}

func (self *B) F2() () {
	fmt.Println("I'm B: ", self.y)
    return
}

type C struct {
    A
    B
    z int
}

func (self *C) F3() () {
	fmt.Println("I'm C: ", self.x, " and can call A and B: ")
    self.F1()
    self.F2()
    return
}


Лично мне неизвестно как такое может уложиться в привычные ООП рамки.
Множественное наследование же, или тут скрыто что-то сильно большее?

Пример на C++:

class A {
public:
  int x;
  void F1() {
    std::cout << "I'm A: " << x << std::endl;
  }
};

class B {
public:
  int y;
  void F2() {
    std::cout << "I'm B: " << y << std::endl;
  }
};

class C : public A, public B {
public:
  int z;

  void F3() {
     std::cout << "I'm C: " << x << " and can call A and B" << std::endl;
     F1();
     F2();
  }
};

Хм, тут я видимо погорячился. Предыдущий комментарий прошу считать недействительным.
Является ли Go объектно-ориентированным языком?


golang.org/doc/faq#Is_Go_an_object-oriented_language

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.
А это ООП?
data Animal = Animal {
  speak :: Int -> String, 
  move :: Int -> String,
  act :: Int -> String
}

base self = Animal {
   speak = \repeats -> "",
   move = \repeats -> "",
   act = \repeats -> (self  `move` repeats) ++ (self  `speak` repeats)
}

{- One type of animal -}
bird self = let super = base self in Animal {
   speak = \repeats -> concat $ replicate repeats "cheep ", 
   move = \repeats -> concat $ replicate repeats "flap ",
   act = (super `act`)
}

{- Another type of animal -}
dog self = let super = base self in Animal {
   speak = \repeats -> concat $ replicate repeats "bark ", 
   move = \repeats -> concat $ replicate repeats "run ",
   act = (super `act`)
}

new f = f (new f)

b = new bird
d = new dog

*Main> b `act` 2
"flap flap cheep cheep "

Взято отсюда. BTW интересное обсуждение.
Я думаю, что если рассматривать ООП с точки зрения трех основных понятий — инкапсуляция, наследование, полиморфизм, то Go с натяжкой, но все же можно отнести к ООП-языкам:

1. Инкапсуляция — объединение в одной структуре данных и функций, которые работают с этими данными — есть, описано в статье. Средства скрытия некоторых членов структур тоже есть (аналогично public/private; protected нет, но из-за отсутствия традиционного наследования этого никто не замечает :).

2. Наследование. Опять же описано в статье. Пусть данная функциональность реализована через композицию, а не через «традиционное» наследование, своей цели оно достигает — структура ведет себя так же, как ее «предок», расширяя функциональность «предка» своими методами и полями.

3. Полиморфизм. Опять-таки с натяжкой полиморфизм в Go есть, реализуется он через механизм «утиной» типизации (duck typing). Подход простой — если нам нужен интерфейс с функциями add(), del(), get() (для примера), то любая структура Go, у которой есть данные функции, поддерживает данный интерфейс. Таким образом, абсолютно неважен реальный тип данных, которых нам передают для обработки — главное, чтобы у него были нужные функции (нужный интерфейс). Полиморфизм с точки зрения независимости интерфейса от реальной реализации соблюден.

Лично мне подход Go импонирует. Он немного непривычен, но довольно интересен.
а какие у языка «Go» есть плюсы по сравнению с другими ЯП?
С одной стороны: есть указатели, но при этом есть сборка мусора. С другой стороны: есть указатели, но при этом нет арифметики с ними. Лично для меня это оказалось определяющей характеристикой языка, которая у меня вызвала диссонанс, и я никак не могу определиться со своим отношением к этому языку. Возможно, за последнее время что-то изменилось, поправьте меня, если так.
Так это же классно: наличие указателей дает определенное ускорение, где это критично, а отсутствие адресной арифметики и сборка мусора делает использование указателей безопасным.
Арифметика всё-таки есть, просто она не на виду.
В смысле, до неё вполне можно добраться, модуль специальный есть (причём, это не какое-то нововведение).
Э, ну так в классическом Паскале тоже были указатели без арифметики.
А в Обероне, якобы, и сборку мусора навернули.
Всё это лет десять назад как.
— компиляция в нативный код
— статическая типизация
— всякие «плюшки» из современных интерпретируемых языков: замыкания, слайсы, множественные возвращаемые из функции значения
Common Lisp. Всё это и ещё немного. :)
(и (ещё (немного (скобочек ;-)))))
Похоже, это действительно лучший из существующих языков на все случаи жизни, если, у кого ни спроси, все единственным недостатком видят только префиксный синтаксис, к которому нужно привыкнуть так же, как все мы в школе привыкали к инфиксному.

На самом деле я своим комментарием хотел сказать, что Го в сравнении с некоторыми «другими ЯП» по этим фичам опоздал чуть больше чем на двадцать лет. Вечность по меркам IT. Вы, кстати, ещё рестарты забыли, которые, да, тоже есть в Common Lisp (и Smalltalk). Более того, в этих двух, в отличие от Go, они являются стандартным и единственным способом обработки ошибок, за что авторам стандарта большое человеческое спасибо. Благодаря им существует хотя бы два языка с вменяемой системой обработки ошибок.

Второй обзац, в отличие от первого, троллоло не является. Весь. И да, я в курсе, что у Go немного другие решаемые задачи и существуют другие преимущества. Это ответ на конкретный комментарий.
Основной недостаток CL (с моей точки зрения) — разделение пространств имен функций и значений. В результате в программах мусорного слова funcall не многим меньше, чем скобочек.
Лечится, конечно, но осадочек остается :-).
Основная фишка go в _крайне простой_ параллелизации, засчёт каналов и go-рутин. Кроме этого, от прочих асинхронных языков и фреймворков go отличает не ограниченная многопоточность (например, node.js однопоточная), экономная работа с памятью (а не как scala), сравнительно быстрая общая работа (т.к. статически типизированный), и довольно быстрый сборщик мусора.

В целом, это системный язык со встроенным обеспечением параллельности и синхронизации между серверами, при этом с лаконичным синтаксисом и прочими плюшками (например, import прямо с гитхаба).
Да уж, scala по прожорливости — просто жесть! Сам столкнулся на практике, пришлось переписывать некоторые классы прямо на яве, чтобы не позорится ява стилем в scala. Согласен тут с авторами yammer. Но в малых дозах, как в playframework'е scala как бальзам на душу.
А можно по-подробней про синхронизацию между серверами?
Из плюшек языка, был netchannel (канал в терминах go, но по сети). До 1.0 он был непосредственно в package'ах языка, в ближайших релизах обещают вернуть. Я его руками делал в несколько десятков строк из gob'ов поверх обычного сетевого сокета.

В целом, сам язык способствует удобной и лаконичной межсерверной работе, засчёт многопоточной асинхронности и заточенности на потоковую обработку. В качестве примера есть прекрасный github.com/ha/doozerd — кластер наподобие zookeeper'а.
Имеется ещё chanio (по ссылке описывается реализация) от автора этой статьи который в некоторой степени повторяет функции netchan.
Смысл всей статьи:
в Go нет наследования, но есть агрегация
.
Агрегация есть везде. То есть можно остановиться на «в Go нет наследования». А раз нет наследования, то нет и полиморфизма. Итого только инкапсуляция.
Там прекрасный полиморфизм времени исполнения, основанный на структурной типизации.
Вот рабочий пример, основанный на примере из книги Ivo Balbaert — The way to Go.
package main

import (
	"fmt"
)
// квадрат
type Square struct {
	side float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

// круг
type Circle struct {
	radius float32
}

func (c Circle) Area() float32 {
	return 3.14159 * c.radius * c.radius
}

func main() {
	var areaIntf Shaper  // объект интерфейса

	// создаем объект Квадрат
	sq1 := new(Square)
	sq1.side = 5
	// присваиваем объект интерфейсу и вызываем полиморфно функцию через интерфейс
	areaIntf = sq1
	fmt.Printf("The square has area: %f\n", areaIntf.Area())

	// создаем объект Круг
	cr1 := new(Circle)
	cr1.radius = 5
	// присваиваем объект интерфейсу и вызываем полиморфно функцию через интерфейс
	areaIntf = cr1
	fmt.Printf("The circle has area: %f\n", areaIntf.Area())
}

Даже догадываюсь как это может быть устроено на низком уровне, если доберусь — надо будет Идой посмотреть ассемблерный код, проверить предположение:)
Как уже сказали, это не полиморфизм, это утиная типизация. Почти то же, но неявно. Хочешь-не хочешь, но если сигнатура совпала — ты реализуешь этот интерфейс.
Cобственно, сама возможность полиморфизма без наследования появилась с тем, что интерфейс отпочковался от абстрактного класса. То бишь не стоило мне говорить, что это взаимосвязано. Вообще полиморфизм действительно возможен без наследования.

И, раз уж появились пишущие на этом языке, вопрос — в семплах с# есть такой пример. Два интерфейса, метрический и английский. Если присвоить квадрат в переменную метрического типа, то Area() вернет значение в квадратных метрах, если в переменную английского — в квадратных футах. Здесь такое реализуемо?
Реализуемо через возврат интерфейса, но требует небольшой обвязки:
func Area(i  interface{}) interface{} {
 ...
}

func main() {
   out_value := Area(in_value)
    if value, ok := out_value.(MetricArea); ok {
        use_metric(value)
    }
    if value, ok := out_value.(ImperialArea); ok {
        use_imperial(value)
    }
}
Проблема: чтобы «унаследоваться» от класса, придется переопределить все объявленные в нем методы (я верно понимаю этот код?). И не дай бог потом набор методов в предке поменяется…
Нет, при инкапсуляции структуры в структуре во внешней доступны все методы внутренней.
play.golang.org/p/Qqw6v1oWNF

Мне кажется, go не стоит воспринимать как объектно-ориентированный язык — удобней просто работать со структурами, методами структур и интерфейсами — тогда всё логично, просто, легко, и всего хватает.
Весьма похоже на прототипы в Javascript.
Объекты в Objective-C устроены так же, первым полем структуры является поле Class isa; — указатель на супер класс.
Не совсем — там указатель не на «суперкласс» (базовый класс в терминах С++), а на «метакласс», то есть сам класс (тип данных) в ObjC является объектом, доступным в рантайме, на него и указывает isa. Это скорее рефлексия.
Но зато рантайм ObjC позволяет реализовать структурный полиморфизм, похожий на то, что я привел здесь.
Блин я дурень, описался, конечно же на класс объекта.
Sign up to leave a comment.

Articles