Пользователь
0,0
рейтинг
12 июня 2014 в 10:06

Разработка → Является ли Go языком ООП? перевод

Object-oriented design is the roman numerals of computing.
— Rob Pike, автор Go.

image

Предлагаю вашему вниманию вольный перевод заметки «Is Go An Object Oriented Language?» за авторством Steve Francia, в которой автор наглядно рассказывает об особенностях использования парадигмы ООП в Go. Сразу предупреждаю, что из-за свойств оригинального материала большую часть текста пришлось переформулировать полностью, где-то добавить своего. Флажок перевода убирать не стал.

1. Введение


Так что же это значит, быть «объектно-ориентированным»? Обратимся к истории возникновения концепта ООП, попробуем разобраться.
Первый объектно-ориентированный язык, Simula, появился на горизонте в 60-x годах. Он привнёс понятия объектов, классов, понятия наследования и классов-потомков, виртуальных методов, сопрограмм и многое другое. Походу, самым ценным вкладом стала парадигма абстракции данных.

Вы можете быть не знакомы со Simula, но, вне всяких сомнений, точно знаете некоторые из тех языков, для которых он стал вдохновением — Java, C++, C# и Smalltalk, которые позже, в свою очередь, сильно повлияли на Objective-C, Python, Ruby, Javascript, Scala, PHP, Perl… полный перечень содержит почти все популярные современные языки. Эта парадигма настолько прочно вошла в нашу жизнь, что большинство современных программистов никогда и не думали иначе.

Поскольку общепринятого определения ООП не существует, для продолжения дискуссии мы сформулируем своё.

Вместо явного разделения кода и данных в тексте программы, объектно-ориентированная система объединяет их используя концепцию «объекта». Объектом называется абстрактный тип данных, включающий состояние (данные) и поведение (код).

Поскольку изначальная реализация имела механизмы наследования и полиморфизма, которые были переняты практически во всех производных языках, определения объектно-ориентированного программирования обычно включают их как необходимое условие.

Далее мы рассмотрим, как в Go используются объекты, полиморфизм и наследование, после чего будет проще ответить на поставленный вопрос.

2. Объекты?


В Go нет ничего с именем object, хотя это и не важно. Пусть тип object не встречается, зато есть тип, попадающий под определение объектно-ориентированного подхода — структуры данных, включающие и состояние и поведение, они обозначаются как struct. struct это тип, содержащий именованные поля и методы.

Для наглядности приведу пример:
type rect struct {
    width int
    height int
}

func (r *rect) area() int {
    return r.width * r.height
}

func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("area: ", r.area())
}

Первый блок определяет новый тип rect структурного типа, содержащего 2 целочисленных поля. Следующий блок определяет метод на этой структуре путём определения функции area и прикрепления её к типу rect. Точнее, на самом деле функция прикрепляется к типу-указателю на rect.
Последний блок является точкой входа программы, это функция main. Первая строка создаёт новый экземпляр rect (выбранный способ создания экземпляра — через составной литерал — является наиболее удобным в Go). Вторая строчка отвечает за вывод результатов вызова функции area на значении r.

Лично мне это всё сильно напомнило работу с объектами. Я могу создать тип структурированных данных и определить методы для работы с некоторыми из них.
Чего-то ещё не хватает? Да, в большинстве объектно-ориентированных языков для описания объектов используются классы с поддержкой наследования, причём хорошей практикой считается определение интерфейсов для этих классов и тем самым определение дерева иерархии классов (в случае простого наследования).

2. Наследование и полиморфизм


Существует несколько различных подходов к определению отношений между объектами. И хотя имеются некоторые различия, все они используются практически с одной целью — ради повторного использования кода.

2.1. Простое и множественное наследование
Наследование это механизм языка, позволяющий описать новый класс на основе уже существующего (базового) класса. Существует две разновидности наследования, на основании количества базовых классов. Принципиальную разницу можно ощутить лишь оценивая последствия применения множественного наследования: иерархия простого наследования (single inheritance) представляет собой дерево, в то время как множественное наследование порождает решётку. Языки с поддержкой исключительно простого включают PHP, C#, Java и Ruby, а к языкам с поддержкой множественного наследования относятся Perl, Python и C++.

2.2. Полиморфизм подтипов
В некоторых языках понятия подтипов и наследования так тесно переплетены, что разница между ними едва заметна. На самом деле подтипы определяют семантические отношения между двумя и более объектами, тем самым образуя отношения is-a. То есть, тип A является подтипом B тогда, когда спецификация A следует из спецификации B и любой удовлетворяющий спецификации A объект (или класс) также удовлетворяет спецификации B. В то время как простое наследование только повторно использует реализацию, тем самым обеспечивая синтаксический сахар, но не более.

Следует чётко различать наследование через реализацию и «наследование» через полиморфизм подтипов, что будет понятно из текста далее.

2.3. Композиция
При композиции один объект определяется путём включения в него других объектов, то есть вместо наследования он просто содержит их. Такой тип взаимосвязи называется has-a и включаемые объекты подчиняются правилам принадлежности.

3. Есть ли в Go наследование?


Go по задумке был спроектирован без наследования в обычном понимании этого слова. Вовсе не значит, что объекты (структуры) не имеют отношений, просто авторы языка выбрали иной механизм обозначения таковых. Для многих начинающих писать на Go это решение может показаться серьёзной недоработкой, однако, в действительности же, это одно из самых приятных свойств языка и оно решает много проблем, заодно закрывая споры вокруг наследования — возраст которых исчисляется десятками лет — раз и навсегда.

4. «Простое наследование лучше выкинуть»


Далее я приведу фрагмент из статьи на JavaWorld — «Why extends is evil»:
В книге банды четырёх о паттернах проектирования детально обсуждается замена наследования через реализацию (extends) на наследование через интерфейсы (implements).

Я однажды посетил сходку юзер группы Java, куда James Gosling (создатель Java) был приглашён делать доклад. Во время памятной сессии вопросов и ответов кто-то спросил его: «Если бы вы могли сделать Java заново, что бы вы изменили?». «Я бы выкинул классы», ответил он. После того как смех в зале утих, он объяснил, что настоящая проблема заключается не в классах по сути, а в наследовании через реализацию (отношение extends). Наследование же через интерфейсы (отношение implements) является предпочтительным, следует избегать наследование через реализацию там, где возможно.

5. Отношения объектов в Go


5.1. Композиция типов
Вместо обычного наследования в Go строго используется принцип композиции вместо наследования и отношения между структурами и интерфейсами строятся по принципам is-a и has-a. Используемый здесь механизм композиции объектов представляется встраиваемыми типами, так, Go позволяет встроить структуру в структуру, создавая при этом отношения типа has-a. Хорошим примером является связь между типами Person и Address в коде ниже:
type Person struct {
   Name string
   Address Address
}

type Address struct {
   Number string
   Street string
   City   string
   State  string
   Zip    string
}

func (p *Person) Talk() {
    fmt.Println("Hi, my name is", p.Name)
}

func (p *Person) Location() {
    fmt.Println("Im at", p.Address.Number, p.Address.Street, p.Address.City, p.Address.State, p.Address.Zip)
}

func main() {
    p := Person{Name: "Steve"}
    p.Address = Address{ Number: "13", Street: "Main" }
    p.Address.City = "Gotham"
    p.Address.State = "NY"
    p.Address.Zip = "01313"
    p.Talk()
    p.Location()
}

Результат:
>  Hi, my name is Steve
>  Im at 13 Main Gotham NY 01313

play.golang.org/p/5TVBDR7AYo

В этом примере важно то, что Address остаётся обособленной сущностью, находясь внутри Person. В функции main продемонстрировано, как можно присвоить p.Address объект адреса и, обращаясь к его полям через точку, проинициализировать его.

5.2. Имитация полиморфизма подтипов

Примечание автора. Первая версия данной статьи неверно объясняла реализацию отношения is-a через анонимные поля структуры. На деле же она лишь напоминает отношение is-a, поскольку включаемые методы и свойства становятся видимыми извне, как если бы они существовали во включающей структуре. Такой подход не является is-a по причинам, описанным далее, однако, в Go есть поддержка is-a и достигается она через интерфейсы. Текущая версия статьи ссылается на анонимные поля как на имитацию is-a, поскольку они похожи на механизм полиморфизма, но не более. //

Имитация отношения is-a работает подобным же образом. Пусть человек (Person) умеет говорить. Горожанин (Citizen) является человеком (Person), а значит также умеет говорить (Talk). Расширим предыдущий пример с учётом этого.
type Citizen struct {
   Country string
   Person // анонимное поле без имени
}

func (c *Citizen) Nationality() {
    fmt.Println(c.Name, "is a citizen of", c.Country)
}

func main() {
    c := Citizen{}
    c.Name = "Steve"
    c.Country = "America"
    c.Talk()
    c.Nationality()
}

Результат:
>  Hi, my name is Steve
>  Steve is a citizen of America

play.golang.org/p/eCEpLkQPR3

Мы наладили имитацию отношения is-a используя анонимное поле структуры, в данном случае поле Person (указывается только тип) у Citizen. Тип Citizen приобрёл все свойства и методы типа Person и имеет возможность дополнить или перекрыть некоторые из этих свойств и методов своими. Например, пусть городские жители будут отвечать немного иначе:
func (c *Citizen) Talk() {
    fmt.Println("Hello, my name is", c.Name, "and Im from", c.Country)
}

Результат:
>  Hello, my name is Steve and Im from America
>  Steve is a citizen of America

play.golang.org/p/jafbVPv5H9

Обратите внимание, что теперь в main вызывается метод *Citizen.Talk() вместо *Person.Talk().

6. Почему анонимные поля не дают полиморфизм


Имеются две причины.

6.1. Остаётся доступ к индивидуальным полям каждого из встроенных типов
Ну, на самом деле это не так уж и плохо, поскольку при множественном «наследовании» становится неочевидно, какой именно метод из родительских классов будет вызван. При использовании анонимного поля Go создаёт вспомогательное поле, дублируя имя типа, поэтому вы всегда сможете обратиться к индивидуальным методам всех анонимных полей, то есть базовых классов в нашей имитации наследовательного механизма. Пример:
func main() {
    c := Citizen{}
    c.Name = "Steve"
    c.Country = "America"
    c.Talk()         // <- Метод доступен
    c.Person.Talk()  // <- Также доступен
    c.Nationality()
}

Результат:
>  Hello, my name is Steve and Im from America
>  Hi, my name is Steve
>  Steve is a citizen of America


6.2. Тип-потомок не становится типом-предком
Если бы полиморфизм был настоящий, анонимное поле заставило бы включающий тип стать включаемым типом, но в Go и включаемый и включающий до конца остаются раздельными. Лучше один раз увидеть, приведём пример:
type A struct {
}

type B struct {
	A // B is-a A
}

func save(A) {
	// xxx
}

func main() {
	b := &B{}
	save(b) // OOOPS! b IS NOT A
}

Результат:
>  prog.go:17: cannot use b (type *B) as type A in function argument
>   [process exited with non-zero status]

play.golang.org/p/EmodogIiQU

Данный пример предложен в этом комментрии с Hacker News. Спасибо, Optymizer.

7. Настоящий полиморфизм подтипов


Интерфейсы в Go — штука довольно уникальная по своей природе. В данном разделе мы сосредоточимся на применении интерфейсов для реализации полиморфизма, что не является их первоочередной задачей.

Как я уже писал раньше, полиморфизм это отношение is-a. В Go каждый тип является обособленным и ничто не может замаскироваться под другой тип просто так, но зато оба типа могут подходить под один и тот же интерфейс. Интерфейсы можно передавать как параметры в функции и методы и это позволит нам установить настоящее отношение is-a между типами.

Факт соответствия типа интерфейсу проверяется автоматически, путём сравнения наборов требуемых интерфейсом методов и набора имеющихся методов у проверяемого типа. Иными словами, такое отношение можно описать как «если что-то может делать это, то оно может использоваться здесь», то есть имеет место быть принцип утиной типизации.

Возвращаясь к предыдущему примеру, добавим новую функцию SpeakTo и в main попробуем применить её поочерёдно к экземплярам Citizen и Person.
func SpeakTo(p *Person) {
    p.Talk()
}

func main() {
    p := &Person{Name: "Dave"}
    c := &Citizen{Person: Person{Name: "Steve"}, Country: "America"}

    SpeakTo(p)
    SpeakTo(c)
}

Результат:
>  Running it will result in
>  prog.go:48: cannot use c (type *Citizen) as type *Person in function argument
>  [process exited with non-zero status]

play.golang.org/p/fkKz0FkaEk

Как и предполагалось, конструкция просто неверна. В данном случае Citizen не является Person, даже не смотря на то, что у них общие свойства. Однако, добавим интерфейс Human, сделаем его принимаемым типом функции SpeakTo и вот, всё пошло по плану.
type Human interface {
    Talk()
}

func SpeakTo(h Human) {
    h.Talk()
}

func main() {
    p := &Person{Name: "Dave"}
    c := &Citizen{Person: Person{Name: "Steve"}, Country: "America"}

    SpeakTo(p)
    SpeakTo(c)
}

Результат:
>   Hi, my name is Dave
>   Hi, my name is Steve

play.golang.org/p/vs92w57c5-

Подытоживая, можно сделать два важных замечания про полиморфизм в Go:

  • Используя анонимные поля можно добиться соответствия типа интерфейсу, в том числе одновременного соответствия набору интерфейсов, тем самым достигая почти канонического полиморфизма.
  • Go может обеспечить полиморфизм подтипов во время использования значения (c.Name = "Xlab"), но в действительности же все типы являются обособленными. Как показано в последнем примере, чтобы установить свойство Name у Citizen, мы обязаны явно указать это свойство у Person, присвоив затем объект Person соответствующему полю у Citizen. (речь о составном литерале Citizen{Person: Person{Name: "Steve"}, Country: "America"}).

8. Итоги


Как вы могли наблюдать в примерах выше, фундаментальные принципы объектно-ориентированного подхода актуальны и успешно применяются в Go. Существуют отличия в терминологии, поскольку используются несколько иные механизмы, нежели в других классических языках ООП. Так, для объединения состояния и поведения в одной сущности используются структуры с заданными (прикреплёнными) методами. Для обозначения отношений has-a между типами используются композиция, тем самым минимизируя количество повторений в коде и избавляя нас от бардака с классическим наследованием. Для установления is-a отношений между типами в Go используются интерфейсы, без лишних слов и контрпродуктивных описаний.

Вот так, встречайте новую модель объектно-ориентированного программирования — без объектов!

~ translation & adaptation by Xlab.
См. также: «Повторное использование кода в Go на примере»
Кстати, на всякий случай приведу здесь эту цитату:
I invented the term Object-Oriented and I can tell you I did not have C++ in mind.
— Alan Kay
Перевод: Steve Francia
Максим Куприянов @Xlab
карма
22,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (27)

  • +6
    ИМХО, для ООП достаточно понятия object identity, и полиморфизма поведения (т.е. какой-либо механизм, при котором данный вызов может обрабатываться по-разному в зависимости от типа объекта). Т.е. например Go — несомненно объектно-ориентирован. Равно как и JS. И даже Lua.

    Что касается цитаты Алана Кэя… может, он и придумал термин «ООП», но Simula, которая по факту уже была вполне себе ООП, появилась за несколько лет до Smalltalk. А объектная модель плюсов, это дальнейшее развитие Simula (в т.ч. и синтаксис — «virtual» родом именно оттуда). Так что не думаю, что у Кэя есть монополия на это дело.
    • +2
      Насколько я помню, в Simula 67 слово virtual означало лишь возможность перегрузки метода в наследниках, но полиморфизма не было: при вызове нужно было явно указать, метод какого именно класса использовать в данный момент. Сейчас не имею возможности перепроверить, но если так, то, следуя вашему же определению (и я с ним согласен), Simula по факту не был объектным. Так что монополия Алана Кэя сохраняется :)
      • 0
        «Частичный» пруфлинк: The Birth of Object Orientation: the Simula Languages by Ole-Johan Dahl. Автор называет механизм виртуальных вызовов «полудинамическим». Объяснение довольно туманное. Раньше где-то видел более наглядный пример (где явно показывалась необходимость точно указывать правильный класс объекта для правильного вызова виртуального метода), но сейчас не могу найти :(
        • 0
          В Википедии есть конкретный пример про virtual, где он используется ровно так, как в C++. Но вообще, конечно, это надо смотреть в спеке. Но там не текст, а отсканированные картинки, плюс терминология очень запутанная, так что я пока так и не смог разобраться.

          Если судить по этой книге (по которой я, собственно, в свое время и изучал Симулу), необходимость указывать класс объекта через QUA — это не связанное с виртуальными функциями ограничение оператора точка, которое обходится использованием INSPECT.
        • 0
          Поигрался с Cim — там честные виртуальные вызовы, т.е. например:

          begin
              class Base;
              virtual:
                  procedure Foo is
                      procedure Foo;
                  ;
              begin
                  procedure Foo;
                  begin
                      outtext("Base");
                  end;
              end;
          
              Base class Derived;
              begin
                  procedure Foo;
                  begin
                      outtext("Derived");
                  end;
              end;
          
              ref(Base) obj;
              obj :- new Base;
              obj.Foo;
              obj :- new Derived;
              obj.Foo;
          end
          


          Печатает «Base Derived». Судя по этому, такое поведение ожидается.
  • +7
    Тоже мне, открыли секрет полишинеля. Объекты на Си так же делаются ;D
    • 0
      Я программирую и на Си и на Гоу, Си в отношении объектов до Гоу далековато.
  • +3
    Спасибо за перевод. Лично мне бы хотелось видеть больше о «Го» на хабре. Особенно что-то типа «Подборка интересностей за последнюю неделю»)
    • +1
      А была демо-подборка от человека, кто этим занимается: habrahabr.ru/post/221061/
      Он написал, что остальные подборки будут на отдельном сайте, увы.
    • 0
      4gophers.com/ Там переодически появляются статьи «тулзовины и хреновины #X», там уже 7 таких статей, и я для себя нашел очень много интересного, редко конечно, но полезного очень много. Можете пройтись по всем статьям, найдете для начала информации на очень долгий период.
      • 0
        спасибо за ссылку. от себя могу добавить рассылку «go weekly».
  • 0
    Go по своему прекрасен, но есть очень спорный момент, обработка ошибок…
    • 0
      Ну, ошибки обрабатывать надо, так что сколько ошибок — столько и обработчиков. Во многих других языках можно плевать на них, вы можете плевать и здесь, но так дела не делаются. Наилучший вариант — пробрасывать ошибки снизу вверх через return err, как можно выше. А что кроме этого вас не устраивает?
      • 0
        отписался ниже
    • +1
      А что не так с обработчиком ошибок?
      Опишите кейс, который вызвал трудности. Не считаю себя go-гуру, но пара демонов в продакшне имеется, последний достаточно жирный по коду.
      • 0
        с обработчиком ошибок все так

        иногда глаза рябит от количества err которые нужно проверить в коде

        	if err != nil {
        		log.Fatal(err)
        	}
        	//или такого
        	if err != nil {
        		return nil, err
        	}
        

        привычнее конструкция
        try{

        }catch

        но как говорится все это дело привычки
        • 0
          Попробуйте использовать механизм panic — recover. Практически тот-же try — catch, только привязанный к границам функций.
          • 0
            В Go panic это для непредвиденных разработчиком исключительных ситуаций, которых просто не должно быть, типа обращения к null. Если мешать его с механизмом предвиденных исключительных ситуаций (error), получится хрень.
            У нас на первом курсе люди в C# оборачивали Main в try-catch и были довольны стабильностью, но так дела не делаются. В любом случае, при правильном подходе будет рябить либо от проверок err, либо от проверок try-catch.
          • +1
            Так точно делать не нужно.
            Вы можете написать что-то типа:
            Control.try(func(){
                // code
                _, err := my.func(); Control.handle(err);
                // code
            }).cath(func(message err){
                // handle
            })
            


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

            Паника нужна тогда, когда действительно паника. Мы не можем дальше работать, мы не знаем, что делать — всё пропало.
            • 0
              Всем надо пользоваться с умом. Есть соглашение, об этом написано в статье Effective Go — Errors, что сигнатуры библиотечных методов и функций должны соответствовать определенному формату, а именно, возвращать error, если может иметь место исключительная ситуация. Но внутри кода своей библиотеки вы можете использовать и механизм panic — recover, когда это оправдано (даже для обработки предвиденных ошибок).
              Например есть цепочка вызовов функций и исключительная ситуация может возникать где-нибудь на глубине пятого уровня. Чтобы не пробрасывать вручную и не писать в каждой функции проверки типа if err {return err}, уместно использовать panic и просто отловить проброшенное исключение на верхнем уровне. Почему это будет дорого? Ведь .NET и Java используют именно такую модель обработки ошибок. А каком геморрое вы говорите? И почему это будет не Go-way? Ведь если существуют подобные механизмы языка, значит ими можно пользоваться, но и это не значит что их следует пихать везде, где вздумается. Более того, такой способ используют сами разработчики, например в пакете regexp, вот здесь кратко описано что, где и зачем: Effective Go — Recover
              • 0
                Никто не утверждал, что в особых ситуациях глубоко внутри пакета НЕЛЬЗЯ сделать panic и затем где-нибудь вверху его поймать. Конечно же можно, только вот ловля ошибок с этим никак не связана. Попробуйте создать папку и файл, записать в файл текст — вы должны отловить 4 возможные ошибки, что заставит скопипастить любой код ловли ошибок, каким бы он не был.
                Почему это будет дорого?
                Товарищ привёл пример кода и о нём говорил, что дорого и муторно.

                А что касается предназначения panic, об этом явно написано в том же Effective Go:
                But what if the error is unrecoverable? Sometimes the program simply cannot continue.
                This is only an example but real library functions should avoid panic. If the problem can be masked or worked around, it's always better to let things continue to run rather than taking down the whole program.
              • +1
                Почему это будет дорого? Ведь .NET и Java используют именно такую модель обработки ошибок.

                Go не JAVA и не .NET. Предполагается, что модель Go в области обработки ошибок это эволюция. Есть механизм panic, есть механизм Error.
                Исключения в Go не нужны. И нужно очень извратить свой мозг, чтобы они появились(типа той реализации, что я привел).

                О каком геморрое вы говорите?

                Есть такая ситуация, которая ведет к боли обычно — попытка перенести привычный опыт на всё новое. Go не про огромные системы с DI, не про абстрактные классы и кучи фабрик. Он про решения больших задач маленькими пакетами. Во-всяком случае я в этом уверен.
                И маленькие пакеты при вызове своих функций могут вернуть ошибку.

                И почему это будет не Go-way?

                Вы можете посмотреть на случаи использования panic в стандартной библиотеке.

                У меня на работе демон с 30k строк только моего кода крутится, написанный на Go и я не испытал трудностей или проблем с обработкой ошибок. Паника используется тогда, когда мы не можем дальше продолжать работать и это правильно.
                Я бы вообще порекомендовал попридежать panic на время первичного знакомства с языком.

                По-поводу дорого — я думаю написать статью на недельке по-поводу ошибок, почему они именно так сделаны и про panic-recovery тоже. С бенчмарками. И примерами из стандартной библиотеки.

                Возможно, я немного резок, но это потому, что не хочется, чтобы перековали приятный и умный язык на какую-нибудь JAVA.
        • +1
          По-поводу return val,val,val могу посоветовать только использовать именованные возвращаемые параметры.
          Не забывайте только о разнице между := и =, хотя по-моему новые версии компиляторов уже сообщают о ошибках типа %var% is shadowed during return

          Или можно так:

          func foo() (err error) {
          	var v bool
          	if v1, err = baz(); err == nil {
          		if v, err = bar(); err == nil {
          			println("value is", v, v1)
          		}
          	}
          	return
          }
          


          Правда мне кажется, что такие кульбиты выглядят похуже проверки возвращаемого err на nil.

  • НЛО прилетело и опубликовало эту надпись здесь
    • +2
      Вообще концепт анонимных полей используется для встраивания структур и с си-подобными языками это никак не связано. Здесь походу требуется некоторое понимание концептов Go.
      Другое дело, что при использовании встраивания мы добиваемся того, что напоминает си-подобное наследование, но не более. Об этом и есть статья, кстати.

      «удалил его имя, а про тип забыл» — ну знаете ли, в программе можно много чего удалить, а чего-то забыть, лучше от этого никому не станет ни на каком-либо другом языке. И нет ничего плохого, если класс вдруг станет наследником, это же не полноценное наследование, он просто приобретёт новые свойства/методы и всё.

      «но не превратиться ли код на нем через пару лет в спагетти» — любой код превратится в хрень, если забывать что-то там удалять. Другое дело, что подходы Go меньше всего способствуют образованию хренового кода, я бы сказал даже препятствуют.
  • +1
    Вы можете быть не знакомы со Simula, но, вне всяких сомнений, точно знаете некоторые из тех языков, для которых он стал вдохновением — Java, C++, C# и Smalltalk, которые позже, в свою очередь, сильно повлияли на Objective-C, Python, Ruby, Javascript, Scala, PHP, Perl… полный перечень содержит почти все популярные современные языки.

    Перечень языков для которых он встал вдохновением — крайне странен. Откуда в списке взялись Java ('95), C# ('00)? Уж явно не у Simula черпалось вдохновение, т.к. череда событий перед между '67 и '95 уж точно была не маленькая. А дальше вообще смешно, «позже» и «сильно повлияли на» — Obj-C ('83, рядом с C++, кстати), Perl ('87), Python ('91). Даже если «повлияли» имелось в виду на развитие в процессе, то, опять же, ну уж никак не Simul'ой в головы поэтичных япошек и минималистичных американцев приходили их идеи. Клевая статья, но этот абзац просто выбил из колеи, честное слово.
    • +1
      Симула впервые появилась в 67-м, но она тоже развивалась и стандартизировалась. Последняя спецификация датирована 86-м.

      Но, да, список все равно странноватый. C# там точно не к месту — это дитя Java и Delphi.

      Реально Симула была прямым вдохновлением для C++, причем это вполне достоверно — помимо вполне очевидных синтаксических заимствований, Бьёрн об этом говорил открыто уже много раз, и в C++ FAQ есть соответствующий пункт.

      Насколько она был прямым вдохновением для Java, я не знаю, но факт то, что объектная модель последней намного ближе к оригинальной симуловской (классы исключительно как reference-типы, одиночное наследование), чем плюсовая. А Гослинг в свое время работал над одним из популярных компиляторов Симулы, и высказался по этому поводу так: «Simula pushed my love for object oriented programming. Simula was pure and simple. It was really a lovely language to use». И вроде бы как-то раз на JavaOne он обмолвился, что она оказала сильное влияние на дизайн языка.

      Про «повлияли» — видимо, имелось в виду, что повлияли C++/C#/Smalltalk/Java, причем в каждом конкретном случае имеют смысл только некоторые комбинации — т.е., например, Obj-C явный наследник Smalltalk.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.