Зачем нужно понимать ООП



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

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

    Примеры кода буду приводить из iOS разработки.

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

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

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

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


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

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

    Это и есть принцип наследования, где каждый админ/VIP-клиент/аноним являются пользователями, но не каждый пользователь должен быть админом или VIP-пользователем.

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

    Еще пример ошибочной трактовки принципа наследования, это когда базовый класс и наследник являются представителями разных логических групп. Выглядит это следующим образом, реализуем MVC в iOS проекте, где UIViewController это Controller с абстрактными методами, которые должен реализовать наследник. А наследник — это уже Model. Там где по логике проектирования должно быть взаимодействие между двумя группами классов, один класс становиться одновременно и Model и Controller. Не говорим уже о том, что UIViewController в реалиях iOS разработки еще и берет на себя роль View. В итоге мы получаем один объект, который делает все сам. Если у нас есть пользователь (User), то он будет и Controller и View одновременно.

    Пример наследования в iOS
    /**
        Нужно сделать экран профиля пользователя, в котором отображаются имя и фамилия. Этот экран должен переиспользоваться.
     */
    
    // INCORRECT
    
    class UserProfileViewController: UIViewController {
    
        // MARK: - IBOutlets
    
        @IBOutlet private var firstNameLabel: UILabel!
        @IBOutlet private var lastNameLabel: UILabel!
    
        /**
        Заполнение данных оставляем классу наследнику в виде абстрактных методов
         */
    
        // MARK: - Abstract methods
    
        func firstName() -> String? {
            return nil
        }
    
        func lastName() -> String? {
            return nil
        }
    
        // MARK: - Lifecycle
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            firstNameLabel.text = firstName()
            lastNameLabel.text = lastName()
        }
    }
    
    /**
        Создаем класс-наследник, который отвечает за функцию заполнения данных. В таком случае, наследник будет выполнять роль не только UIViewController, а и роль модели, которая предоставляет данные для отображения.
     */
    
    class UserProfileModel: UserProfileViewController {
        override func firstName() -> String? {
            return "Name"
        }
    
        override func lastName() -> String? {
            return "Last name"
        }
    }
    
    // CORRECT
    
        /**
        Корректней будет, добавить новый класс-модель, которая будет предоставлять данные.
         */
    
    class UserProfileModel {
        func firstName() -> String? {
            return "Name"
        }
    
        func lastName() -> String? {
            return "Last name"
        }
    }
    
    /**
        В таком случае у нас будут два отдельных класса, каждый из которых имеет свою зону ответственности.
     */
    
    class UserProfileViewController: UIViewController {
    
        var model: UserProfileModel?
    
        // MARK: - IBOutlets
    
        @IBOutlet private var firstNameLabel: UILabel!
        @IBOutlet private var lastNameLabel: UILabel!
    
        // MARK: - Lifecycle
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setupUserInfo()
        }
    
        // MARK: - Private
    
        private func setupUserInfo() {
            firstNameLabel.text = model?.firstName()
            lastNameLabel.text = model?.lastName()
        }
    }
    
    // PERFECT
    
    /**
        Еще лучше, взаимодействие между двумя типами классов, контроллер и модель, сделать через протокол, чтобы можно было создавать и использовать разные модели.
     */
    
    protocol UserProfileProtocol {
        func firstName() -> String?
        func lastName() -> String?
    }
    
    class UserProfileViewController: UIViewController {
    
        var model: UserProfileProtocol?
    
        // MARK: - IBOutlets
    
        @IBOutlet private var firstNameLabel: UILabel!
        @IBOutlet private var lastNameLabel: UILabel!
    
        // MARK: - Lifecycle
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setupUserInfo()
        }
    
        // MARK: - Private
    
        private func setupUserInfo() {
            firstNameLabel.text = model?.firstName()
            lastNameLabel.text = model?.lastName()
        }
    }
    
    class UserProfileModel: UserProfileProtocol {
        
        // MARK: - UserProfileProtocol
        
        func firstName() -> String? {
            return "Name"
        }
    
        func lastName() -> String? {
            return "Last name"
        }
    }
    


    Представляю эту картину в реальной жизни, у нас есть регистратура, где некий администратор ведет записи о посетителях в тетрадку. Также в этой тетради можно читать данные о посетителях. Мы получаем посетителя — Model, человека в регистратуре — Controller и тетрадь View. И в вышеуказанной интерпретации принципа наследования мы получаем, что посетитель всегда является регистратором и тетрадкой. Уже дико выглядит то, что человек и тетрадка одно целое. И логика наследования нарушается. Если рассматривать, что посетитель и регистратор — люди и принять тот факт, что представитель класса регистратора может стать посетителем, то логичней сказать, что регистратор является наследником посетителя.

    Это пример, когда непонимание принципа наследования приводит к сложности понимания системы.

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

    Абстракция


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

    Абстракция гласит — останавливаем внимание на важных и необходимых аспектах объекта и игнорируем ненужные для нас.

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

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

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

    Также к абстракции я бы отнес декомпозицию, когда сложный объект разбивается на систему. Мы абстрагируемся от некоторых особенностей и переносим их в отдельный компонент. Пример: пользователь у которого есть место проживания, то есть адрес. Адрес в свою очередь состоит из города, улицы, номера дома и т. д. В этот момент мы думаем, а нужно ли указывать страну или регион? Если это приложения для пользования администрацией конкретного района города, то можно упустить такие детали. В итоге мы получаем пользователя, который абстрагируется от некоторых деталей адреса. Опять-таки, непонимание того, что мы не только пишем код, но и занимаемся моделированием, приводит к тому, что у нас есть, допустим, MenuViewController, который состоит из 5000+ строк кода.

    Пример абстракции через декомпозицию
    
    /**
        Распространенная ситуация: создаем класс, к примеру, простую модель пользователя. Но с добавлением функционала, все больше появляется полей и методов в этом классе.
     */
    
    // INCORRECT
    
    class User {
        let firstName: String
        let lastName: String
        let fullName: String
        let age: Int
        let birthday: Date
        let street: String
        let postalCode: Int
        let city: String
    
        var phoneNumber: String?
        var phoneCode: String?
        var phoneFlag: UIImage?
    
        var isLoggined: Bool = false
    
        var isAdmin: Bool = false
    
        // MARK: - Init
        
        init(firstName: String,
             lastName: String,
             fullName: String,
             age: Int,
             birthday: Date,
             street: String,
             postalCode: Int,
             city: String) {
    
            self.firstName = firstName
            self.lastName = lastName
            self.fullName = fullName
            self.age = age
            self.birthday = birthday
            self.street = street
            self.postalCode = postalCode
            self.city = city
        }
    
        // MARK: - Admin functionality
        
        func createNewReport() {
            guard isAdmin else { return }
            
            print("New report created")
        }
        
        func updateReport(for user: User) {
            guard isAdmin else { return }
            
            print("Update report for \(user.fullName)")
        }
    
    }
    
    // CORRECT
    
    /**
        Правильней будет, декомпозировать код, абстрагируя части большого сложного класса на маленькие компоненты.
     */
    
    class Address {
        let street: String
        let postalCode: Int
        let city: String
    
        init(street: String,
             postalCode: Int,
             city: String) {
    
            self.street = street
            self.postalCode = postalCode
            self.city = city
        }
    }
    
    class Name {
        let firstName: String
        let lastName: String
    
        init(firstName: String,
             lastName: String) {
    
            self.firstName = firstName
            self.lastName = lastName
        }
    
        var fullName: String {
            firstName + " " + lastName
        }
    }
    
    class PhoneNumber {
        let phone: String
        let code: String
        let flag: UIImage
    
        init(phone: String,
             code: String,
             flag: UIImage) {
            self.phone = phone
            self.code = code
            self.flag = flag
        }
    }
    
    class User {
    
        /**
        В результате, класс User уменьшился в размерах, при этом мы абстрагируемся от деталей имени и адреса.
         */
    
        let name: Name
        let address: Address
        let birthday: Date
    
        var phoneNumber: PhoneNumber?
    
        init(name: Name,
             address: Address,
             birthday: Date) {
            self.name = name
            self.address = address
            self.birthday = birthday
        }
    }
    
    /**
        Так как после логина система получает залогиненого Пользователя, то класс User не должен отвечать за состояния системы. За статус логина будет отвечать новая сущность, тем самым система абстрагируется от деталей логики этого статуса.
     */
    
    class LoginSession {
        var user: User?
    
        var isLoggined: Bool {
            user != nil
        }
    }
    
    /**
    Дополнительные свойства Администратора выносяться в класс-наследник Пользователя.
     */
    
    class Admin: User {
        func createNewReport() {
            print("New report created")
        }
        
        func updateReport(for user: User) {
            print("Update report for \(user.fullName)")
        }
    }
    


    Полиморфизм


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

    Полиморфизм плавно вытекает из наследования. Гласит он следующее: можно создавать классы наследники, которые будут имитировать интерфейс базового класса, но со своей собственной реализацией. Этот принцип отражается в таком принципе SOLID как принцип Барбары Лисков: мы можем подставлять объекты классов наследников там, где предполагается использование базового класса, при этом замена не должна никак себя проявлять.

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

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

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

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

    Летающий автомобиль

    Добавлю, что полиморфизм тесно связан с наследованием и проблемы в наследовании отзываются еще большими проблемами в полиморфизме.

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


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

    Первый вариант иногда воспринимается как инкапсуляция = сокрытие, что, как я считаю, не совсем верное понимание.

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

    К примеру, я разрабатываю кнопку, которая в момент нажатия становится немного светлее. Проще говоря, когда кнопка в состоянии “нажата”, цвет фона становится светлее.

    Для этого я создаю класс наследник UIButton и в наследнике добавляю метод, который устанавливает цвет кнопки в цвет с альфа каналом 50% от оригинального. А также я добавляю метод, который возвращает бэкграунд цвет кнопки в оригинальный, без альфа канала.

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

    Пример кнопки с открытыми методами, которые могут нарушить повидение этой кнопки
    /**
        Создаем кнопку, у которой при нажатии цвет бэкграунда устанавливается в оригинальный цвет но с альфаканалом 0,5
     */
    
    // INCORRECT
    
    class Button: UIButton {
    
         /**
        Добавляем два метода, которые устанавливают цвет бекграунда для состояния нажатой кнопки и нормального состояния кнопки
         */
    
        func decorateSelected() {
            backgroundColor = backgroundColor?.withAlphaComponent(0.5)
        }
        
        func decorateDeselected() {
            backgroundColor = backgroundColor?.withAlphaComponent(1)
        }
        override var isSelected: Bool {
            didSet {
                if isSelected {
                    decorateSelected()
                } else {
                    decorateDeselected()
                }
            }
        }
    }
    
    // SAMPLE
    
     /**
        Проблемой будет то, что методы, декорирующие кнопку в разных состояних, являются публичными. А это значит, что можно нарушить логику работы кнопки, вызвав метод в неправильный момент.
     */
    
    let button = Button()
    button.decorateSelected()
    
    // CORRECT
    
    class Button: UIButton {
        
        override var isSelected: Bool {
            didSet {
                if isSelected {
                    decorateSelected()
                } else {
                    decorateDeselected()
                }
            }
        }
    
         /**
        Мы сделали методы, настраивающие внешний вид кнопки, приватными, тем самым обеспечили правильную логику отображения.
         */
    
        // MARK: - Private
    
        private func decorateSelected() {
            backgroundColor = backgroundColor?.withAlphaComponent(0.5)
        }
        
        private func decorateDeselected() {
            backgroundColor = backgroundColor?.withAlphaComponent(1)
        }
    }
    
    // PERFECT
    
     /**
        Но! У кнопки остаеться возможнось измененить цвет через базовое поле var backgroundColor: UIColor?. Поэтому, немного заморочившись, делаем невозможным менять цвет в момент, когда кнопка нажата.
     */
    
    class Button: UIButton {
            
        override var backgroundColor: UIColor? {
            get {
                super.backgroundColor
            }
            set {
                if isHighlighted == false {
                    super.backgroundColor = newValue
                }
            }
        }
        
        override var isHighlighted: Bool {
            willSet {
                if newValue {
                    decorateSelected()
                }
            }
            
            didSet {
                if isHighlighted == false {
                    decorateDeselected()
                }
            }
        }
    
        // MARK: - Private
    
        private func decorateSelected() {
            backgroundColor = backgroundColor?.withAlphaComponent(0.5)
        }
        
        private func decorateDeselected() {
            backgroundColor = backgroundColor?.withAlphaComponent(1)
        }
    }
    


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

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

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

    Заключение


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

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 10 037 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

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

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

      А то, что полноценное наследование — большой геморрой, стало понятно после появления C++: мало того, что понимание работы класса требует изучить весь граф предков, так ещё и внесение изменений в один из классов-предков может самым неожиданным образом сломать работу потомков — код становится крайне хрупким.

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

      Единственная реальная причина использования наследования в современных ООП-языках — уменьшение дублирования кода. Но и это можно решить без наследования: например, через типажи (trait) и обобщённое программирование (generic).

      P.S. Для моделирования многокомпонентных систем лучше всего подходит парадигма компонентно-ориентированного программирования. ООП-же лишь имитирует модульность.
        +3
        ООП через композицию обеспечивает такую же мощность кода, но без геморроя, вызванного наследованием.
        Сравнение кастрюли со сковородкой. Композиция и наследование — это РАЗНЫЕ инструменты для решения РАЗНЫХ задач. Если вам по предметной области нужно именно наследование, то решение этой же задачи через композицию, как правило, приводит к огромным объёмам boilerplate-кода, в котором неизбежно будут стадами пастись баги. И в результате код будет более «правильным» но менее рабочим.
          0
          Ну почему же. Задача ровно та же самая, переиспользование кода.
          А будет там бойлерплейт или нет — зависит от языка. На плюсах будет, да.
          Но даже на них я предпочту избыточность неопределенности.
            0
            Если вам по предметной области нужно именно наследование, то решение этой же задачи через композицию, как правило, приводит к огромным объёмам boilerplate-кода, в котором неизбежно будут стадами пастись баги.

            Если этот бойлерплейт генерируется, скажем, макросами, то не будут.

              0
              Я правильно понимаю — вы предлагаете использовать макросы для генерации бойлерплейт-кода, чтобы эмулировать наследование, лишь бы не использовать настоящее наследование?
            +1

            Наследование нужно для моделирования отношений "является" между объектами и классами.

              –1
              Зря вы так.

              Вы не поверите, но в «начале» в ООП вообще не было никаких классов. Классы появились после появления отдельных реализаций парадигмы.

              И в этих реализациях наследование описывает иерархию классов и только. Причём здесь отношения между классами и объектами?
                0
                Отношения играют роль, потому что как показывает моя практика (не мировая статистика, но все же), если связать наследованием классы только по общему поведению, то потом непременно в дочернем классе какой-то метод родителя делает не то что нужно и его нужно переопределять полностью, либо глушить (а тут уже SOLID нарушился), кое кто выделяет базовые классы, но такая иерархия однобокая и создана она лишь для удовлетворения нужд одного дочернего класса. Это потом приведет к проблеме, если появится еще один ребенок (а он появится, поверьте). Но тут есть проблема пострашнее, если изменятся безнес требования, и их нужно будет внести в базовый класс, вот тут вы и поймете что жизнь — боль, особенно если дочерних классов уже много. Вобщем и отношение «является» не всегда серебряная пуля, я бы тут наследовался если бы понимал что оно «является» и класс по моей бизнес модели уточняющий. Иначе я бы предпочел композицию.
                  +1

                  В каком "начале"?


                  Object subclass: #Account.

                  Это уже не начало?

                  0

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

                    0

                    Можно, кто же спорит, но как по мне код типа


                    class User {
                    }
                    
                    class Admin extends User {
                    }

                    более читабельный и поддерживаемый в большинстве случаев чем


                    interface User {
                    }
                    
                    interface Admin extends User {
                    }
                    
                    class UserImplementation  implements User {
                    }
                    
                    class AdminImplementation implements Admin {
                    }
                      +2

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

                        0

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

                +1
                вытекает с из наследования

                регистратору с из другого отдела


                Здеь следует использовать предлог «из» вместо «с».
                  0

                  Там половина текста состоит из ошибок с ться/тся. Уже после половины статьи не мог всерьёз её воспринимать. Ощущение такое, как будто писал или студент для курсовой или вчерашний студент

                  +1

                  "Само по себе" не работает. Инкапсуляция — это именно адекватное объединение данных и кода, с ними рабоащего в один объект. Низкая связанность, высокая связность (не перепутал?) — вот это вот всё

                    0

                    Оффтоп.


                    Опять 25. Ещë одна статья, в которой автор решил, что все вокруг непоняли ооп и счëл долгом объяснит всë решительно последний раз.


                    Алана Кея упямянули хоть?

                      0
                      Наш мозг воспринимает мир как набор объектов, которые взаимодействуют друг с другом.

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

                      Если функция — просто действие без контекста, то метод класса это уже действие в определенном контексте, это действие, которое относится к определенному объекту.

                      В функциональном программировании функция действует в контексте, просто этот контекст вся программа (или модуль).

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

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

                      Представляю эту картину в реальной жизни, у нас есть регистратура, где некий администратор ведет записи о посетителях в тетрадку. Также в этой тетради можно читать данные о посетителях. Мы получаем посетителя — Model, человека в регистратуре — Controller и тетрадь View. И в вышеуказанной интерпретации принципа наследования мы получаем, что посетитель всегда является регистратором и тетрадкой. Уже дико выглядит то, что человек и тетрадка одно целое.

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

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

                      Дело в том, что моделирование вообще в принципе не может полностью описать реальность. И иногда для того, что бы необходимый кусок реальности описать, приходится отходить от принципов хорошего программирования (солид и прочее). Что будет эффективнее (с точки зрения затрат), добавить 10 классов и 20 функций, но сохранить красивую картинку наследования или одно поле и 5 проверок? Вот отсюда и растут ноги у всяких странных функций выведенных в базовые объекты. Естественно, такое лепить всюду нельзя, надо думать в первую очередь о последствиях, но иногда можно нагрязнить.

                      А для моделирования лучше всего подходит ООП парадигма

                      А вот тут любители функциональщины не согласятся :)
                        0
                        А для моделирования лучше всего подходит ООП парадигма

                        А вот тут любители функциональщины не согласятся :)

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

                            a = F/m — моделирование нерелятивистского движения тела в инерциальных системах отсчёта )

                          –5

                          ООП — это плохо. У этого человека много качественных видео, где это детально объяснено с примерами.
                          https://youtu.be/QM1iUe6IofM


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

                            +1

                            Забавно выглядит "в современных языкАХ" и только один пример, Go бы добавили сразу )

                              –4
                              Go

                              Мамонт со сборкой мусора

                                0
                                Сборка мусора в лиспе была. Это 60-е, кажется.
                                А вот CSP — уже конец 70-х.
                            +1
                            Неплохая статья с еще одной попыткой обьяснить принципы ООП на примере iOS разработки) Повторение мать учения)
                              0
                              Выскажу непопулярное мнение на счет ООП, может кого-нибудь заинтересует. Надо стараться воздерживаться от активного использования терминологии ООП (неважно какого именно языка, рантайма исполнения, стиля). Например любую задачу рассматривать оперируя только интерфейсами сущностей (ответственностью) и реализациями этих интерфейсов. Некоторые плюсы такого подхода — вы не зависите от языка программирования и стиля на текущем проекте, можете говорить на одном языке с аналитиком.
                              Из минусов — возможно может быть не комфортно общаться на собеседованиях
                                0
                                Забавно, но на картинке где «ООП, которое пользователи заявляют» идеальный пример как не стоит моделировать. Вместо иерархии классов достаточно иметь один класс где ноги, тип животное/человек, блохи — это всего лишь аттрибуты.
                                  0

                                  И как выявить тот факт, что человек — это животное и блох у него не может быть в принципе?

                                    0
                                    В принципе могут . А как имея базовый класс oAnimal выяснить этот же факт? В простом дизайне по дефолту fleas = 0 и это уже зависит от контекста каким образом это значение может/не может измениться.
                                      0

                                      Мы про конкретную модель


                                      oHuman instanceof oAnimal или типа того есть во всех ООП языках, что я встречал, а fleas нет ни в oHuman, ни в oAnimal

                                        0
                                        И тут выясняется, что у людей они таки есть и что возможно кроме oPet, могут быть другие наследники oAnimal (oWild) у которых тоже могут быть блохи.

                                        Кроме того выяснять наличие блох с помощью instanceof — это прямое нарушение LSP.

                                        Если моделирование бизнес области требует, можно заменить блох на котейнер «Паразиты», где блохи один из возможных вариантов.
                                          0

                                          В реальном мире они может и есть, но в нашей модели их нет, и "нет" значит быть не может, а не нулевое количество.


                                          В чём нарушение? Компилятор или что там у нас вместо него не даст вызвать thing.fleas на инстансах, объявленных как oAnimal или oHuman.

                                            0
                                            но в нашей модели их нет, и «нет» значит быть не может

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

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

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