| |||
|
|
goбъектное В go, как я уже грил, оригинальных концепций мало - в основном это довольно удачная комбинация хорошо известных идей (хотя многие вещи я бы сделал по другому). Что довольно понятно, если учесть, что команда разработчиков восходит к авторам Unix (Кен Томпсон там во плоти, Ричи и прочие присутствую духовно - чрез язык Limbo*, от которого в некотором роде унаследован Go). Одна из двух оригинальных (вторая - defer), в смысле мне ранее не встречавшихся, - это их подход к объектности. Сама по себе идея, что VMT или ее аналог должна ходить вместе с указателем, а не лежать внутри объекта довольно очевидна, мне по крайней мере она пришла в голову почти сразу, когда в начале 90-х я впервые задумался на тему привнесения в ООП какой-то внятной семантики. Проблема тут в реализации. И вот пожалуй в Go она решена c моей точки зрения правильно (есть шероховатости, но концепция в целом не вызывает ощущения кривизны). В чем задача: придумать сравнительно низкоуровневую модель, в которой с одной стороны наследование и прямое и множественное будут выразимы, причем в различных вариантах, и будут выразимы без извращений всем знакомым по С++. Второе - должен быть выдержан принцип - "платишь только за то, чем пользуешься" - то есть вспомогательные структуры типа vmt и косвенности не должны отягощать программу там, где они не нужны. Вышло на удивление просто: в два приема: Есть понятие структуры. В самом обычном смысле. В структуре кроме обычных полей могут быть "анонимные" - описываемые просто указанием типа, кой должен быть либо структурой, либо примитивным типом, либо указателем на оные. Анонимные поля получают имена своих типов, а их компоненты и методы могут быть доступны напрямую. Есть естественное требование уникальности имен анонимных полей внутри структуры: import "fmt" import . "math" type Point2D struct { x, y float64 } func (p Point2D) abs () float64 { return Sqrt(p.x*p.y + p.y*p.y) } type Point3D struct { Point2D z float64 } func (p Point3D) abs () float64 { return Sqrt(Pow(p.Point2D.abs(),2) + p.z*p.z) } func main() { p := Point3D {Point2D {1, 2}, 3} p.x += 10 fmt.Println(p.abs ()) fmt.Println(p.Point2D.abs ()) } (если убрать описание метода abs (Point3D) программа будет работать как и можно ожидать. Особо обращаю внимание, что это само по себе не объектность: Методы - это не то, чтобы методы, это просто такие особенные функции. И Point3D не является подтипом Point2D (хотя ничего не мешает авторам языка это ограничение снять - но это не нужно). Пока это просто синтаксис. А теперь собственно то, что требуется: type Point interface { abs () float64 } func printAbs (p Point) { fmt.Println(p.abs()); } func main() { p2 := Point2D {1, 2} p3 := Point3D {p2, 3} printAbs(p2) printAbs(p3) } Работает так, как и хочется. У интерфейсов, в отличие от структур, есть отношение тип-подтип просто по включению сигнатур. Как на этом выражаются все объектные извращения вплоть до виртуального множественного наследования, я думаю объяснять не надо. Особенно отмечу, что тут я с указателями не игрался, но они в этой конструкции являются довольно существенной деталью. Например "виртуальное наследование" в стиле С++ подразумевает как раз анонимное поле-указатель. Поскольку прописывать VMT в структуру не надо, в качестве конструктора может выступать обычная функция (единственная реально важная функция конструктора - именно инициализация VMT), а логику инициализации можно задать ту, которая нужна в данном случае, а не ту, которую навязывает компилятор. Простенько и со вкусом. И без всяких мистических сущностей и куч ad-hoc правил. PS: Поскольку я подозреваю, что народ возбудится - инкапсуляция делается на уровне межмодульных ограничений видимости. Как в Turbo Pascal. *) Я перечитал описание Limbo и понял, что мое давнее впечатление от него было верным - громоздко и малоинтересно. Отличия вроде бы мелкие, но результат совершенно не впечатляет. Добавить комментарий: |
||||||||||||||