Принципы ООП для начинающих

  • 26 марта, 12:56
  • 12179
  • 1

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

Итак, существует четыре принципа объектно-ориентированного программирования - это инкапсуляция , абстракция , наследование и полиморфизм .

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

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

Представьте, что у нас есть небольшая программа.

В ней есть несколько объектов, которые взаимодействуют друг с другом в рамках правил, определенных в программе.

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

Ключевые слова, которые применяют при инкапсуляции:

  1. State: состояние представляет данные (значение) объекта. Предположим, что банковский счет - это объект, тогда номер счета, сумма и т. д. будут состояниями  банковского счета.
  2. Private: доступ к данным возможен только внутри класса.
  3. Protected: данные, к которым можно получить доступ только внутри класса и его подклассов.
  4. Public: данные или функции (методы), к которым можно получить доступ вне класса.

Нарушение инкапсуляции

Представьте, что у нас есть однопользовательская ролевая игра с классом, который управляет квестами Quest.

public class Quest{    // possible options for status    private static final int NOT_PICKED = 0;    private static final int PICKED = 1;   private static final int COMPLETED = 2;
    // state    public String title;    public int reward;    public int status;
    // Constructor    public Quest(String title, int reward, int status){        this.title = title;        this.reward = reward;        this.status = status;   }
    // Other behavior
}


Наш  код клиента будет выглядеть примерно так:

public static void client() { Quest quest = new Quest('Goblin Slaying', 190, 3);
}


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

public static void client() {    Quest quest = new Quest('Goblin Slaying', 190, 3);   quest.reward = 1000000000;    quest.status = 5;
}


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

Исправляем наш пример

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

public class Quest {    private static final int NOT_PICKED = 0;    private static final int PICKED = 1;    private static final int COMPLETED = 2;
private String title;    private int reward;    private int status;
public Quest(String title, int reward, int status){        this.title = title;       this.reward = reward;        this.status = status;    }
    public String getTitle() {        return title;    }
    public int getReward() {        return reward;    }
    public int getStatus() {        return status;    }
    public void setQuestNotPicked(){        this.status = NOT_PICKED;    }
    public void setQuestPicked(){        this.status = PICKED;   }
    public void setQuestCompleted(){        this.status = COMPLETED;    }
   // other behavior
}


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

Наконец, наш код безопасен для "хакеров".

Преимущества инкапсуляции

  1. Инкапсуляция защищает объект от нежелательного доступа клиентов.
  2. Инкапсуляция позволяет получить доступ к уровню, не раскрывая сложных деталей ниже этого уровня.
  3. Снижает количество человеческих ошибок.
  4. Упрощает обслуживание приложения
  5. Делает приложение более понятным.

Советы по инкапсуляции

Для лучшей инкапсуляции данные объекта почти всегда должны быть ограничены privateили protected. Если вы решили установить уровень доступа public, убедитесь, что вы понимаете последствия этого выбора.

Абстракция

Абстракция - это концепция объектно-ориентированного программирования, которая «показывает» только основные атрибуты и «скрывает» ненужную информацию.

Абстракция - это расширение инкапсуляции, но в чем разница?

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

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

Абстрактный класс - это просто обычный класс, у которого есть несколько особых исключений:

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

Итак, зачем  использовать такие классы?

Что ж, они позволяют нам делать несколько вещей:

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

Давайте посмотрим на простой пример:

//abstract parent class
abstract class Animal{   //abstract method   public abstract void sound();
}
//Dog class extends Animal class
public class Dog extends Animal{
   public void sound(){    System.out.println("Woof");   }   public static void main(String args[]){    Animal obj = new Dog();    obj.sound();   }
}


Абстрактный класс против конкретного класса

  1. Абстрактный класс бесполезен, пока он не будет расширен другим классом.
  2. Если вы объявляете абстрактный метод в классе, вы также должны объявить абстрактный класс. В конкретном классе не может быть абстрактного метода. И наоборот: если класс не имеет абстрактного метода, он также может быть отмечен как абстрактный.
  3. Он также может иметь неабстрактный метод (конкретный).

Интерфейсы

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

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

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

public interface DogInterface {    public void bark();    public void wiggleTail();    public void eat();
}


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

public class LabradorRetriever implements DogInterface {
public void bark() {        System.out.println("Woof, woof!!");        }
public void wiggleTail() {       System.out.println("wiggles tail");    }
    public void eat(){        System.out.println("eats food");    }
}


Вы можете спросить: «Зачем мне вообще нужен интерфейс?»

Вкратце, интерфейсы гарантируют, что у класса будет x методов.

Это может показаться слишком простым, но на самом деле это так.

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

Один из способов сделать это - создать общий интерфейс, TransportInterfaceкоторый будет использоваться всеми транспортными средствами (самолетом, автомобилем и т. д.). Таким образом, нашему клиентскому коду не придется беспокоиться, если у класса Airplaneне будет общего метода.

Что использовать? Абстрактные классы или интерфейсы?

Как всегда, зависит от обстоятельств.

Следует учитывать множество различных факторов.

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

Возвращаясь к абстракции

В двух словах:

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

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

Альтернативный текст

Наследование - это механизм базирования объекта или класса на другом объекте или классе с сохранением аналогичной реализации.

Распространенная проблема в программировании состоит в том, что объекты чертовски похожи.

Объекты часто очень похожи, имеют общие функции, но не совсем одинаковы.

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

« Могу ли я создать совершенно новый класс или поместить все общие функции в базовый класс?»

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

Пример школы

Представьте, что нам было поручено создать систему управления школой, и у нас есть две сущности, которые называются «Студент» и «Учитель». В нашей первой версии приложения мы реализовали их так:

public class Student {    private String name;   private String last_name;   private String gender;   private String classroom;    private String year;
    public void attendClass(){        System.out.println("Attend Class");    }
    public void eatSandwich(){        System.out.println("Eating a sandwich");    }
}


public class Teacher {    private String name;    private String last_name;    private String gender;    private String schedule;    private String subject;
    public void teachClass(){        System.out.println("Teaching a class");    }
    public void eatSandwich(){        System.out.println("Eating a sandwich");    }
}


Как видите, существует много общих состояний и поведения, как насчет того, чтобы извлечь все это в общий класс, называемый Human

public class Human {   protected String name;    protected String last_name;    protected String gender;
    public void eatSandwich(){        System.out.println("Eating a sandwich");    }
}


При этом мы можем наследовать все общие состояния и поведение в наш Studentи Teacherкласс.

public class Student extends Human {    private String classroom;    private String year;
    public void attendClass(){        System.out.println("Attend Class");    }
}


public class Teacher extends Human {    private String schedule;    private String subject;
    public void teachClass(){        System.out.println("Teaching a class");    }
}


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

Возвращение к наследству

Наследование не так просто, на самом деле существуют разные типы наследования.

  1. Одиночное наследование : это простейший вид наследования, при котором мы наследуем от родительского класса. Как и в нашем примере выше с учителями и учениками.
  2. Многоуровневое наследование : наследование становится многоуровневым, когда оно наследуется от подкласса. Например, давайте представим из нашего примера выше, что мы добавили класс с именем Intern. Intern имеет то же состояние и поведение, что и, Teacherно с некоторыми дополнительными полями, поэтому мы просто наследуем от Teacher. Это множественное наследование, потому что теперь класс Internнаследуется от обоих Teacherи Human.
  3. Множественное наследование : происходит, когда класс наследует более одного родительского класса. Обычно это не разрешено в большинстве языков программирования, но доступно в некоторых.
  4. Иерархическое наследование : это когда несколько дочерних классов наследуются от одного и того же родительского класса. Например, у нас есть класс, Dogи Catоба они наследуются от класса Animal.
  5. Гибридное наследование : когда существует комбинация нескольких форм наследования, это называется гибридным наследованием.

Преимущества наследования

  1. Возможность повторного использования кода
  2. Один суперкласс может использоваться для количества подклассов в иерархии.
  3. Никаких изменений во всех базовых классах делать нельзя, просто вносите изменения только в родительский класс.
  4. Наследование позволяет избежать дублирования и избыточности данных.
  5. Наследование используется, чтобы избежать пространственной и временной сложности.

Меры предосторожности

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

Полиморфизм

Полиморфизм - это способность объекта принимать разные формы.

Разберем слово:

  1. Поли = много: многоугольник = многосторонность, полистирол = много стиролов, многоязычный = много языков и т. д.
  2. Морф = изменение или форма, морфология = изучение биологической формы, Морфеус = греческий бог снов, способный принимать любую форму.

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

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

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

Сделав класс ответственным за свой код, а также за свои данные, вы можете добиться полиморфизма. В этом примере у каждого класса будет своя собственная Draw()функция, а клиентский код может просто:

shape.Draw()


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

Типы полиморфизма

Два типа полиморфизма - это динамический полиморфизм и статический полиморфизм .

Динамический полиморфизм

  1. Полиморфизм времени выполнения
  2. Динамическое связывание
  3. Привязка во время выполнения
  4. Поздняя привязка
  5. Переопределение метода

Это ваш стандартный полиморфизм. Это в основном, когда у подкласса есть метод с тем же именем и параметрами метода в родительском классе, эти методы находятся в разных формах (переопределение метода).

Например, давайте проиллюстрируем наш пример фигур выше.

public class Shape {    protected int width;    protected int height;
    // some more functionality for shape
    public void draw(){        // draws a shape using the width and height.    }
}


public class Circle extends Shape {    private int radius;
    @Override    public void draw() {        // draw a circle code    }
}


public class Square extends Shape {
    @Override    public void draw() {        // specifically draw a square    }
}


Наш код будет выглядеть примерно так:

public static void main(String[] args) {        Shape shape = new Shape();        Shape circle = new Circle();        Shape square = new Square();
        shape.draw(); // calls the draw method in shape class        circle.draw(); // calls the draw method in circle class        square.draw(); // calls the draw method in square class    }


Как видите, и наш, Circleи Squareкласс наследуются от Shapeкласса. Они также переопределяют draw()метод в Shapeклассе.

Статический полиморфизм

Это также известно как:

  1. Полиморфизм времени компиляции
  2. Статическая привязка
  3. Привязка во время компиляции
  4. Раннее связывание
  5. Перегрузка метода

Это в основном, когда метод имеет несколько форм в одном классе.

Давайте посмотрим на пример:

public class Calculator {    void add(int a, int b){        System.out.println(a+b);   }    void add(int a, int b, int c) {        System.out.println(a+b+c);   }
}


public static void main(String args[]) {        Calculator calculator = new Calculator();
        // method with two parameters gets called        calculator.add(10, 20); // output: 30
        // method with three parameters get called        calculator.add(10, 20, 30); //output: 60    }


Преимущества полиморфизма

  1. Помогает программисту повторно использовать коды, т. е. Однажды написанные, протестированные и реализованные классы могут быть повторно использованы по мере необходимости. Экономит много времени.
  2. Одну переменную можно использовать для хранения нескольких типов данных.
  3. Легко отлаживать коды.

Вывод

Четыре принципа объектно-ориентированного программирования:

  1. Инкапсуляция : означает скрытие данных, и использование getterиsetter
  2. Абстракция : скрывает реализацию с использованием интерфейсов и абстрактных классов.
  3. Наследование : механизм базирования объекта или класса на другом объекте или классе с сохранением аналогичной реализации.
  4. Полиморфизм : способность объекта принимать разные формы.

Поздравляем, теперь вы можете ответить на один из самых задаваемых вопросов в интервью.


1 комментарий
Сортировка:
Добавить комментарий
Александра
Александра 2022, 14 марта, 19:17
1

Одна из лучших статей о принципах ООП!
Все предельно понятно и легко.
Спасибо большое!
 


IT Новости

Смотреть все