Почему объекты Java и C # лучше соответствуют неясным философским представлениям о сущности 17-го века, чем здравому смыслу

Объектно-ориентированное программирование - это вычислительная парадигма, реализованная в широко используемых языках программирования, таких как Java и C #. В разговорной речи это часто описывается следующими интуитивно правдоподобными идеями:

  • Самые простые существа во Вселенной - это особые объекты.
  • Объекты принадлежат к видам, математически представленным как классы
  • У объектов есть состояния и поведение, которые математически представлены полями и методами.

Состояния и поведение могут принадлежать только вещам, которые сами принадлежат - в другом смысле - к видам. Чтобы обеспечить это, объектно-ориентированное программирование требует, чтобы поля и методы для типов объектов были присоединены к ассоциированному классу. Обычно это достигается путем помещения кода для методов и полей, которые объект данного класса может иметь, в скобки {}, которые дают код для класса в целом. Когда это происходит, поля и методы считаются инкапсулированными в своем классе.

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

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

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

1 Объекты объектно-ориентированного программирования не являются объектами

class Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

    void changeCadence(int newValue) {
         cadence = newValue;
    }

    void changeGear(int newValue) {
         gear = newValue;
    }

    void speedUp(int increment) {
         speed = speed + increment;   
    }

    void applyBrakes(int decrement) {
         speed = speed - decrement;
    }

    void printStates() {
         System.out.println("cadence:" +
             cadence + " speed:" + 
             speed + " gear:" + gear);
    }
}

Выше приведен пример велосипедного класса из Java Tutorials, серии руководств по изучению языка Java, выпущенных Oracle, которые владеют правами на этот язык с момента приобретения Sun Microsystems, создателя языка, в 2010. Полями класса являются каденция, скорость и передача. В этом случае кажется, что поля вписываются в (не исчерпывающий) список свойств, которыми может обладать велосипед.

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

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

2 Что такое объекты ООП?

Что же тогда является объектами объектно-ориентированного программирования? А еще лучше: что делает такие объекты каким-то образом унифицированными, а не случайные связки кода? И соответственно, что заставляет одни объекты ООП лучше соответствовать идеалу объекта ООП, чем другие?

Чтобы ответить на этот вопрос, давайте рассмотрим еще один пример:

public class Card {
    private final int rank;
    private final int suit;

    // Kinds of suits
    public final static int DIAMONDS = 1;
    public final static int CLUBS    = 2;
    public final static int HEARTS   = 3;
    public final static int SPADES   = 4;

    // Kinds of ranks
    public final static int ACE   = 1;
    public final static int DEUCE = 2;
    public final static int THREE = 3;
    public final static int FOUR  = 4;
    public final static int FIVE  = 5;
    public final static int SIX   = 6;
    public final static int SEVEN = 7;
    public final static int EIGHT = 8;
    public final static int NINE  = 9;
    public final static int TEN   = 10;
    public final static int JACK  = 11;
    public final static int QUEEN = 12;
    public final static int KING  = 13;

    public Card(int rank, int suit) {
        assert isValidRank(rank);
        assert isValidSuit(suit);
        this.rank = rank;
        this.suit = suit;
    }

    public int getSuit() {
        return suit;
    }

    public int getRank() {
        return rank;
    }

    public static boolean isValidRank(int rank) {
        return ACE <= rank && rank <= KING;
    }

    public static boolean isValidSuit(int suit) {
        return DIAMONDS <= suit && suit <= SPADES;
    }

    public static String rankToString(int rank) {
        switch (rank) {
        case ACE:
            return "Ace";
        case DEUCE:
            return "Deuce";
        case THREE:
            return "Three";
        case FOUR:
            return "Four";
        case FIVE:
            return "Five";
        case SIX:
            return "Six";
        case SEVEN:
            return "Seven";
        case EIGHT:
            return "Eight";
        case NINE:
            return "Nine";
        case TEN:
            return "Ten";
        case JACK:
            return "Jack";
        case QUEEN:
            return "Queen";
        case KING:
            return "King";
        default:
            //Handle an illegal argument.  There are generally two
            //ways to handle invalid arguments, throwing an exception
            //(see the section on Handling Exceptions) or return null
            return null;
        }    
    }
    
    public static String suitToString(int suit) {
        switch (suit) {
        case DIAMONDS:
            return "Diamonds";
        case CLUBS:
            return "Clubs";
        case HEARTS:
            return "Hearts";
        case SPADES:
            return "Spades";
        default:
            return null;
        }    
    }

    public static void main(String[] args) {
    	
    	// must run program with -ea flag (java -ea ..) to
    	// use assert statements
        assert rankToString(ACE) == "Ace";
        assert rankToString(DEUCE) == "Deuce";
        assert rankToString(THREE) == "Three";
        assert rankToString(FOUR) == "Four";
        assert rankToString(FIVE) == "Five";
        assert rankToString(SIX) == "Six";
        assert rankToString(SEVEN) == "Seven";
        assert rankToString(EIGHT) == "Eight";
        assert rankToString(NINE) == "Nine";
        assert rankToString(TEN) == "Ten";
        assert rankToString(JACK) == "Jack";
        assert rankToString(QUEEN) == "Queen";
        assert rankToString(KING) == "King";

        assert suitToString(DIAMONDS) == "Diamonds";
        assert suitToString(CLUBS) == "Clubs";
        assert suitToString(HEARTS) == "Hearts";
        assert suitToString(SPADES) == "Spades";

    }
}

Выше приведен пример класса Card, снова взятый из Руководств по Java Oracle. Давай пройдемся через это.

После имени класса и начальной фигурной скобки объявляются два поля: rank и suit. После этого следуют всевозможные костюмы, затем виды рангов. За ним следует специальный метод, называемый конструктором, который может использоваться другими объектами для создания экземпляра класса Card, а затем пары методов getSuit () и getRank (), которые соответственно предоставляют или возвращают значение масти и ранга объекта карты любому объекту, использование или вызов этих методов. Эта пара функций принадлежит к большему классу методов, называемых геттерами, чьи имена и функциональность соответствуют давнему соглашению о кодировании. За ним следуют два логических метода isValidRank () и isValidSuit (), методы suitToString () и rankToString () и метод main (). Логические методы, названные в честь логика Джорджа Буля, называются так потому, что возвращаемые ими значения - «истина» и «ложь», которые представлены числами 1 и 0 в двузначной булевой системе счисления, которую используют все компьютеры. Методы ToString () преобразуют целочисленный тип данных в соответствующую строку букв. Например, если указано число 2, метод rankToString () возвращает строку «Два». Обычная передовая практика для реальных Java-приложений помещает основной метод в отдельный, отдельный класс Main, поэтому мы пока оставим этот метод в стороне.

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

3 объекта ООП как монады

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

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

4. Вывод

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

Первоначально опубликовано на http://jacobarchambault.com 11 мая 2019 г.

📝 Прочтите этот рассказ позже в Журнале.

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