ООП в картинках

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



    Прежде всего стоит ответить, зачем? Объектно-ориентированная идеология разрабатывалась как попытка связать поведение сущности с её данными и спроецировать объекты реального мира и бизнес-процессов в программный код. Задумывалось, что такой код проще читать и понимать человеком, т. к. людям свойственно воспринимать окружающий мир как множество взаимодействующих между собой объектов, поддающихся определенной классификации. Удалось ли идеологам достичь цели, однозначно ответить сложно, но де-факто мы имеем массу проектов, в которых с программиста будут требовать ООП.

    Не следует думать, что ООП каким-то чудным образом ускорит написание программ, и ожидать ситуацию, когда жители Вилларибо уже выкатили ООП-проект в работу, а жители Виллабаджо все еще отмывают жирный спагетти-код. В большинстве случаев это не так, и время экономится не на стадии разработки, а на этапах поддержки (расширение, модификация, отладка и тестирование), то бишь в долгосрочной перспективе. Если вам требуется написать одноразовый скрипт, который не нуждается в последующей поддержке, то и ООП в этой задаче, вероятнее всего, не пригодится. Однако, значительную часть жизненного цикла большинства современных проектов составляют именно поддержка и расширение. Само по себе наличие ООП не делает вашу архитектуру безупречной, и может наоборот привести к излишним усложнениям.

    Иногда можно столкнуться с критикой в адрес быстродействия ООП-программ. Это правда, незначительный оверхед присутствует, но настолько незначительный, что в большинстве случаев им можно пренебречь в пользу преимуществ. Тем не менее, в узких местах, где в одном потоке должны создаваться или обрабатываться миллионы объектов в секунду, стоит как минимум пересмотреть необходимость ООП, ибо даже минимальный оверхед в таких количествах может ощутимо повлиять на производительность. Профилирование поможет вам зафиксировать разницу и принять решение. В остальных же случаях, скажем, где львиная доля быстродействия упирается в IO, отказ от объектов будет преждевременной оптимизацией.

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

    Классы и объекты


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

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

    Таким образом, класс — это описание того, какими свойствами и поведением будет обладать объект. А объект — это экземпляр с собственным состоянием этих свойств.

    Мы говорим «свойства и поведение», но звучит это как-то абстрактно и непонятно. Привычнее для программиста будет звучать так: «переменные и функции». На самом деле «свойства» — это такие же обычные переменные, просто они являются атрибутами какого-то объекта (их называют полями объекта). Аналогично «поведение» — это функции объекта (их называют методами), которые тоже являются атрибутами объекта. Разница между методом объекта и обычной функцией лишь в том, что метод имеет доступ к собственному состоянию через поля.

    Итого, имеем методы и свойства, которые являются атрибутами. Как работать с атрибутами? В большинстве ЯП оператор обращения к атрибуту — это точка (кроме PHP и Perl). Выглядит это примерно вот так (псевдокод):

    // объявление класса с помощью ключевого слова class
    class Transformer(){
        // объявление поля x
        int x
    
        // объявление метода конструктора (сюда нам чуть ниже передадут 0)
        function constructor(int x){
            // инициализация поля x 
            // (переданный конструктору 0 превращается в свойство объекта)
            this.x = x
        }
    	
        // объявление метода run
        function run(){
            // обращение к собственному атрибуту через this
            this.x += 1
        }
    }
    
    // а теперь клиентский код:
    
    // создаем новый экземпляр трансформера с начальной позицией 0
    optimus = new Transformer(0)
    
    optimus.run() // приказываем Оптимусу бежать
    print optimus.x // выведет 1
    optimus.run() // приказывает Оптимусу еще раз бежать
    print optimus.x // выведет 2
    

    В картинках я буду использовать такие обозначения:



    Я не стал использовать UML-диаграммы, посчитав их недостаточно наглядными, хоть и более гибкими.


    Анимация №1

    Что мы видим из кода?

    1. this — это специальная локальная переменная (внутри методов), которая позволяет объекту обращаться из своих методов к собственным атрибутам. Обращаю внимание, что только к собственным, то бишь, когда трансформер вызывает свой метод, либо меняет собственное состояние. Если снаружи обращение будет выглядеть так: optimus.x, то изнутри, если Оптимус захочет сам обратиться к своему полю x, в его методе обращение будет звучать так: this.x, то есть "я (Оптимус) обращаюсь к своему атрибуту x". В большинстве языков эта переменная называется this, но встречаются и исключения (например, self)

    2. constructor — это специальный метод, который автоматически вызывается при создании объекта. Конструктор может принимать любые аргументы, как и любой другой метод. В каждом языке конструктор обозначается своим именем. Где-то это специально зарезервированные имена типа __construct или __init__, а где-то имя конструктора должно совпадать с именем класса. Назначение конструкторов — произвести первоначальную инициализацию объекта, заполнить нужные поля.

    3. new — это ключевое слово, которое необходимо использовать для создания нового экземпляра какого-либо класса. В этот момент создается объект и вызывается конструктор. В нашем примере, конструктору передается 0 в качестве стартовой позиции трансформера (это и есть вышеупомянутая инициализация). Ключевое слово new в некоторых языках отсутствует, и конструктор вызывается автоматически при попытке вызвать класс как функцию, например так: Transformer().

    4. Методы constructor и run работают с внутренним состоянием, а во всем остальном не отличаются от обычных функций. Даже синтаксис объявления совпадает.

    5. Классы могут обладать методами, которым не нужно состояние и, как следствие, создание объекта. В этом случае метод делают статическим.

    SRP


    (Single Responsibility Principle / Принцип единственной ответственности / Первый принцип SOLID). С ним вы, наверняка, уже знакомы из других парадигм: «одна функция должна выполнять только одно законченное действие». Этот принцип справедлив и для классов: «Один класс должен отвечать за какую-то одну задачу». К сожалению с классами сложнее определить грань, которую нужно пересечь, чтобы принцип нарушался.

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

    Ассоциация


    Традиционно в полях объекта могут храниться не только обычные переменные стандартных типов, но и другие объекты. А эти объекты могут в свою очередь хранить какие-то другие объекты и так далее, образуя дерево (иногда граф) объектов. Это отношение называется ассоциацией.

    Предположим, что наш трансформер оборудован пушкой. Хотя нет, лучше двумя пушками. В каждой руке. Пушки одинаковые (принадлежат к одному классу, или, если будет угодно, выполненные по одному чертежу), обе одинаково умеют стрелять и перезаряжаться, но в каждой есть свое хранилище боеприпасов (собственное состояние). Как теперь это описать в ООП? С помощью ассоциации:

    class Gun(){ // объявляем класс Пушка
        int ammo_count // объявляем количество боеприпасов
    
        function constructor(){ // конструктор
            this.reload() // вызываем собственный метод "перезарядить"
        }
    
        function fire(){ // объявляем метод пушки "стрелять"
            this.ammo_count -= 1 // расходуем боеприпас из собственного магазина
        }
    
        function reload(){ // объявляем метод "перезарядить"
            this.ammo_count = 10 // забиваем собственный магазин боеприпасами
        }
    }
    
    class Transformer(){ // объявляем класс Трансформер
        Gun gun_left // объявляем поле "левая пушка" типа Пушка
        Gun gun_right // объявляем поле "правая пушка" тоже типа Пушка
        
        /*
        теперь конструктор Трансформера принимает
        в качестве аргументов две уже конкретные созданные пушки,
        которые передаются извне
        */
        function constructor(Gun gun_left, Gun gun_right){
            this.gun_left = gun_left // устанавливаем левую пушку на борт
            this.gun_right = gun_right // устанавливаем правую пушку на борт
        }
        
        // объявляем метод Трансформер "стрелять", который сначала стреляет...
        function fire(){
            // левой пушкой, вызывая ее метод "стрелять"
            this.gun_left.fire()
            // а затем правой пушкой, вызывая такой же метод "стрелять"
            this.gun_right.fire()
        }
    }
    
    gun1 = new Gun() // создаем первую пушку
    gun2 = new Gun() // создаем вторую пушку
    optimus = new Transformer(gun1, gun2) // создаем трансформера, передавая ему обе пушки
    


    Анимация №2

    this.gun_left.fire() и this.gun_right.fire() — это обращения к дочерним объектам, которые происходят так же через точки. По первой точке мы обращаемся к атрибуту себя (this.gun_right), получая объект пушки, а по второй точке обращаемся к методу объекта пушки (this.gun_right.fire()).

    Итог: робота сделали, табельное оружие выдали, теперь разберемся, что тут происходит. В данном коде один объект стал составной частью другого объекта. Это и есть ассоциация. Она в свою очередь бывает двух видов:

    1. Композиция — случай, когда на фабрике трансформеров, собирая Оптимуса, обе пушки ему намертво приколачивают к рукам гвоздями, и после смерти Оптимуса, пушки умирают вместе с ним. Другими словами, жизненный цикл дочернего объекта совпадает с жизненным циклом родительского.

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

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



    Наследование


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

    Что если, мы не хотим штамповать одинаковых трансформеров, а хотим сделать общий каркас, но с разным обвесом? ООП позволяет нам такую шалость путем разделения логики на сходства и различия с последующим выносом сходств в родительский класс, а различий в классы-потомки. Как это выглядит?

    Оптимус Прайм и Мегатрон — оба трансформеры, но один является автоботом, а второй десептиконом. Допустим, что различия между автоботами и десептиконами будут заключаться только в том, что автоботы трансформируются в автомобили, а десептиконы — в авиацию. Все остальные свойства и поведение не будут иметь никакой разницы. В таком случае можно спроектировать систему наследования так: общие черты (бег, стрельба) будут описаны в базовом классе «Трансформер», а различия (трансформация) в двух дочерних классах «Автобот» и «Десептикон».

    class Transformer(){ // базовый класс
        function run(){
            // код, отвечающий за бег
        }
        function fire(){
            // код, отвечающий за стрельбу
        }
    }
    
    class Autobot(Transformer){ // дочерний класс, наследование от Transformer
        function transform(){
            // код, отвечающий за трансформацию в автомобиль
        }
    }
    
    class Decepticon(Transformer){ // дочерний класс, наследование от Transformer
        function transform(){
            // код, отвечающий за трансформацию в самолет
        }
    }
    
    optimus = new Autobot()
    megatron = new Decepticon()
    
    


    Анимация №3

    Сей пример наглядно иллюстрирует, как наследование становится одним из способов дедуплицировать код (DRY-принцип) с помощью родительского класса, и одновременно предоставляет возможности для мутации в классах-потомках.

    Перегрузка


    Если же в классе-потомке переопределить уже существующий метод в классе-родителе, то сработает перегрузка. Это позволяет не дополнять поведение родительского класса, а модифицировать. В момент вызова метода или обращения к полю объекта, поиск атрибута происходит от потомка к самому корню — родителю. То есть, если у автобота вызвать метод fire(), сначала поиск метода производится в классе-потомке — Autobot, а поскольку его там нет, поиск поднимается на ступень выше — в класс Transformer, где и будет обнаружен и вызван.

    Неуместное применение


    Любопытно, что чрезмерно глубокая иерархия наследования может привести к обратному эффекту — усложнению при попытке разобраться, кто от кого наследуется, и какой метод в каком случае вызывается. К тому же, не все архитектурные требования можно реализовать с помощью наследования. Поэтому применять наследование следует без фанатизма. Существуют рекомендации, призывающие предпочитать композицию наследованию там, где это уместно. Любая критика наследования, которую я встречал, подкрепляется неудачными примерами, когда наследование используется в качестве золотого молотка. Но это совершенно не означает, что наследование в принципе всегда вредит. Мой нарколог говорил, что первый шаг — это признать, что у тебя зависимость от наследования.

    Как при описании отношений двух сущностей определить, когда уместно наследование, а когда — композиция? Можно воспользоваться популярной шпаргалкой: спросите себя, сущность А является сущностью Б? Если да, то скорее всего, тут подойдет наследование. Если же сущность А является частью сущности Б, то наш выбор — композиция.

    Применительно к нашей ситуации это будет звучать так:

    1. Автобот является Трансформером? Да, значит выбираем наследование.
    2. Пушка является частью Трансформера? Да, значит — композиция.

    Для самопроверки попробуйте обратную комбинацию, получится фигня. Эта шпаргалка помогает в большинстве случаев, но бывают и другие факторы, на которые стоит опираться при выборе между композицией и наследованием. Кроме того, эти методы можно комбинировать для решения разного типа задач.

    Наследование статично


    Еще одно важное отличие наследования от композиции в том, что наследование имеет статическую природу и устанавливает отношения классов только на этапе интерпретации/компиляции. Композиция же, как мы видели в примерах, позволяет менять отношение сущностей на лету прямо в рантайме — иногда это очень важно, поэтому об этом нужно помнить при выборе отношений (если конечно нет желания использовать метапрограммирование).

    Множественное наследование


    Мы рассмотрели ситуацию, когда два класса унаследованы от общего потомка. Но в некоторых языках можно сделать и наоборот — унаследовать один класс от двух и более родителей, объединив их свойства и поведение. Возможность наследоваться от нескольких классов вместо одного — это множественное наследование.



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

    Абстрактные классы


    Кроме обычных классов в некоторых языках существуют абстрактные классы. От обычных классов они отличаются тем, что нельзя создать объект такого класса. Зачем же нужен такой класс, спросит читатель? Он нужен для того, чтобы от него могли наследоваться потомки — обычные классы, объекты которых уже можно создавать.

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

    Полиморфизм


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

    Положим, у нас есть три трансформера: Оптимус, Мегатрон и Олег. Трансформеры боевые, стало быть обладают методом attack(). Игрок, нажимая у себя на джойстике кнопку «воевать», сообщает игре, чтобы та вызвала метод attack() у трансформера, за которого играет игрок. Но поскольку трансформеры разные, а игра интересная, каждый из них будет атаковать каким-то своим способом. Скажем, Оптимус — объект класса Автобот, а Автоботы снабжаются пушками с плутониевыми боеголовками (да не прогневаются фанаты трансформеров). Мегатрон — Десептикон, и стреляет из плазменной пушки. Олег — басист, и он обзывается. А в чем польза?

    Польза полиморфизма в данном примере заключается в том, что код игры ничего не знает о реализации его просьбы, кто как должен атаковать, его задача просто вызвать метод attack(), сигнатура которого одинакова для всех классов персонажей. Это позволяет добавлять новые классы персонажей, или менять методы существующих, не меняя код игры. Это удобно.

    Инкапсуляция


    Инкапсуляция — это контроль доступа к полям и методам объекта. Под контролем доступа подразумевается не только можно/неможно, но и различные валидации, подгрузки, вычисления и прочее динамическое поведение.

    Во многих языках частью инкапсуляции является сокрытие данных. Для этого существуют модификаторы доступа (опишем те, которые есть почти во всех ООП языках):

    • publiс — к атрибуту может получить доступ любой желающий
    • private — к атрибуту могут обращаться только методы данного класса
    • protected — то же, что и private, только доступ получают и наследники класса в том числе

    class Transformer(){
        public function constructor(){ }
    
        protected function setup(){ }
    
        private function dance(){ }
    }
    

    Как правильно выбрать модификатор доступа? В простейшем случае так: если метод должен быть доступен внешнему коду, выбираем public. В противном случае — private. Если есть наследование, то может потребоваться protected в случае, когда метод не должен вызываться снаружи, но должен вызываться потомками.

    Аксессоры (геттеры и сеттеры)


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

    В некоторых языках есть синтаксический сахар, позволяющий такие аксессоры маскировать под свойства, что делает доступ прозрачным для внешнего кода, который и не подозревает, что работает не с полем, а с методом, у которого под капотом выполняется SQL-запрос или чтение из файла. Так достигается абстракция и прозрачность.

    Интерфейсы


    Задача интерфейса — снизить уровень зависимости сущностей друг от друга, добавив больше абстракции.

    Не во всех языках присутствует этот механизм, но в ООП языках со статической типизацией без них было бы совсем худо. Выше мы рассматривали абстрактные классы, затрагивая тему контрактов, обязующих имплементировать какие-то абстрактные методы. Так вот интерфейс очень смахивает на абстрактный класс, но является не классом, а просто пустышкой с перечислением абстрактных методов (без имплементации). Другими словами, интерфейс имеет декларативную природу, то есть, чистый контракт без капельки кода.

    Обычно в языках, в которых есть интерфейсы, нет множественного наследования классов, но есть множественное наследование интерфейсов. Это позволяет классу перечислить интерфейсы, которые он обязуется имплементировать.

    Классы с интерфейсами состоят в отношении «многие ко многим»: один класс может имплементировать множество интерфейсов, и каждый интерфейс, в свою очередь, может имплементироваться многими классами.

    У интерфейса двустороннее применение:

    1. По одну сторону интерфейса — классы, имплементирующие данный интерфейс.
    2. По другую сторону — потребители, которые используют этот интерфейс в качестве описания типа данных, с которым они (потребители) работают.

    Например, если какой-то объект помимо основного поведения, может быть сериализован, то пускай он имплементирует интерфейс «Сериализуемый». А если объект можно склонировать, то пусть он имплементирует еще один интерфейс — «Клонируемый». И если у нас есть какой-то транспортный модуль, который передает объекты по сети, он будет принимать любые объекты, имплементирующие интерфейс «Сериализуемый».

    Представим, что каркас трансформера оборудован тремя слотами: слот для оружия, для генератора энергии и для какого-нибудь сканера. Эти слоты обладают определенными интерфейсами: в каждый слот можно установить только подходящее оборудование. В слот для оружия можно установить ракетную установку или лазерную пушку, в слот для генератора энергии — ядерный реактор или РИТЭГ (радиоизотопный термоэлектрический генератор), а в слот для сканера — радар или лидар. Суть в том, что каждый слот имеет универсальный интерфейс подключения, а уже конкретные устройства должны соответствовать этому интерфейсу. К примеру, на материнских платах используется несколько типов слотов: слот для процессора позволяет подключать различные процессоры, подходящие под данный сокет, а слот SATA — любой SSD или HDD накопитель или даже CD/DVD.

    Обращаю внимание, что получившаяся система слотов у трансформеров — это пример использования композиции. Если же оборудование в слотах будет сменным в ходе жизни трансформера, то тогда это уже агрегация. Для наглядности, мы будем называть интерфейсы, как принято в некоторых языках, добавляя заглавную «И» перед именем: IWeapon, IEnergyGenerator, IScanner.

    // описания интерфейсов:
    
    interface IWeapon{
        function fire() {} // декларация метода без имплементации. Ниже аналогично
    }
    
    interface IEnergyGenerator{
        // тут уже два метода, которые должны будут реализовать классы:
        function generate_energy() {} // первый
        function load_fuel() {}       // второй
    }
    
    interface IScanner{
        function scan() {}
    }
    
    
    // классы, реализующие интерфейсы:
    
    class RocketLauncher() : IWeapon
    {
        function fire(){
            // имплементация запуска ракеты
        }
    }
    
    class LaserGun() : IWeapon
    {
        function fire(){
            // имплементация выстрела лазером
        }
    }
    
    class NuclearReactor() : IEnergyGenerator
    {
        function generate_energy(){
            // имплементация генерации энергии ядерным реактором
        }
    	
        function load_fuel(){
            // имплементация загрузки урановых стержней
        }
    }
    
    class RITEG() : IEnergyGenerator
    {
        function generate_energy(){
            // имплементация генерации энергии РИТЭГ
        }
    	
        function load_fuel(){
            // имплементация загрузки РИТЭГ-пеллет
        }
    }
    
    class Radar() : IScanner
    {
        function scan(){
            // имплементация использования радиолокации
        }	
    }
    
    class Lidar() : IScanner
    {
        function scan(){
            // имплементация использования оптической локации
        }
    }
    
    // класс - потребитель:
    
    class Transformer() {
        // привет, композиция:
        IWeapon slot_weapon   // Интерфейсы указаны в качестве типов данных.
        IEnergyGenerator slot_energy_generator // Они могут принимать любые объекты,
        IScanner slot_scanner // которые имплементируют указанный интерфейс
    	
        /*
        в параметрах методов интерфейс тоже указан как тип данных,
        метод может принимать объект любого класса,
        имплементирующий данный интерфейс:
        */
        function install_weapon(IWeapon weapon){ 
            this.slot_weapon = weapon
        }
    	
        function install_energy_generator(IEnergyGenerator energy_generator){
            this.slot_energy_generator = energy_generator
        }
    	
        function install_scanner(IScanner scanner){
            this.slot_scanner = scanner
        }
    }
    
    // фабрика трансформеров
    
    class TransformerFactory(){
        function build_some_transformer() {
           	transformer = new Transformer()
           	laser_gun = new LaserGun()
           	nuclear_reactor = new NuclearReactor()
           	radar = new Radar()
           	
           	transformer.install_weapon(laser_gun)
           	transformer.install_energy_generator(nuclear_reactor)
           	transformer.install_scanner(radar)
            	
            return transformer
        }
    }
    
    // использование
    
    transformer_factory = new TransformerFactory()
    oleg = transformer_factory.build_some_transformer()
    
    


    Анимация №4

    К сожалению, в картинку не влезла фабрика, но она все равно необязательна, трансформера можно собрать и во дворе.

    Обозначенный на картинке слой абстракции в виде интерфейсов между слоем имплементации и слоем-потребителем дает возможность абстрагировать одних от других. Вы можете это наблюдать, посмотрев на каждый слой в отдельности: в слое имплементации (слева) нет ни слова про класс Transformer, а в слое-потребителе (справа) нет ни слова про конкретные имплементации (там нет слов Radar, RocketLauncher, NuclearReactor и т. д.)

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

    Утиная типизация


    Явление, которое мы наблюдаем в получившейся архитектуре, называется утиной типизацией: если что-то крякает как утка, плавает как утка, и выглядит как утка, то, скорее всего — это утка.

    Переводя это на язык трансформеров, звучать будет так: если что-то стреляет как пушка, и перезаряжается как пушка, скорее всего это пушка. Если устройство генерирует энергию, скорее всего это генератор энергии.

    В отличие от иерархической типизации наследования, при утиной типизации трансформеру пофиг, какого класса пушку ему дали, и пушка ли это вообще. Главное, что эта штуковина умеет стрелять! Это не достоинство утиной типизации, а скорее компромисс. Может быть и обратная ситуация, как на этой картинке ниже:



    ISP

    (Interface Segregation Principle / Принцип разделения интерфейса / Четвертый принцип SOLID) призывает не создавать жирные универсальные интерфейсы. Вместо этого интерфейсы нужно разделять на более мелкие и специализированные, это поможет гибче их комбинировать в имплементирующих классах, не заставляя имплементировать лишние методы.

    Абстракция


    В ООП все крутится вокруг абстракции. Существуют фанатики, утверждающие, что абстракция должна быть частью ООП-троицы (инкапсуляция, полиморфизм, наследование). А мой инспектор по УДО говорил обратное: абстракция присуща для любого программирования, а не только для ООП, поэтому она должна стоять отдельно. С другой стороны, то же самое можно сказать и про остальные принципы, но из песни слов не выкинешь. Так или иначе, абстракция нужна, и особенно в ООП.

    Уровень абстракции


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

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

    Неверный выбор уровня абстракции ведет к одной из двух проблем:

    1. если абстракции недостаточно, дальнейшие расширения проекта будут упираться в архитектурные ограничения, которые ведут либо к рефакторингу и смене архитектуры, либо к обилию костылей (оба варианта обычно несут за собой боль и финансовые потери)
    2. если уровень абстракции слишком высок, это приведет к оверинжинирингу в виде чересчур сложной архитектуры, которую трудно поддерживать, и излишней гибкости, которая никогда в этом проекте не пригодится. В этой ситуации любые простейшие изменения в проекте будут сопровождаться дополнительной работой для удовлетворения требований архитектуры (это тоже порой несет определенную боль и финансовые потери)



    Еще важно понимать, что уровень абстракции определяется не для всего проекта в целом, а отдельно для разных компонентов. В каких-то местах системы абстракции может быть недостаточно, а где-то наоборот — перебор. Однако, неверный выбор уровня абстракции можно исправить своевременным рефакторингом. Ключевое слово — своевременным. Запоздалый рефакторинг провести проблематично, когда на данном уровне абстракции реализовано уже множество механизмов. Проводить обряд рефакторинга в запущенных системах может сопрягаться с острой болью в труднодоступных местах программиста. Это примерно как поменять фундамент в доме — дешевле построить рядом дом с нуля.

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

    Первый слой. В игре есть один класс трансформера, все свойства и поведение описаны в нем. Это совсем деревянный уровень абстракции, подходит для казуальной игры, которая не предполагает никакой особой гибкости.

    Второй уровень. В игре есть базовый трансформер с основными способностями и классы трансформеров со своей специализацией (типа разведчик, штурмовик, саппорт), которая описывается дополнительными методами. Тем самым игроку предоставляется возможность выбора, а разработчикам упрощается добавление новых классов.

    Третий уровень. Помимо классификации трансформеров вводится агрегация с помощью системы слотов и компонентов (как в нашем примере с реакторами, пушками и радарами). Теперь часть поведения будет определяться тем, какой стаф игрок установил в своего трансформера. Это дает игроку еще больше возможностей для кастомизации игровой механики персонажа, а разработчикам дает возможность добавлять эти самые модули расширения, что в свою очередь упрощает работу гейм-дизайнерам по выпуску нового контента.

    Четвертый уровень. В компоненты можно тоже включить собственную агрегацию, предоставляющую возможность выбора материалов и деталей, из которого собираются эти компоненты. Такой подход даст игроку возможность не только набивать трансформеров нужными комплектующими, но и самостоятельно производить эти комплектующие из различных деталек. Признаться, такой уровень абстракции я в играх никогда не встречал, и не без резона! Ведь это сопровождается значительным усложнением архитектуры, а регулировка баланса в таких играх превращается в ад. Но не исключаю, что такие игры существуют.



    Как видим, каждый описанный слой, в принципе, имеет право на жизнь. Все зависит от того, какую именно гибкость мы хотим заложить в проект. Если в техническом задании ничего об этом не сказано, или автор проекта сам не знает, что может потребовать бизнес, можно посмотреть на похожие проекты в этой сфере и ориентироваться на них.

    Паттерны проектирования




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

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

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

    Еще одна ценность от паттернов — формализации терминологии. Гораздо проще коллеге сказать, что в этом месте используется «цепочка обязанностей», чем полчаса рисовать поведение и отношения объектов на бумажке.

    Заключение


    В условиях современных требований наличие в вашем коде слова class не делает из вас ООП-программиста. Ибо если вы не используете описанные в статье механизмы (полиморфизм, композицию, наследование и т. д.), а вместо этого применяете классы лишь для группировки функций и данных, то это не ООП. То же самое можно решить какими-нибудь неймспейсами и структурами данных. Не путайте, иначе на собеседовании будет стыдно.

    Хочется закончить свою песнь важными словами. Любые описанные механизмы, принципы и паттерны, как и ООП в целом не стоит применять там, где это бессмысленно или может навредить. Это ведет к появлению статей со странными заголовками типа «Наследование — причина преждевременного старения» или «Синглтон может приводить к онкологическим заболеваниям».

    Я серьезно. Если рассмотреть случай с синглтоном, то его повсеместное применение без знания дела, стало причиной серьезных архитектурных проблем во многих проектах. И любители забивать гвозди микроскопом любезно его нарекли антипаттерном. Будьте благоразумны.

    К сожалению, в проектировании не существует однозначных рецептов на все случаи жизни, где что применять уместно, а где неуместно. Это будет постепенно укладываться в голове с опытом.

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

    Ну и собственно опрос, ради которого писалась статья: будь вы трансформером, в кого бы вы предпочли трансформироваться?

    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

    Комментарии 101

      0
      Неплохо. И на мой взгляд полезно к прочтение для начинающих. Как минимум полезнее чем вот такие вещи: «Заблуждения начинающих C# разработчиков. Пытаемся ответить на стандартные вопросы»
        +2
        Читанул по-диагонали — вроде всё очень даже гут. Спасибо за материал, солилная работа! Буду использовать как референс для интересующихся людей )
          +1
          Вы описали не инкапсуляцию, а сокрытие.
            0
            Вы описали не инкапсуляцию, а сокрытие.

            Я описал и инкапсуляцию, и сокрытие, но мне не стоило их смешивать. Спасибо. Вечером поправлю.
            0
            Весьма толковая статья про ООП (и не только, учитывая что как правильно заметил автор абстракция и паттерны не имеют к нему прямого отношения).

            Единственное что, возможно выскажу непопулярное мнение, но:

            • Интерфейсы — это такой костыль для языков, разработчики которых не хотели реализовывать множественное наследование, а точнее бороться с «ромбами» реализаций. Классическая иллюстрация — Java, где после появления default реализаций, интерфейсы это по сути классы без non-static field'ов. То есть абстракция получилась очень дырявая.
            • Паттерны проектирования, в общем случае — зло. Либо вынужденное зло — когда используются как костыль, чтобы скрыть недостатки используемых языков / платформ (например паттерн Model-View), либо, что еще хуже — лень, когда разработчик не пытается использовать медленное мышление — структурировать задачу и придумать правильное решение, а включает быстрое мышление и начинает мыслить шаблонами, городя «фабрики фабрик» со всеми вытекающими.

            Ну и к инкапсуляции, как к коктейлю, который одновременно отвечает за обеспечение модульности / пространств имен, реализацию полиморфизма (причем одиночного), синтаксический сахар в виде автоподстановки первого параметра (this), ну и функционал структур (то есть реализацию работы с первичными данными — field'ами) тоже есть много вопросов. В частности, например, она усугубляет объектно-реляционный семантический разрыв. Но это отдельная большая тема.
              +3
              Интерфейсы и вообще система тимов — это метаинформация для компилятора которая дает ему больше информации, как можно еще лучше оптимизировать код. А ИДЕ возможность найти ошибку еще до компиляции. Зло — это когда машинный код после компиляции все еще имеет в себе куски типизации и тратит время и стек на проверку типов в рантайме.

              Паттерны проектирования — это процесс стандартизации индустрии. Как минимум чтобы люди друг друга понимали. Как максимум — чтобы была возможность переиспользовать код. Зло — это когда почти каждая программа имеет свой текстовый редактор и спеллчекер, когда нам нужна 20Гб операционка чтобы отредактировать *.txt файл.
                –1
                Зло — это когда машинный код после компиляции все еще имеет в себе куски типизации и тратит время и стек на проверку типов в рантайме.

                Насколько я знаю, мало какой машинный код проверяет типы просто ради проверки (чтобы выкинуть ошибку). Обычно машинный код использует информацию построенную на основе типов, например, таблицы виртуализации, но альтернатива им в виде условных переходов не сильно оптимальнее.

                Ну и не понятно какое отношение это имеет к «дырявости» интерфейсов (в смысле их дублированию логики классов).

                Паттерны проектирования — это процесс стандартизации индустрии. Как минимум чтобы люди друг друга понимали. Как максимум — чтобы была возможность переиспользовать код. Зло — это когда почти каждая программа имеет свой текстовый редактор и спеллчекер, когда нам нужна 20Гб операционка чтобы отредактировать *.txt файл.

                Для того, чтобы люди друг друга понимали, нужно проектировать и писать понятный код. Какое отношение паттерны проектирования имеют к повторному использованию кода (в частности к переписыванию текстовых редакторов) тоже неясно. То есть существует пару паттернов именно для этой задачи, но это именно что костыли, когда либо используемый модуль, либо использующий модуль спроектированы не очень качественно и их нужно «склеить».
                  0
                  Если честно я тоже не совсем понимаю что вы так прямо чуть ли не ненавидите паттерны. Возьмём те же IOC или Dependency Injection: вы считаете что они не нужны, что они «зло», «костыль», «неправильная проектировка архитектуры» или «нежеленаие разработчиков найти правильное решение»?
                    0
                    Если честно я тоже не совсем понимаю что вы так прямо чуть ли не ненавидите паттерны. Возьмём те же IOC или Dependency Injection: вы считаете что они не нужны, что они «зло», «костыль», «неправильная проектировка архитектуры» или «нежеленаие разработчиков найти правильное решение»?


                    А причем тут IOC и DI. Они не создают дырявые абстракции (а точнее классы/объекты), и достаточно спрятаны от разработчика (в коде всего одна аннотация).

                    Я про всякие фабрики, хранители, посредники и другие суррогаты по ссылке в статье.
                      +2
                      А причем тут IOC и DI.

                      При том что они на мой взгляд вполне себе являются «паттернами проектирования», а вы выше написали:
                      Паттерны проектирования, в общем случае — зло


                      И даже если не относить IoC и DI к паттернам, с чем многие вполне себе могут поспорить, то IoC и DI тоже можно использовать не к месту и/или криво и тем самым насоздавать себе кучу проблем. Они в этом плане мало чем отличаются от фабрик, декораторов, фасад и прочего.

                      Практически любой паттерн имеет свою область применения и вредит если используется не к месту. Но это не значит что паттерны зло «в общем случае»…

                        0
                        При том что они на мой взгляд вполне себе являются «паттернами проектирования»

                        Я специально зашел по ссылке автора и там ни IoC ни DI нет. А я под паттернами проектирования подразумеваю достаточно конкретную вещь (помню даже книжку по ним лет 15 назад читал) и именно то, что описано по ссылке автора.

                        И, еще раз, эти концепции (IoC и DI) не создают дополнительные публичные классы и объекты. Это принципиальный момент, поэтому они практически не вносят дополнительной сложности.
                          +1
                          Я специально зашел по ссылке автора и там ни IoC ни DI нет.

                          Если я не ошибаюсь, то внизу там была ссылка на DI, да и так нелюбимая вами «фабрика» это вроде бы один из вариантов IoC.
                          Кроме того как минимум в английской версии статьи они присутствуют оба: en.wikipedia.org/wiki/Software_design_pattern

                          А я под паттернами проектирования подразумеваю достаточно конкретную вещь (помню даже книжку по ним лет 15 назад читал) и именно то, что описано по ссылке автора.

                          Вы извините но языки программирования развиваются и появляются новые языки и фреймворки. И это приводит к появлению новых паттернов. И заявлять, что что-то не является паттерном программирования потому что вы не прочитали об этом в какой-то книжке 15 лет назад, это на мой взгляд немного странно.

                          И, еще раз, эти концепции (IoC и DI) не создают дополнительные публичные классы и объекты.


                          Во первых они создают оверхед, как минимум в виде конфигурации и мэппинга. Во вторых они часто создают проблемы с зависимостями от библиотек или их отсуствием. И в третьих я конечно не знаю что вы сейчас конкретно подразумеваете под «дополнительные публичные классы и объекты», но практически все DI варианты на C#, которые я видел, имеют какой-нибудь DependencyLocator и/или коллекцию мэппингов.
                            0
                            Вы извините но языки программирования развиваются и появляются новые языки и фреймворки. И это приводит к появлению новых паттернов. И заявлять, что что-то не является паттерном программирования потому что вы не прочитали об этом в какой-то книжке 15 лет назад, это на мой взгляд немного странно.

                            Просто если абстрагировать паттерны проектирования до всех «техник применяемых в программировании», то они автоматически потеряют всякий смысл, так как будут значить все что угодно. Так и само ООП можно паттерном проектирования считать.
                            Если я не ошибаюсь, то внизу там была ссылка на DI, да и так нелюбимая вами «фабрика» это вроде бы один из вариантов IoC.

                            Давайте так, чтобы уточнить, когда я говорил про IoC и DI я имел ввиду, что они встроены в язык / платформу (вроде как в Java Spring / .Net). И все создаваемые при этом классы разработчику не видны.

                            А когда IoC и DI делают явно в коде (создавая proxy-классы и заменяя на них field'ы и конструкторы) это ад. Такой код читать / рефакторить очень тяжело.
                              +1
                              Просто если абстрагировать паттерны проектирования до всех «техник применяемых в программировании», то они автоматически потеряют всякий смысл, так как будут значить все что угодно.

                              Угу. Вот только почему-то большинство известных мне программистов считаю DI и IoC паттернами. Да и гугл с вики с этим вроде бы согласны :)

                              Давайте так, чтобы уточнить, когда я говорил про IoC и DI я имел ввиду, что они встроены в язык / платформу (вроде как в Java Spring / .Net). И все создаваемые при этом классы разработчику не видны.

                              Более менее «встроены» они начиная с .Net Core и даже там они невидимыми не являются, так как минимум конфигурировать всё это дело кому-то надо.

                              А когда IoC и DI делают явно в коде (создавая proxy-классы и заменяя на них field'ы и конструкторы) это ад. Такой код читать / рефакторить очень тяжело

                              Угу. И именно так всё это делалось пока не появились удобные фреймворки. И даже такой вариант на мой взгляд раньше часто являлся лучшим решением чем не иметь никакого IoC :)
                                0
                                Угу. И именно так всё это делалось пока не появились удобные фреймворки. И даже такой вариант на мой взгляд раньше часто являлся лучшим решением чем не иметь никакого IoC :)

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

                                После фреймворков, они стали частью языка / платформы и перестали быть паттернами, в моем понимании этого слова.

                                Но, если вы мне предложите термин именно для явных паттернов (то есть классы которые создает разработчик руками и они видны в коде, попадают во все поиски использований, графы вызовов и т.п.), буду использовать его.
                                  +2
                                  От того что паттерн реализуется не напрямую, а скажем через библиотеку, фреймворк или нативную поддержку языка, он не перестаёт быть паттерном.

                                  И честно говоря пока не в одном определении паттерна, которое я встречал в своей жизни, не видел чтобы паттерн описывали как «классы которые создает разработчик руками и они видны в коде, попадают во все поиски использований, графы вызовов и т.п.» :)
                        +1
                        Я про всякие фабрики, хранители, посредники и другие суррогаты по ссылке в статье.

                        И почему вы считаете, что они суррогаты?

                      +2
                      > Насколько я знаю, мало какой машинный код проверяет типы просто ради проверки

                      да, но…

                      > Обычно… таблицы виртуализации, но альтернатива…

                      А почему компилятор не смог в байт код без таблицы? Без альтернатив? У него есть последовательность данных и последовательность операций которые сразу синхронизированны. Как часто мы действительно не можем обойти данные потому что не знаем точно что внутри? Почему в таких случаях скомпилированная микро* машина состояний не лучше?

                      *одна функция до сотни инструкций.

                      Мое личное мнение — потому что компилятор не смог достаточно точно угадать что там будет. Это происходит по двум причинам. Первая — программисты ленивы и пишут «минимально рабочий» код. Который и стабильный и читабельный. Но работает со слишком большим спектром входящих данных. Вторая — мы не пишем языки (за редким исключением) которые помогают передать компилятору бОльше информации. Которые помогают компилятору увидеть что входящий поток данных имеет низкую вариативность и может быть существенно упрощен.

                      > Ну и не понятно какое отношение это имеет к «дырявости» интерфейсов (в смысле их дублированию логики классов).

                      Потому что интерфейс — просто разновидность системы типов. То что отдельно взятые языки пошли по пути наименьшего сопротивления и понапихали синтаксических костылей. Я согласен что «дефолтная реализация» интерфейсов портит целостность языка. В то же время — я хорошо понимаю почему это добавили и как облегчили жизнь разработчикам.

                      Однако Ваш нонконформизм слишком мелочен. Зачем недолюбливать интерфейсы? Идите глубже — нелюбите само наследование. Наследование — это такое же зло как и нулл. От него тоже надо отказаться.

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

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

                      Сказка для детей. Я не отрицаю что в нее верят и некоторые студенты, однако они просто еще не доросли. Понятного кода не существует. Если Вы считаете что Ваш код понятен — Вы его просто не показали достаточному числу людей.

                      Код может быть интересным, красивым, оригинальным и прочее. Но не понятным. Обьяснить код машине проще, чем человеку. И подавляющее время программирование — поиск и исправление ошибочного понимания кода машиной. Хотя не правда, все наоборот. Машина всегда понимает код правильно. За оооочень редким исплючением. Это мы не правильно ей обьяснили с первых N раз.

                      > Какое отношение паттерны проектирования имеют к повторному использованию кода (в частности к переписыванию текстовых редакторов) тоже неясно…

                      Не паттерны как таковые (хотя тут есть что обсудить) а сам процесс стандартизации.

                      А стандартизация невозможна без паттернов. Если мы не видем что одно и тоже сделано на разных языках, если мы не имеем общего термина для этого — двигаться дальше невозможно.

                      В любой профессии большая часть работы состоит из повторяющихся кусков. Если мы не можем их осознать, выделить и оптимизировать — прогресс невозможен. Любое задание будет занимать столько времени, что результат к моменту своего появления уже никому будет не нужен.

                      PS Код может понятен тысяче программистов только при условии существования паттернов. Только потому что им не надо читать и компилировать каждую строку у себя в голове. А можно увидеть знакомые куски и пропустить минимум половину кода.
                        +3
                        Для того, чтобы люди друг друга понимали, нужно проектировать и писать понятный код.

                        И понятным он становится в том числе и из-за паттернов. А когда человек изобретает те же паттерны, только называет их по своему, код становится непонятным.

                      0
                      Интерфейсы — это такой костыль для языков, разработчики которых не хотели реализовывать множественное наследование, а точнее бороться с «ромбами» реализаций. Классическая иллюстрация — Java, где после появления default реализаций, интерфейсы это по сути классы без non-static field'ов. То есть абстракция получилась очень дырявая.

                      С другой стороны, проблема "ромба" именно при наличии полей в базовом классе и проявляется наиболее сильно!

                        0
                        Интерфейсы — это такой костыль для языков

                        Про интерфейсы хотелось бы услышать мнение господина lair
                          0

                          Что конкретно?

                            0
                            Что конкретно?

                            Что думаете по поводу этой мысли?
                              +4

                              Думаю, что там сильно больше одной мысли. Но если по верхам:


                              Интерфейсы — это такой костыль для языков, разработчики которых не хотели реализовывать множественное наследование

                              Мне не нравится слово "костыль", но по сути это недалеко от того, что я думаю: для потребителя (то есть того, кто откуда-то получает Something some, а потом делает ему some.Do()) различие между интерфейсом и классом сугубо техническое, и без него прекрасно можно обойтись. При этом разделение на реализацию и абстракцию (или, точнее, внешний контракт) — очень важно; не важно, чем этот контракт выражается, важно, чтобы были способы его знать и, по возможности, навязывать.


                              Паттерны проектирования, в общем случае — зло.

                              … а по этому поводу уже все сказали: в общем случае, паттерны проектирования — это совсем не зло, потому что это типовые решения типовых задач. Общий словарь.

                          +1
                          Паттерны проектирования, в общем случае — зло.
                          Паттерны проектирования — это просто типовые решения типовых задач, только и всего (а чтобы не путаться, вместо слова «паттерн» употребляйте более привычное русскому уху слово «шаблон»). Зло появляется, когда в них пытаются увидеть что-то большее, от этого происходят всякие бессмысленные холивары, как у вас с Kanut.
                            –1
                            более привычное русскому уху слово «шаблон»

                            Перевод слов «паттерн» и «шаблон» — образец.
                            Есть такое понятие — «стандартный образец».
                          0

                          Почему бы для рисования сразу не использовать UML?

                            0
                            Почему бы для рисования сразу не использовать UML?

                            В статье написано, почему. Или я не понял вопрос.
                              +1
                              Я не стал использовать UML-диаграммы, посчитав их недостаточно наглядными, хоть и более гибкими.

                              Слабое объяснение. Крайне субъективное и слабое. Но мало того: UML — есть стандарт, а ваша методика рисования — есть только ваша методика рисования. Вот возьмёт новичок и по вашим диаграммам, пойдёт в профессию и столкнётся с профессионалом, который не долго думаючи спросит: "а почему ты, новичок, рисуешь диаграммы как-то странно? Ты что — дурак? Ведь есть же UML — зачем ты переизобрёл велосипед в 115 раз?"
                              Как будет чувствовать себя новичок?

                                +3

                                Новичок будет вполне разумно раздражен, потому что каждый рисует диаграммы по-своему. И все утверждения "UML — есть стандарт" разбиваются о "не принятый в этой компании".


                                Так что профессионал, который заранее не сказав, что ему нужен UML, обзывает других людей дураками, несколько не прав.

                                  0

                                  К любому "рисованию диаграмм по своему" шли через БАЗУ рисования диаграм вообще. А база в IT для этого — UML диаграммы.


                                  Мне когда то, ещё в те времена молодому профессионалу, вложили в голову, что без знания базы — разработчики не будут понимать вообще что нужно делать и я с этим мнением — полностью согласен. Потому что именно база приводит идеи к общему знаменателю, понятному всем заинтересованным персонам.


                                  Да и бритву Оккама я уважаю — и не вижу смысла для переизобретания велосипеда. Особенно если учесть, что UML не имеет строгой нотации и любой вид агрегации можно заменить обычной прямой линией.

                                    +1
                                    А база в IT для этого — UML диаграммы.

                                    … а на чем основано это утверждение?


                                    Мне когда то, ещё в те времена молодому профессионалу, вложили в голову, что без знания базы — разработчики не будут понимать вообще что нужно делать и я с этим мнением — полностью согласен. Потому что именно база приводит идеи к общему знаменателю, понятному всем заинтересованным персонам.

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


                                    Особенно если учесть, что UML не имеет строгой нотации

                                    Вот именно поэтому я и не вижу смысла за него ратовать, если споров за то, как в нем что-то правильно изобразить, будет больше, чем споров об изображенном (real life story).

                                      0
                                      … а на чем основано это утверждение?

                                      Первая буква U из UML — отвечает на этот вопрос.


                                      без отсутствия общего языка. Но вы почему-то считаете, что UML таким языком является

                                      А чем вам UML не общий? И без шпаргалки его сможет прочесть любой, сноски нужны — для понимания нюансов. Но вот в чём штука — чтобы понять ЛЮБУЮ предлагаемую ВАМИ (или автором) диаграмму — сноски обязательны. Но в вашем случае — они нужны априори, а в UML без них, за счёт хоть какой-то известности — можно обойтись. Так что он уже более общий, нежели ваш (или автора) продукт.


                                      как в нем что-то правильно изобразить

                                      Кто определит понятие "правильность изображения" — тот собственно и решит все споры.

                                        +1
                                        Первая буква U из UML — отвечает на этот вопрос.

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


                                        А чем вам UML не общий?

                                        Тем, что не все его знают.


                                        И без шпаргалки его сможет прочесть любой

                                        Неа. И, что важнее, все прочитают по-разному.


                                        Но в вашем случае — они нужны априори, а в UML без них, за счёт хоть какой-то известности — можно обойтись.

                                        Вот это как раз и иллюзия. Может быть можно. На практике я такого не видел.

                                          0
                                          как кто-то решил назвать свой язык.

                                          Не кто-то, а люди, которые в далёком 1995 году уже столкнулись с проблемой рисованяи классов и постарались решить эту проблему, пройдя несколько процессов улучшения и попутно внедрив систему в общеобразовательный стандарт ВУЗов не только в США, но и по всему миру.


                                          Неа. И, что важнее, все прочитают по-разному.

                                          ну не знаю не знаю, но класс — я различу. Интерфейс тоже… С ассоциациями — морока конечно, но тоже можно разобраться.
                                          А с другой стороны — я не знаю как в России, но я кроме двух ВУЗов в Латвии знаю как минимум один в Германии — в которых в IT на программиста преподают UML. И представляют как стандарт. Я знаю, что используется в некоторых ВУЗах США.
                                          Это как минимум в несколько раз больше, нежели — изобрести велосипед заново в данной статье.


                                          На практике я такого не видел.

                                          Субъективное мнение. Я видел, как люди — не пугались UMLа :).

                                            0
                                            Не кто-то, а люди, которые в далёком 1995 году уже столкнулись с проблемой рисованяи классов и постарались решить эту проблему

                                            Никто не спорит с тем, что постарались. Но решили ли?


                                            ну не знаю не знаю, но класс — я различу. Интерфейс тоже…

                                            Вы — различите. Но вы — это не все.


                                            А с другой стороны — я не знаю как в России, но я кроме двух ВУЗов в Латвии знаю как минимум один в Германии — в которых в IT на программиста преподают UML. И представляют как стандарт.

                                            А те, кто в этих вузах не учился, в разработке ПО не работают? Мне вот никто и никогда не преподавал UML, мне это работать не мешает никак в смысле "совсем никак".


                                            Это как минимум в несколько раз больше, нежели — изобрести велосипед заново в данной статье.

                                            Мне кажется, вы не понимаете целей "велосипеда" в данной статье. У него ровно одна задача: пояснять рассуждения в данной статье. Смысла сравнивать его с преподаванием UML ровно ноль.


                                            Субъективное мнение.

                                            Ну да, субъективное. Но оно прекрасно показывает общую идею: бесполезно ожидать, что если вы нарисуете диаграмму в UML, ее поймут все и одинаково.

                                              0

                                              У нас в ВУЗе UML преподавали и именно как общий стандарт.


                                              Проблема только в том, что в каждой фирме где я работал был свой "диалект" и каждый раз приходилось привыкать/переучиваться заново.

                                                0
                                                изобрести велосипед заново в данной статье

                                                Велосипед — это изобретение чего-то уже изобретенного. Я тоже сначала хотел UML, но уперся в то, что не нашел, как изобразить с помощью него некоторые моменты, описанные в данном комментарии

                                                Понимаете, это как цифры и числа. Нельзя преподавать человеку числа, если он не знает цифр. UML — это числа, а мои схемы — это цифры. UML помогает описывать архитектуру с помощью известных всем механизмов. А мои картинки описывают, как работают эти самые механизмы. Это разные уровни.
                                      +1
                                      Крайне субъективное и слабое

                                      Никто и не говорил, что это объективное обоснование. Там так и написано — это мое субъективное мнение.

                                      А вот, что объяснение слабое, давайте обсудим. Можете нарисовать с помощью UML так чтобы было понятно, что класс отличается от объекта, что код метода содержится в классе, а состояние в объекте, что в интерфейсе только сигнатура метода, а в классе — имплементация, что метод fire после перегрузки будет вызываться из имплементации класса десептикона, а не трансформера? Если сможете, то вы правы, это объяснение слабое.

                                      Я убежден, что на ранних этапах знакомства, нужно отсечь лишние элементы из диаграмм, которые создают информационный шум, и выделить важное (что для новичка не очевидно) визуальными приёмами: цвет, заполнение, форма. А уже после того, как понимание пришло, можно перейти на профессиональный инструмент.

                                      Если бы у меня была цель вместо букваря детям дать чертежи, я бы назвал статью «ООП в UML-диаграммах», а не в картинках. И тогда да, дети после первого класса могут сразу идти на собеседование, владея чертежами.
                                  +1
                                  Чтиво не осилил, плюсую за картинки).
                                    0
                                    Уфф… отлего. Значит я не один такой. Всем спасибо, очень было интересно, но чтобы этот текст по-настоящему вкурить, надо большую часть изложенного знать достаточно хорошо. )
                                    0
                                    С теорией применения паттернов все вроде-бы понятно. Теперь хочется научиться правильно декомпозировать задачи (посоветуйте материалов) и правильно применять паттерны, особенно в рефакторинге легаси-кода.
                                    Хочется примеров «best practice», признаков того, что все сделано правильно.
                                      +1
                                      Если бы так просто было определить сделано всё правильно или неправильно, то мир программированиябыл бы райским садом. К сожалению то, что что-то сделано неправильно, обычно выясняется уже после того как оно сделано. Причём обычно гораздо позже.

                                      И какого-то «best practice» на все случаи жизни лично я не знаю. Я даже не могу назвать «best practice» для выбора «best practice для какой-то конкретной проблемы» :)
                                        0
                                        Как раз это я пытался донести в разделе «заключение».
                                        0
                                        Не все задачи легко декомпозируются в ООП. Попробуйте для разминки декомпозировать геометрию.
                                          +3
                                          и правильно применять паттерны, особенно в рефакторинге легаси-кода.

                                          Делаете рефакторинг, делаете так как лучше, не оглядываясь на паттерны. Увидели сходство своих изменений с каким-то паттерном, добавили соответствующий суффикс к названию класса. Оценили отличия классической и вашей версии, изменили код либо добавили пояснения в документацию, почему отличия присутствуют. При этом паттерны это довольно абстрактная вещь, если отличий слишком много, возможно это другой паттерн, или вы делаете что-то неправильно.

                                          0
                                          Немного занудства.
                                          Заголовок: ООП в картинках.
                                          А по факту, наиболее подходящая под заголовок картинка лишь предпоследняя (со слоями абстракций), а всё остальное — простые UML-диаграммы.
                                            0
                                            Стопицотый раз читаю статью по ООП. Для себя нашел проблему — почему я так и не могу нормально разобраться в ООП (увы). Я привык к стилю программированию на чистом Си (в основном всякой математики), без ++, и соответственно возникает вопрос — а зачем мне использовать ООП, если можно и без него. Но это-то ладно — я могу сообразить, что некие объекты лучше сделать классами — ну скажем сделать если я захочу комплексных чисел или кватернионов, я напишу соответствующий класс и определю операции взаимодействия, но дальше… какие задачи стоит решать именно через ООП? Я с удовольствием бы увидел примеры с объяснениями — типа вот задача, мы её решаем так-то и делаем это именно так по таким-то причинам…
                                              0
                                              Хм… Я бы не сказал что существуют задачи, которые обязательно нужно решать при помощи ООП. В конце-концов любой ООП код трансформируется в машинный и следовательно любую проблему «решаемую при помощи ООП» можно решить сразу написав нужный машинный код.

                                              ООП это, в контексте вашего вопроса, просто «инструмент», который по идее просто облегчает написание и поддержку кода.

                                              И такое часто просто вопрос привычки и/или необходимости. И если лично вам проще без этого и/или вы без этого прекрасно обходитесь, то возможно ООП вам и не нужно.
                                                0
                                                Я привык к стилю программированию на чистом Си (в основном всякой математики), без ++, и соответственно возникает вопрос — а зачем мне использовать ООП, если можно и без него.

                                                Действительно, зачем?

                                                Цитата из статьи:
                                                Любые описанные механизмы, принципы и паттерны, как и ООП в целом не стоит применять там, где это бессмысленно или может навредить.

                                                Зачем натягивать сову на глобус, если у вас и без ООП все прекрасно получается. Возможно ООП вам только навредит.
                                                  +4
                                                  Важно понимать: нужно ли оно вам.
                                                  Возьмем какой нить упрощенный пример. Вы делаете космические корабли, а ваши клиенты ими просто пользуются, не вникая в тонкости.

                                                  class Starship
                                                    {
                                                    private $position;
                                                  
                                                    public function fly(Vector $coords) {}
                                                    public function moveTo($coords) {}
                                                    public function hyperjumpTo($coords) {}
                                                    public function shootAt($coords) {}
                                                    }
                                                  


                                                  $myStarship=new Starship;
                                                  $myStarship->flyTo($someRandomPoint);
                                                  


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

                                                  abstract class Starhip {} 
                                                  
                                                  class StarDestroyer extends Starship {}
                                                  


                                                  Но некоторым нужно подчеркнуть свою индивидуальность

                                                  class SuperStarDestroyer extends Starship {}
                                                  


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

                                                  //Название не совсем удачное, поскольку Imerial/Rebel на самом деле не указывает на владельца
                                                  abstract class ImperialStarship extends Starship {}
                                                  abstract class RebelStarship extends Starship {}
                                                  
                                                  class StarDestroyer extends ImperialStarship {}
                                                  class SuperStarDestroyer extends ImperialStarship {}
                                                  
                                                  class CorelianCorvette extends RebelStarShip {}
                                                  class MonCalamariCruiser extends RebelStarShip {}
                                                  


                                                  Более того мы теперь можем построить верфи

                                                  //ну или Shipyard
                                                  class ImperialWarf () : ImperialStarship {}
                                                  class RebelWarf() : RebelStarship {}
                                                  class CorelianWarf() : Starship {}
                                                  


                                                  Однако мир чуть сложнее, да и зло нам нужно не просто так, а чтобы творить, ну… собственно зло.

                                                  abstract class Starhip 
                                                    {
                                                    public function shootAt(Starship $target)
                                                    }
                                                  


                                                  И наверняка, просто зла недостаточно, нужно вселенское зло

                                                  interface Destroyable {}
                                                  
                                                  abstract class Starship implements Destroyable {}
                                                  abstract class Planet implements Destroyable {}
                                                  
                                                  //ее по канону конечно нужно сделать синглтоном, но кто знает....
                                                  class DeathStar extends Starship
                                                    {
                                                    public function destroy(Destroyable $target)
                                                    }  
                                                  


                                                  Ну и наведем чуточку красоты

                                                  class GalaxyFarFarAway
                                                    {
                                                    private static $planets;
                                                  
                                                    public static function getPlanetByName(String $planetName) {}
                                                    public static function getRandomHabitablePlanet() {}
                                                    }
                                                  


                                                  И как итог, сферический Люк Скайвокер в вакууме, может теперь:

                                                  $myNewShip = new MilleniumFalcon();
                                                  $destination=GalaxyFarFarAway::getPlanetByName('Alderaan')
                                                  
                                                  $myNewShip->hyperjumpTo($destination->getCoords());
                                                  


                                                  А губернатор Таркин, мерзко ухмыляясь:

                                                  $myPrecious = new DeathStar();
                                                  $victim=GalaxyFarFarAway::getRandomHabitablePlanet()
                                                  
                                                  $myPrecious->destroy($victim);
                                                  


                                                  А вы, в свою очередь продолжаете быть творцом, реализовывая лаконичные методы и сущности. Решаете нанять команду, делегируете штуки подчиненным/коллегам: ну скажем вам надо организовать лютую дизмораль в галактике при уничтожении обитаемой планеты. А сами при этом продолжаете строить корабли.

                                                  При этом: помнить или разбираться сразу во всем необходимости нет вообще никакой. По методам IDE подскажет прямо на ходу. По дереву абстракций ходить тоже легко и просто. Захотели сгенерить документацию? Тоже без проблем.
                                                    0
                                                    «губернатор Таркин, мерзко ухмыляясь»

                                                    ))) Спасибо!!! Хоть прям бери и пишу игрушку по мотивам ЗВ ))).
                                                    Видимо мои программы слишком просты структурно. Хотя возможно я нахожусь в некой психологической ловушке сравнительно низкоуровневого программирования, впрочем высокоуровневое тоже может быть ловушкой — ваял что-то на С++ Builder — ну как ваял — складывал из кубиков, заметил, что мне уже лениво писать какие-то функции самому, лучше я поищу готовые компоненты, а до знакомства с этим чудесным продуктом такого не было.
                                                      0
                                                      А почему эти разные классы нельзя передавать как параметр функции на входе?
                                                      +1
                                                      какие задачи стоит решать именно через ООП?

                                                      Те, решение которых без ООП оказывается менее поддерживаемым. Если вы пишете математику и на C, скорее всего, вам это низачем не нужно.

                                                        +1
                                                        Я для себя в качестве базового случая, при котором уже довольно полезны классы — это когда нужно обрабатывать одним набором функций несколько наборов данных, причем важно не только прямое преобразование, но и уже хранимое в наборе данных состояние.

                                                        Пример — цифровая фильтрация. Пока у нас только один фильтр, мы можем тупо написать функцию типа int filter(int input_value), а всякие там внутренние данные типа массива для фильтрации методом усреднения или хотя бы одного предыдущего значения для КИХ/БИХ спрятать в локальной области видимости модуля. Даже если нужно менять параметры фильтра на ходу — опять же добавляем функцию, присваивающую переданные ей значения внутренним переменным модуля, задающим коэффициенты, или даже флаги, меняющие тип фильтрации.

                                                        По сути, модуль обеспечивает одну из трех фишек ООП — инкапсуляцию. Но у такого модуля есть проблема — он один. Даже если мы размножим наборы хранимых внутренних данных (массив1, массив2, коэффА_1, коэффА_2, коэффБ_1, коэффБ_2), то как указать, с какими из них должна работать функция в каждом случае? Не говоря уже о том, что такое размножение просто неудобно и громоздко.

                                                        Окей, путь есть — упаковываем набор в структуру, используем несколько структур (можно даже динамически создавать экземпляры структур в рантайме по мере надобности) и получаем возможность при вызове функции указывать, с какой из структур работать: filter(value, &dataset1). Но это нарушает инкапсуляцию — чтобы передать в функцию указатель или ссылку на структуру, внешний код должен иметь эту структуру в своей видимости.
                                                        Хотя и это можно обойти — если наборы заданы статично и их немного, можно сразу присвоить им некие идентификаторы, и передавать функциям эти идентификаторы вместо ссылки/указателя. При динамическом же созданий наборов код еще усложняется — надо будет генерировать идентификаторы, где-то хранить соответствие идентификаторов и адресов структур в памяти и т.п. При том что мы по сути уже «изобрели объект».

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

                                                        Наследование и полиморфизм тоже можно подобными путями реализовать на чистом С. Это будет еще более громоздко, и что самое главное — это таки будет объектно-ориентированным программированием, только без использования ОО-возможностей компилятора.

                                                        Ну ок, наследование и полиморфизм мне лично в разработке прошивок для микроконтроллеров пока еще не пригождались (только для софта на ПК). А вот инкапсуляция — еще как. Кстати к дихотомии низкоуровневое/высокоуровневое — цифровая фильтрация или там программное подавление дребезга контактов это ведь как раз очень низкоуровневые задачи.
                                                          0
                                                          Здорово, спасибо!!!
                                                          +1
                                                          Пожалуй, таки опишу свой пример использования полиморфизма.

                                                          Вот, к примеру, одна моя софтина принимает от железки кучу разных типов данных (даты, числа с довольно разнообразным кодированием и т.п.) и отображает их в интерфейсе, выводит в отчеты и т.п.
                                                          Как это делается чисто процедурным путем — для каждого такого значения вызывается функция преобразования в строку — inttostr, datetostr, temperature1tostr, temperature2tostr.
                                                          Тут есть две засады. Во-первых, код тупо загромождается кучей вызовов этих функций
                                                          label1.Caption = inttostr(parameter1);
                                                          edit2.Text = inttostr(parameter2);

                                                          Во-вторых если у нас вдруг поменяется тип параметра или мы захотим изменить вид отображения параметра (скажем, с десятичного на шестнадцатеричный, или добавить/убрать ведущие нули) — придётся искать по всему коду, где у нас происходит такое отображение и везде менять функцию или ее дополнительные параметры вызова.
                                                          А можно сделать все эти типы данных наследниками абстрактного класса «параметр» с методом «Показать значение как строку», реализовав в каждом наследнике нужный вариант — и мы при необходимости можем в любом из наследников подкрутить эту функцию как надо в одном месте, не трогая всю остальную программу (вся остальная программа сможет вообще не париться про тип параметра и считать их все абстрактным «параметром» вызывая всегда «Показать», а уж как именно показать — конкретный экземпляр сам знает свой тип и умеет преобразовывать значение).
                                                          0
                                                          Классная статья, хотя про интерфейсы малость путанно.

                                                          Не хватает раздела: «а зачем?». Ну серьезно, очень многие так увлекаются, что KISS, YAGNI итд для них не существует, есть только сферический ООП в вакууме. Причем с костылями естественно. Соответственно, растет сложность разработки и поддержки. Как раз то, с чем ООП призван бороться.

                                                          Заключение тоже неплохое. Но надо еще сказать, что даже при наличии ооп, многие переходят к классовому программированию. Ну вот холивара ради(ну все равно почти под каждой статьей про ооп такой есть): допустим ваши трансфомеры начали сражаться друг с другом, соответственно нам понадобиться расчитать опыт/здоровье/щиты/прочие штуки, а потом обработать собственно убийство. Тут сразу возникнет вопрос: а где и как? И на этом этапе многие скажут, что нужен класс CombatManager, которому нужно передать трансормеров, вместо optimus->attack(megatron). Вот только про его существование нужно обязательно знать и помнить, ибо IDE в данном случае просто бесполезна.
                                                            0
                                                            Не хватает раздела: «а зачем?». Ну серьезно, очень многие так увлекаются, что KISS, YAGNI итд для них не существует, есть только сферический ООП в вакууме.

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

                                                            Ну вот холивара ради(ну все равно почти под каждой статьей про ооп такой есть): допустим ваши трансфомеры начали сражаться друг с другом, соответственно нам понадобиться расчитать опыт/здоровье/щиты/прочие штуки, а потом обработать собственно убийство. Тут сразу возникнет вопрос: а где и как? И на этом этапе многие скажут, что нужен класс CombatManager, которому нужно передать трансормеров, вместо optimus->attack(megatron).

                                                            Это не относится к ООП, это относится к проектированию. А само ООП — просто один из возможных инструментов реализации вашей архитектуры.
                                                              0
                                                              там, где в этом появляется необходимость.

                                                              Да это хорошо, но таки неплохо было бы понимать, при каких условиях она возникает. Это очень мало где поясняется.

                                                              Это не относится к ООП, это относится к проектированию. А само ООП — просто один из возможных инструментов реализации вашей архитектуры.

                                                              Ловко вы ушли от ответа. Даже возразить нечего)
                                                                0
                                                                Это очень мало где поясняется.

                                                                Потому что тут нет идеальной инструкции. В этом и заключается проблема, которую каждый решает, опираясь на свой и чужой опыт в похожих проектах.
                                                                  0
                                                                  Почему нет?
                                                                  Как правило есть два краеугольных камня: стоимость поддержки и разработки. ООП призвана решить их обе. Если в результате ваших манипуляций стоимость только возрастает, значит либо что-то пошло не так, либо ООП тут даром ненужен.
                                                                    0
                                                                    Определить было ли использование ООП «в результате», то есть постфактум, хорошей или плохой идеей относительно просто. Вопрос в том как определить это заранее ;)
                                                                      0
                                                                      Да и после и вовремя и заранее хочется знать)
                                                                      0
                                                                      Почему нет?

                                                                      Потому что, чтобы определить, какая парадигма подойдет для данного проекта лучше, нужно реализовать этот проект в разных парадигмах и сравнить стоимость разработки/поддержки в каждой из них. При этом требуются опытные команды разработчиков для каждой парадигмы. Иначе получится, что ООП-шники начнут пробовать реализовывать проект на ФП, у них получится плохо, и они решат, что ФП отстой, ООП — сила. (Я привел две популярные парадигмы, но их на самом деле много, это тоже учитывайте)

                                                                      А поскольку никто такие дорогостоящие эксперименты не ставит, нам приходится сравнивать похожие проекты в разных парадигмах, не имея на руках данных о стоимости поддержки/разработки, оценивая разницу на глазок. Это и есть «свой и чужой опыт», на который мы можем опираться, об этом я и сказал выше.
                                                                        0
                                                                        Ах если бы каждый кто пишет статью про ООП прилагал к ней свой опыт, было бы просто шикарно
                                                                          0
                                                                          Ах если бы каждый кто пишет статью про ООП прилагал к ней свой опыт, было бы просто шикарно

                                                                          Согласен. Но это уже будет не статья, а книга про конкретный проект.
                                                                    0
                                                                    Ну если навскидку, то есть несколько «критериев», которые по той или иной причине могут указывать на «полезность» ООП:
                                                                    — Сложная и/или иерархическая структура «реальных» объектов/предметов с которыми надо работать.
                                                                    — Большой объем работы/проекта.
                                                                    — Большое количество разработчиков, участвующих в проекте.
                                                                    — Большая вероятность частых и/или больших изменений.
                                                                    — Отсутствие критериев исключающих существующие ООП языки/рантаймы/фреймворки(например перформанс или доступные ресурсы в момент выполнения ).

                                                                    Но опять же это всё всего лишь индиции и не больше.
                                                                      0
                                                                      Ну если навскидку, то есть несколько «критериев», которые по той или иной причине могут указывать на «полезность» ООП:

                                                                      Хочется сразу уточнить. Под полезностью ООП вы имеете в виду пользу наличия/отсутствия ООП или пользу сравнительно с какой-то другой парадигмой? Если второе, то с какой именно?
                                                                        +1
                                                                        Опять же всё в зависимости от конкретной ситуации, поэтому и кавычки :)

                                                                        Да и вообще если мы будем честны, то сейчас уже практически никто не выбирает парадигмы как таковые. Выбирают скорее языки программирования или даже сразу фреймворки. И при этом выборе часто главную роль играют факторы абсолютно не связанные с парадигмами.
                                                                0
                                                                По поводу уровня абстракции в играх.
                                                                При использовании первого или второго уровня для написания ключевого функционала длительность разработки средней игры будет стремиться к бесконечности. Ибо при изменение требований, а меняются они постоянно, придется постоянно переписывать классы базового трансформера и его наследников. А в игре со временем появятся не только трансформеры, но и другие объекты, которые могут обладать не хилой частью функционала трансформеров.

                                                                В играх 3-ий и 4-ый уровни абстракции используются. Хотя, может тут лучше выделить еще один уровень, или я сейчас 4-ый уровень опишу, не знаю.
                                                                В общем, вводятся понятия «компонент», «контейнер». Нет никаких классов трансформеров, пушек. Есть компоненты, отвечающие за характеристики, способности, различный функционал. После добавления компонентов в объект-контейнер, получается конкретный трансформер, пушка или радар.
                                                                К тому же, при таком подходе количество интерфейсов уменьшается раз в 100, т.к. их более эффективно заменяют компоненты. Там, где раньше у класса проверялось наличие интерфейса, будет проверяться наличие компонента.

                                                                По поводу 4-го уровня. Не понятно, тут разве не о крафте в играх говорится?)
                                                                Еще, похоже что относящееся к этому уровню встречается в играх при написании ИИ. Например, в деревьях поведений. Там составными частями являются действия, условия.
                                                                  +1
                                                                  Мне кажется, вы неправильно поняли примеры с уровнями абстракции. Эти 4 уровня придуманы для данной статьи для сферической игры в вакууме. Это не какая-то устоявшаяся в индустрии классификация уровней, поэтому рассматривать все возможные уровни и реализации, не имея на руках ТЗ — бессмысленно.

                                                                  По поводу 4-го уровня. Не понятно, тут разве не о крафте в играх говорится?)

                                                                  Похоже вы не видите разницу между уровнями, придуманными мной для данной статьи. Система крафта в играх — это обычно второй или третий уровень.

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

                                                                  Третий: поверх рецептов есть система улучшений или модулей, но рецепты все равно жестко прописаны в игре. Например, добавив редкие материалы, вы можете получить продукт с более высокими характеристиками. Либо можете улучшать продукт дополнительными модулями, добавляя тем самым новый функционал.

                                                                  Четвертый: есть набор деталек, и никаких рецептов. Комбинируя эти детальки, игрок может получать продукты, ранее в игре не существующие. Функционал продукта при этом полностью формируется из свойств деталек и их комбинаций. Поэтому у таких продуктов не может быть названия, игрок должен его придумать сам. И вот такого уровня абстракции в играх я не встречал по указанным в статье причинам, хотя может он и есть.
                                                                    0
                                                                    Minecraft?
                                                                      0
                                                                      А там рецепты разве не прописаны жестко? Получающиеся продукты так уж точно прописаны, ничего нового не сделаешь.
                                                                        0
                                                                        Minecraft?

                                                                        Это второй уровень по нашей вымышленной классификации. Там список рецептов, который игрок поменять не в силах. Получающиеся продукты никак не улучшаются. Но если для каких-то вещей есть зачарование, то тогда третий уровень.
                                                                          0
                                                                          И всё же там есть 4-й уровень, если не 5-й :)
                                                                          Насколько я знаю, рецепта на процессор в minecraft нет.

                                                                          И эти же уровни есть во многих играх-песочницах, где конкретный игрок много что может изменить в своей песочнице, но баланс не портится в силу изолированности.
                                                                            0
                                                                            Насколько я знаю, рецепта на процессор в minecraft нет.

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

                                                                            А во-вторых, не соглашусь, что процессор — это пятый уровень. Скорее второй:
                                                                            1 уровень — это блоки.
                                                                            2 уровень — это прочность блоков и NBT-тэги.
                                                                            3 уровень (которого нет без модов) — возможность в блоки вставлять модификаторы.
                                                                            4 уровень (тоже отсутствует) — возможность создавать новые блоки с новыми названиями и свойствами.

                                                                            Процессор работает полностью на блоках и их состояниях, именно поэтому он такой огромный и не масштабируемый, то бишь не гибкий. Вот если бы этот процессор можно было бы создать в виде нового блока и дать ему название «процессор», то да, это был бы четвертый уровень.
                                                                              0
                                                                              Ну… это уже вопрос точности терминов. Если говорить в рамках того, что описано в статье — то упомянутый процессор всё артефакт, созданный игроком из винтиков и гаек, которые представлены в игре. Причём ему, как и авторам многих самостоятельных артефактов, пришлось крафтить одни блоки из других, потом соединять их, создавая нечто новое, чего не было в рецептах, а потом эти новый элементы снова соединять… Это как раз 4-й уровень.
                                                                              А если из этих артефактов создаётся новая «реальность», в исходной игре, или игра в игре, и исходная механика позволяет это — тут уже и 5-й уровень.

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

                                                                                Вы не путайте, эти уровни абстракции идут не поверх друг друга, а параллельно. Система крафта никакого отношения к окружающему миру и блокам не имеет. Как только игрок устанавливает блок, тот переходит из одной системы в другую уже со своими слоями, в которых нет информации, из чего этот блок скрафтили, есть только ID блока и его состояние. Об этом говорит полная независимость этих механик: если убрать из игры систему крафта, база данных блоков и их механика никуда не денется.

                                                                                это если навесить два ограничения

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

                                                                                Чтобы понять мою мысль, попробуйте описать архитектуру окружающего мира, в котором собрали процессор, с точки зрения разработчика программных сущностей и их ООП-отношений.
                                                                                  0
                                                                                  Я рассматривая вопрос этих абстракций проще. В классических играх с крафтом есть до 3-х уровней:
                                                                                  1 — базовые элементы, данные as-is
                                                                                  2 — крафт предметов/навыки персонажа
                                                                                  3 — аугментация характеристик персонажа/предметов.
                                                                                  Все три уровня укладываются не только в механики, но и любой вариант наверняка есть в некой «базе сущностей» на этапе разработки. Иначе говоря, чтобы не пытался создать игрок — это уже есть в БД игрового мира.

                                                                                  Естественно, всё что удастся создать — укладывается в механики игры, иначе этого в ней не создать. Но точно так же можно говорить, что всё, что написано на JavaScript, укладывается в его механики.

                                                                                  В играх-песочницах есть некий элемент мета-программирования, который позволяет создавать самостоятельные игровые сущности. Дальше вопрос лишь в том, как это реализовано и как много свободы у игрока.
                                                                                  Есть игры, где сущности рандомно генерируются из большого числа «винтиков», как в No Man’s Sky, или где игроку изначально даны большие возможности по созданию неведомого, как в Spore
                                                                                    0
                                                                                    Давайте немного детализируем ваш пример с яваскриптом, чтобы уловить разницу. Представьте, что из яваскрипта уберут функции (как сам механизм). Можно ли на нем будет написать все программы, которые уже написаны? Несомненно, да, и они даже будут работать. Но код превратится в плоскую систему копипаст, участки кода можно будет подписать комментариями, но невозможно будет вызвать из других участков наравне с остальными инструкциями. Проекты перестанут быть гибкими, масштабируемыми и расширяемыми.

                                                                                    Не напоминает процессор из майнкрафта? Автор этого сооружения настолько горд собой, что снял видео и написал статью на хабр. И ему действительно есть, чем гордиться, ведь это реальное достижение, без достаточного количества абстракций реализовать такую махину.

                                                                                    Возможность в игре соорудить какие-то сложные механизмы еще не говорит о гибкости архитектуры. Богатые возможности могут быть обусловлены банально большим количеством строго регламентированных возможностей с разделением на 2-3 уровня абстракции. Но богатые != гибкие.

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

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

                                                                                      Равно так же, в яваскрипте можно создавать новые типы и давать им новое поведение, но в конечном счёте гибкость архитектуры будет ограничена движком и платформой.

                                                                                      Вообще игр-песочниц сильно больше. Я просто не готов так сходу накидать ярких примеров. В той же spore эволюцию не осилили, но конструктор жизненных форм, емнип, был вполне гибок.
                                                                                        0
                                                                                        В Spore гибкость конструктора обеспечивала разнообразие чисто визуальное. На характеристики влияла комбинация конечного набора «кубиков» с заданными модификаторами. Я не помню, можно ли было добавлять свои «кубики», хотя бы с помощью модов — но даже если можно, то все равно это получается горизонтальное расширение того же уровня. Вот если б сами эти «кубики» надо было собирать из «субкубиков» — вот тогда можно было бы говорить о многоуровневости.
                                                                                          0
                                                                                          это получается горизонтальное расширение того же уровня

                                                                                          Вот я товарищу именно это пытаюсь донести уже сколько комментов подряд.
                                                                                          0
                                                                                          Если у нас есть в коде игры на первом уровне сущности A, B и С, и если в самой игре A+B+C == ABC, то это не новый уровень, а просто расширение текущей плоскости.

                                                                                          Новый уровень — это A+B+C == D при тех же условиях, что D не прописан в коде игры, а генерируется действиями игрока на лету.

                                                                                          В статье рассматривается четвертый уровень, где из деталек делаются штуки, которые вставляются в слоты. Процессор в майнкрафте никуда уже вставить нельзя, это просто груда деталек, которые взаимодействуют с соседними. Это богатые, но плоские возможности.

                                                                                          Если вы не видите разницу между этими двумя принципиально разными классификациями, это не значит, что ее нет.

                                                                                          А пример с яваскриптом вы, похоже, даже не попытались понять. Он идеально демонстрирует, как убирание слоев (которое вы как то не так понимаете) делает продукт ригидным и неудобным, но с сохранением богатых возможностей. Что мы и видим с процессором в майнкрафте: отсутствие необходимых слоев в игре не лишает игру возможности построить процессор, но превращает это мероприятие в подвиг. Как и любую программу на яваскрипте без функций.
                                                                                            0
                                                                                            Я понимаю, о чём вы толкуете, но тут уже больше вопрос к тому, что как назвать.
                                                                                            С точки зрения игрока, не известно и не важно, вставляет он в слот композицию (A+B+C) или созданный новый компонент D.
                                                                                            Но если смотреть на то, что написано в статье — то главное отличие — это возможность любым способом создать такое нечто, которого нет в кодовой базе изначально.

                                                                                            В вашем примере про яваскрипт функции — это лишь удобная абстракция для написание кода, сиречь манипуляции данными и внешними/внутренними АПИ. Чем окажутся функции после компиляции — знает только конечный движок.
                                                                                            Есть игры, где доступны скриптовые модификации. Куда вы их отнесёте? С одной стороны, комбинация изначальных модулей и кода — явно что-то новое, т.е. D. Но во внутренней базе это может храниться как модуль+код, т.е. именно как (A+B+C).

                                                                                            Я вот к чему клоню — дайте простое, но исчерпывающее объяснение, что по-вашему будет 4-м уровнем.
                                                                                            Модули/субмодули/детальки не дают такого объяснения.
                                                                                              +1
                                                                                              А мне кажется, дело даже не в количестве уровней, а в принципе их связи. Кажется, автор свалил это в кучу. Давайте отделим мух от котлет.

                                                                                              Достаточно рассмотрения двух уровней — на одном детали, на другом собираемые из них объекты.

                                                                                              Тут есть два варианта:

                                                                                              Первый: разработчики заранее прописали список объектов как возможных комбинаций деталек, и жестко сопоставили этим объектам некие характеристики (пример — предметы в Майнкрафте, список возможных создаваемых топоров-лопат-мечей фиксирован и характеристики зависят только от вида объекта. Другой пример — навешивание модулей на танки в WoT). Такой способ имеет ограничение — величина списка объектов сильно ограничена усидчивостью разработчиков или чуть менее сильно — техническими вопросами хранения такого списка.

                                                                                              Второй: разработчики заложили только список деталей и правила их комбинации, а свойства объекта либо вычисляются в зависимости от модификаторов, приписанных деталям. В этом случае количество возможных объектов подвержено комбинаторному взрыву. Примеры — Robocraft и система генерации оружия и прочей снаряги в Borderlands.

                                                                                              А дальше да, может быть иерархия уровней, детали тоже могут быть «сборочной единицей». При этом разные уровни могут быть устроены по разным принципам. Также в игре могут быть параллельные «сборочные цепочки», не связанные в иерархию.

                                                                                              Говоря про майнкрафт, тут есть две таких «сборочных цепочки», и в иерархию они связаны.

                                                                                              Есть крафт предметов и блоков по рецептам — он целиком первого типа. Список создаваемых объектов и их свойств жестко задан.

                                                                                              И есть конструирование всяких зданий и сооружений — имеется фиксированный набор блоков и некоторый набор правил, но количество их разнообразных комбинаций (по сути, весь мир — комбинация блоков) просто не поддается обозрению. Это второй тип. Правда, сооружения по большинству не обладают какими-то особыми «свойствами» как объекта — кроме как типа «замкнутая коробка из блоков обеспечивает полную изоляцию от солнечного света» или «с высокой башни можно упасть и разбиться». Ну разве что портал имеет некое свойство как объект — но это свойство как раз жестко прописано для группы объектов с определенной топологией — кольцо из блоков заданного типа, заполненное блоками другого заданного типа, то есть скорее относится к первому типу. Также чисто первым типом является сборка снеговиков и големов.

                                                                                              Поскольку при крафте могут создаваться не только предметы, но и блоки, являющиеся деталями для сооружений — вот и связь цепочек, образующая три уровня подчинения игровых объектов: сооружения < — блоки-стройматериал < — блоки-сырьё. Ну или два уровня сборки.

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

                                                                                              (переписываю эту телегу уже часа три, чувствую какие-то сомнительные моменты, но не могу сформулировать)
                                                                                                0
                                                                                                Нет, погодите, перестаньте рассматривать эти 4 уровня как постулат. Процитирую статью:
                                                                                                Давайте рассмотрим определение уровня абстракции из возможных вариантов на примере гипотетической игры «трансформеры-онлайн».

                                                                                                Это говорит о том, что это не какой-то эталон, а просто пример одного из вариантов иерархии отдельно взятой механики игры, а не всей игры целиком. Этих вариантов может быть множество, а уровней для каждой механики может быть разное количество. О чем нам говорит следующая цитата из статьи:

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

                                                                                                То бишь, каждая игровая механика имеет свою иерархию слоев, и они не складываются, а идут параллельно.

                                                                                                Теперь еще раз объясняю, что значит идут параллельно.

                                                                                                Скрафтив какой-то блок, и поставив его в мире в виде блока, этот блок переходит из системы крафта (с одной иерархией слоев) в систему мира (с другой, своей иерархией уровней). Если бы иерархии этих двух механик складывались, то блок в игровом мире имел бы не только ID, но и ссылки на компоненты, из которых его скрафтили. А свойства блока определялись бы свойствами этих самых дочерних объектов.

                                                                                                В системе крафта тоже нет этих слоев, потому что предметы при крафте не сохраняют в себе ссылки на детали, из которых их крафтили. Получая новый предмет, он ложится в эту же самую плоскость в виде нового заранее прописанного предмета, не учитывая свойства деталей, из которых его скрафтили. Это просто новый, независимый предмет этого же уровня. Вот это важно, это ключевой момент.

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

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

                                                                                                Допустим, блок в майнкрафте имеет наследование, а значит хранит ссылку на родителя. Например, блок «песок» унаследован от блока «сыпучий», а значит это уже два уровня. При этому базовые характеристики хранятся в родительских атрибутах (положение и направление), а кастомные — в дочернем. А блок «рамка» помимо ссылки на родителя хранит ссылку на дочерний объект, который в него поместили (это агрегация). Это дает нам возможность менять предмет внутри рамки, меняя ее внешний вид. Вот это система уровней, а не просто ставить блоки рядышком, выдумывая новые слои.

                                                                                                Без сохранения связи между слоями, назвать это слоями можно только с точки зрения игрока, но не с точки зрения ООП, а статья именно про ООП.

                                                                                                Вы совершенно точно подметили, что важны именно отношения этих слоев, а не просто их положение относительно друг друга в одной плоскости.
                                                                                                  0
                                                                                                  Говоря, что я не встречал такой уровень, имелось в виду не то, что я не встречал четырехуровневых механик в игре, а что я не встречал именно учёт и сохранение свойств материалов в создаваемых модулях для слотов.

                                                                                                  Вот кстати еще пример игры, где довольно гибкая кастомизация, благодаря сочетанию глубины настоящих слоев, количества возможных объектов в каждом слое, и количеству их комбинаций: EVE Online.

                                                                                                  1. Космический корабль.
                                                                                                  2. В корабле помимо его собственных характеристик есть слоты под сменные модули (оружия, ускорители, щиты, реакторы и т. д.) — это агрегация.
                                                                                                  3. В модулях есть хранилище расходников (боекомплект, батарейки, наниты и т. д.), которых тоже множество видов, и которые обладают своими характеристиками. Например, разные типы ракет могут быть хороши либо против больших целей, либо против маленьких и быстрых, либо вообще против сооружений. Это тоже агрегация.

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

                                                                                                  Вот это настоящие слои, а не просто взять корпус, кучу пушек, ракет, смешать это все в тазике и получить готовый корабль с деревянными неизменяемыми характеристиками. Либо вообще не смешивать, и получить просто кучу компонентов в одной плоскости.
                                                                                                    0
                                                                                                    Это опять же предположение о программной модели. Кто знает, динамически они это всё рассчитывают, или пересчитывают полную таблицу эффектов при изменении конфигурации? Мой опыт подсказывает, что скорее всего второе. Ибо динамически рассчитывать десятки и сотни характеристик на поле боя на тысячи кораблей — это суперкомпьютер нужен.
                                                                                                      0
                                                                                                      Ну во-первых, это онлайн игра, и расчеты идут на сервере.

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

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

                                                                                                      В целом в eve online все довольно бодренько с быстродействием. В одной битве могут принимать участие тысячи кораблей
                                                                                                        0
                                                                                                        Я, как бы в курсе, what is :)
                                                                                                        Хранить таблицы расчётов характеристик — дёшево. Даже если их в realm миллионы, на одну локацию приходят тысячи. При этом остаётся только рассчитывать параметры взаимодействия. Так что хранить надо не все возможные комбинации, а только тот набор, который есть в конкретном объекте.

                                                                                                        Но даже так нагрузка не смешная. Сервер в единицу времени должен рассчитывать огромное количество параметров, ведь все эти корабли двигаются, игроки что-то делают, ещё и взаимодействуют… Недаром оно иногда лагает :)
                                                                                                          0
                                                                                                          Эти комбинации в принципе нет смысла хранить, когда есть кэш. Инвалидация кэша пускай случается при перезарядке или активации какого-либо модификатора урона, и никакой проблемы не будет.

                                                                                                          И нагрузка все таки смешная, даже без кэширования, даже для тысячи кораблей. Мой домашний процессор такое может рассчитывать без проблем, и у него еще останется, чтоб крипту майнить. А лагает оно, я уверен, из-за IO (сеть, база, и т. д.), но уж точно не из-за CPU.
                                                                                                            0
                                                                                                            Кэш — это один из вариантов хранения. Суть не в том.
                                                                                                            Ещё раз — игровые сервера обслуживают огромную массу событий, и для всякой онлайн-мясорубки главная проблема — это инпут-лаг. Проектировщики обычно стараются избавиться от любых действий, которые удлиняют цепочку обработки. Вы ещё учтите, что эти расчёты надо синхронизировать по времени между всеми участниками события.
                                                                                                            Но… мы как-то сильно удалились от темы :)
                                                                                                    0
                                                                                                    Вот теперь стало ясно, в чём наше разногласие. Говоря про уровни абстракции в конкретной игре вы имели в виду предположения об ООП-модели. Хотя не известно, как там реализованы составные блоки.

                                                                                                    Я же больше про модель, видимую пользователю, иначе говоря — бизнес-модель. Дальше вопрос будет в оптимизациях и подходах к разработке.

                                                                                                    Но если не думать о конкретных оптимизациях и предположить, что реализация сделана именно так, как она видна снаружи, то ничто не мешает подумать, что один конкретный мир в minecraft — один объект, композиция из составных элементов, между которыми ещё и взаимодействие сложное. А ещё можно разделить большой мир на фрагменты по активным связям. Т.е. на кучки блоков, не имеющих активностей между кучками.
                                                                                                    Да вообще много чего можно предположить :)
                                                                            0
                                                                            Функционал продукта при этом полностью формируется из свойств деталек и их комбинаций.

                                                                            Таким образом генерируется оружие в серии игр Borderlands. Правда, игроку этот механизм внутри игры недоступен, только в виде чита — редактора сейвов.
                                                                            При этом слова в названии тоже являются «детальками» и влияют на свойства. Есть вопрос совместимости деталек.
                                                                            Вот здесь можно поперебирать варианты blmodding.wikidot.com/gear-calculator
                                                                              0
                                                                              Ага, не правильно понял. Спасибо за разъяснение!
                                                                            +1
                                                                            Крайне трудно найти толковые вводные по ооп для тех кто уже освоил мозиловские азы, но не имеет представления как и где это применить на практике. Очень полезная статья, многие нюансы, которые даже не знаешь как поисковом запросе прописать разъяснились сами собой. Прям респектую.
                                                                              0
                                                                              Отличное изложение материала! Однозначно полезно для новичков и профессионалов!
                                                                                0
                                                                                Даже зная все выше изложенное, было очень приятно читать.
                                                                                Картинки топ, использование трансформеров топ, прям каеф! Спасибо за статью!
                                                                                  0
                                                                                  Очень уместно, что указаны синонимы к терминам или похожие механизмы. С каждым выученным языком переосмысливаешь концепты, например, для меня оказалось примечательным прототипнное наследование в JS и Lua, интерфейсы лучше раскрылись в Go.
                                                                                  И некоторые отличия теории от практики.
                                                                                  Например, часто создаётся только 1 объект класса (не про Singletone) есть странное ощущение зачем городил целый класс, а создал только один инстанс.
                                                                                  Также полях класса тоже хранят данные (например ссылки на все сущности, чтоб их потом всех по удалять) и вызывают функции класса без создания объекта.

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

                                                                                  Самое читаемое