oopНачало работы с oop


замечания

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

Вступление

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

Существует четыре основных концепции ООП

  1. наследование
  2. Полиморфизм
  3. абстракция
  4. Инкапсуляция

Эти четыре концепции вместе используются для разработки программ в ООП.

Существуют различные языки, которые поддерживают программно-ориентированное программирование. Самые популярные языки

  • C ++
  • Джава
  • C #
  • Python (Python не полностью Object Oriented, но имеет большинство функций OOP)

Введение в ООП

презентационное

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

Есть много языков программирования, которые поддерживают ООП, некоторые из популярных:

  • Джава
  • C ++
  • C #

Известно, что Python поддерживает ООП, но ему не хватает нескольких свойств.


Терминология ООП

Самый простой термин в ООП - это класс .
Класс - это в основном объект , который имеет состояние и работает в соответствии с его состоянием.

Другим важным термином является экземпляр .
Подумайте о классе как шаблоне, используемом для создания экземпляров самого себя. Класс - это шаблон, а экземпляр (ы) - это конкретные объекты.

Экземпляр, созданный из класса A , обычно упоминается как «тип A», точно так же, как тип 5 является int, а тип «abcd» - это строка .

Пример создания экземпляра с именем insance1 типа (класса) ClassA :

Джава

ClassA instance1 = new ClassA();
 

C ++

ClassA instance1;
 

или же

ClassA *instance1 = new ClassA(); # On the heap
 

питон

instance1 = ClassA()
 

Как вы можете видеть в приведенном выше примере, во всех случаях упоминалось имя класса, а после него были пустые круглые скобки (кроме C ++, если они пусты, скобки можно отбросить). В этих круглых скобках мы можем передать arguments конструктору нашего класса.

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

Пример создания экземпляра с именем b1 типа (класса) ClassB . Конструктор ClassB принимает один аргумент типа int :

Джава

ClassA instance1 = new ClassA(5);
 

или же

int i = 5;
ClassA instance1 = new ClassA(i);
 

C ++

ClassA instance1(5);
 

питон

instance1 = ClassA(5)
 

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


Функции против методов

Обе функции и методы очень похожи, но в объектно-ориентированном дизайне (OOD) каждый из них имеет свой собственный смысл.
Метод - это операция, выполняемая над экземпляром класса. Сам метод обычно использует состояние экземпляра для работы.
Между тем функция принадлежит классу, а не конкретному экземпляру. Это означает, что он не использует состояние класса или любые данные, хранящиеся в экземпляре.

Отныне мы покажем наши примеры только на Java, поскольку OOP очень ясен на этом языке, но одни и те же принципы работают на любом другом языке ООП.

В Java функция имеет слово static в своем определении, например:

// File's name is ClassA
public static int add(int a, int b) {
    return a + b;
}
 

Это означает, что вы можете вызвать его из любого места сценария.

// From the same file
System.out.println(add(3, 5));

// From another file in the same package (or after imported)
System.out.println(ClassA.add(3, 5));
 

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

Напротив, мы можем определить mehod в ClassA следующим образом:

// File's name is ClassA
public int subtract(int a, int b){
    return a - b;
}
 

После этого объявления мы можем вызвать этот метод следующим образом:

ClassA a = new ClassA();
System.out.println(a.subtract(3, 5));
 

Здесь нам нужно было создать экземпляр ClassA , чтобы вызывать его метод. Обратите внимание, что мы НЕ МОЖЕМ сделать следующее:

System.out.println(ClassA.subtract(3, 5));
 

Эта строка приведет к ошибке компиляции, вызванной тем, что мы назвали этот нестатический метод без экземпляра.


Использование состояния класса

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

class ClassB {

    private int sub_amount;

    public ClassB(int sub_amount) {
        this.sub_amount = sub_amount;
    }

    public int subtract(int a) {
        return a - sub_amount;
    }

    public static void main(String[] args) {
        ClassB b = new ClassB(5);
        System.out.println(b.subtract(3)); // Ouput is -2
    }
}
 

Когда мы запускаем этот код, создается новый экземпляр с именем b класса ClassB, а его конструктор получает значение 5 .
Конструктор теперь принимает данный sub_amount и сохраняет его как свое личное поле, также называемое sub_amount (это соглашение очень известно в Java, чтобы назвать аргументы такими же, как поля).
После этого мы печатаем на консоль результат вызова метода вычитаем из b со значением 3 .

Обратите внимание, что при реализации вычитания мы не используем this. как в конструкторе.
В Java this нужно только писать, когда есть другая переменная с тем же именем, определенным в этой области. То же самое работает с self Python.
Поэтому, когда мы используем sub_amount в вычитании, мы ссылаемся на частное поле, которое отличается для каждого класса.

Еще один пример подчеркнуть.
Давайте просто изменим основную функцию в приведенном выше коде на следующее:

ClassB b1 = new ClassB(1);
ClassB b2 = new ClassB(2);

System.out.println(b1.subtract(10)); // Output is 9
System.out.println(b2.subtract(10)); // Output is 8
 

Как мы видим, b1 и b2 независимы, и каждый из них имеет собственное состояние .


Интерфейсы и наследование

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

interface Printalbe {
    public void print();
}
 

Интерфейс Printalbe определяет метод print, но он не дает его реализации (довольно странно для Java). Каждый класс, объявляющий себя как implementing этот интерфейс, должен обеспечить реализацию метода draw. Например:

class Person implements Printalbe {

    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void print() {
        System.out.println(name);
    }
}
 

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

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

class AgedPerson extends Person {

    private int age;

    public AgedPerson(String name, int age) {
        super(name);
        this.age = age;
    }

    public void print() {
        System.out.println("Name: " + name + ", age:" + age);
    }
}
 

Происходит несколько новых вещей:

  • Мы использовали сохраненное слово extends чтобы указать, что мы наследуем Person (а также его реализацию для Printable) , поэтому нам не нужно снова объявлять о implementing Printable .
  • Мы использовали save word super для вызова конструктора Person .
  • Мы переопределили метод печати Person с новым.

Это становится довольно техничным Java, поэтому я не буду углубляться в эту тему. Но я упомянул о том, что перед началом использования их необходимо узнать о крайних случаях, связанных с наследованием и интерфейсами. Например, какие методы и функции наследуются? Что происходит с закрытыми / общедоступными / защищенными полями при наследовании от класса? и так далее.

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

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

abstract class AbstractIntStack {

    abstract public void push(int element);

    abstract public void pop();

    abstract public int top();

    final public void replaceTop(int element) {
        pop();
        push(element);
    }
}
 

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