Что такое ООП и с чем его едят?

Из своего опыта могу сказать что всегда считал что понимал ООП, что же тут такого то — полиморфизм, инкапсуляция и наследование, но вот когда дошло до дела, то туговато пришлось. Хочу разложить все по полочкам что бы никто не наступил на мои грабли в будущем :)

 

 

3-principa-oop

Шаг 1.

Немного теории:

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

В центре ООП находится понятие объекта.

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

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

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

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

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

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

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

Полиморфизм — это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

Шаг 2.

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

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

images

Зачем же это нужно?

Думаю вам бы не хотелось что бы кто-то, что-то изменял в написанной вами библиотеки.

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

Цель инкапсуляции — уйти от зависимости внешнего интерфейса класса(то , что могут использовать другие классы) от реализации. Чтобы малейшее изменение в классе не влекло за собой изменение внешнего поведения класса. Давайте разcмотрим как ею пользоваться.

Существует 4 вида модификаторов доступа: public, protected, private и default.

Public — уровень  предполагает  доступ к компоненту с этим модификатором из экземпляра любого класса и любого пакета.

Protected — уровень  предполагает  доступ к компоненту с этим модификатором из экземпляров родного класса и классов-потомков, независимо от того в каком пакете они находятся.

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

Private — уровень предполагает  доступ к компоненту с этим модификатором только из этого класса.

public class Human {
    public String name;
    protected String surname;
    private int age;
    int birthdayYear;
}

public String name; — имя которое доступное из любого места в приложении.
protected String surname; — фамилия доступна из родного класса и потомков.
private int age; — возраст доступен только в рамках класса Human.
int birthdayYear; — хоть не указывается явный модификатор доступа, система понимает его как default, год рождения будет доступен всему пакету в котором находится класс Human.

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

Для класса- только public и default.

Для атрибутов класса — все 4 вида.

Для конструкторов — все 4 вида.

Для методов — все 4 вида.

Шаг 3.

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

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

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

Разберем этот механизм на классическом примере: Геометрические фигуры.

1

У нас есть интерфейс Figure:

public interface Figure {
    public void draw ();
    public void erase ();
    public void move ();
    public String getColor ();
    public boolean setColor ();
}

Интерфейс (более детально будут рассмотрены в скором будущем) — нам говорит как должен выглядеть класс, какие методы в себе содержать, какими переменными и типами данных манипулировать. Сам интерфейс не реализует методы, а создает как бы скелет для класса который будет расширять этот интерфейс. Есть класс Figure который расширяет интерфейс Figure:

public class Figure implements devcolibri.com.oop.inheritance.interfaces.Figure{

    @Override
    public void draw() {
       //need to implement
    }

    @Override
    public void erase() {
       //need to implement
    }     

    @Override      
    public void move(int pixel) {        
       //need to implement    
    }     

    @Override     
    public String getColor() {        
        return null; 
    }     

    @Override     
    public boolean setColor(String colour) {        
        return false; 
    } 
}

В этом классе мы реализовываем все методы интерфейса Figure.

public class Figure implements devcolibri.com.oop.inheritance.interfaces.Figure — с помощью ключевого слова implements мы перенимаем методы интерфейса Figure для реализации.

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

И соответственно у нас есть 3 класса саммых фигур которые наследуются от класса Figure. Класс Figure является родительским классом или классом родителем, а классы Circle, Rectungle и  Triangle — являются дочерними.

public class Circle extends Figure {

    @Override
    public void draw() {
       super.draw(); 
    }

   @Override
   public void erase() {
      super.erase();
   }

   @Override
   public void move(int pixel) {
       super.move(pixel);
   }

   @Override
   public String getColor() {
       return super.getColor();
   }

   @Override
   public boolean setColor(String colour) {
       return super.setColor(colour);
   }
}
public class Rectangle extends devcolibri.com.oop.inheritance.Figure{

    @Override
    public void draw() {
      super.draw();
    }

    @Override
    public void erase() {
       super.erase();
    }

    @Override
    public void move(int pixel) {
       super.move(pixel);
    }

    @Override
    public String getColor() {
       return super.getColor();
    }

    @Override
    public boolean setColor(String colour) {
       return super.setColor(colour);
   }

}
public class Triangle extends devcolibri.com.oop.inheritance.Figure{

    @Override
    public void draw() {
       super.draw();
    }

    @Override
    public void erase() {
       super.erase(); 
    } 

    @Override
    public void move(int pixel) {
       super.move(pixel);
    }

    @Override
    public String getColor() {
       return super.getColor();
    }

    @Override
    public boolean setColor(String colour) {
        return super.setColor(colour);
    }
}

public class Triangle extends devcolibri.com.oop.inheritance.Figure — это значит что класс Triangle наследует класс Figure.

super.setColor(colour); — super модификатор позволяющий вызывать методы из класса родителя.

Теперь каждый класс перенял свойства класса Figure. Что собственно это нам дало?

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

Наверное возник вопрос: чем же extends отличается от implements?

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

В дочерних классах мы можем спокойно добавлять новые интересующие нас методы. Например мы хотим добавить в класс Triangle 2-а новых метода: flimHorizontal () и flipVertical ():

2

/**
* New Method
*/
public void flipVertical () {

};

/**
* New Method
*/
public void flipHorizontal () {

};

Теперь эти 2-а метода принадлежат сугубо классу Triangle. Этот подход используется когда базовый класс не может решить всех проблем.

Или можно использовать другой подход, изменить или переписать методы в дочерним классе:

3

Довольно интересный факт: в java запрещено множественное наследование, но любой из классов по умолчанию наследуется то класса Object. То есть при наследовании любого класса у нас получается множественное наследование)

Но не стоит забивать этим голову!

Шаг 4.

Полиморфизм.

В более общем смысле, концепцией полиморфизма является идея «один интерфейс, множество методов«.

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

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

public class Parent {
    int a = 2;
}

public class Child extends Parent {
    int a = 3;
}

Прежде всего, нужно сказать, что такое объявление корректно.

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

Компилятор может опираться только на тип ссылки, с помощью которой происходит обращение к полю:

Child c = new Child();
System.out.println(c.a); // результатом будет 3
Parent p = c;
System.out.println(p.a); //результатом будет 2

Обе ссылки указывают на один и тот же объект, но тип у них разный. Отсюда и результат. Объявление поля в классе-наследнике «скрыло» родительское поле.

Данное объявление так и называется – «скрывающим». Родительское поле продолжает существовать.

К нему можно обратиться явно:

class Child extends Parent {
     int a = 3;                     //скрывающее объявление
     int b = ((Parent)this).a;      //громоздкое обращение к родительскому полю
     int c = super.a;               //простое обращение к родительскому полю
}

Переменные b и получат значения, родительского поля a. Хотя выражение с super более простое, оно не позволит обратиться на два уровня вверх по дереву наследования.

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

К нему можно обратиться явным приведением, как это делается для b.

class Parent {
     int x = 0;
     public void printX() {
          System.out.println(x);
     }
}

class Child extends Parent {
     int x = -1;
}

Каков будет результат для new Child.printX(); ?

Метод вызывается с помощью ссылки типа Child, но метод определен в классеParent и компилятор расценивает обращение к полю в этом методе именно как к полю класса Parent. Результатом будет 0.

 

Рассмотрим случай переопределения методов:

class Parent {
     public int getValue() {
          return 0;
     }
}

class Child extends Parent {
     public int getValue() {
          return 1;
     }
}

Child c = new Child();
System.out.println(c.getValue()); // результатом будет 1
Parent p = c;
System.out.println(p.getValue()); // результатом будет 1

Родительский метод полностью перекрыт.

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

Хотя старый метод снаружи недоступен, внутри класса-наследника к нему можно обратиться с помощью super.

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

Шаг 5.

Абстракция:

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

В контексте ООП абстракция — это обобщение данных и поведения для типа, находящегося выше текущего класса по иерархии.

Перемещая переменные или методы из подкласса в супер класс, вы обобщаете их. Это общие понятия, и они применимы в языке Java. Но язык добавляет также понятия абстрактных классов и абстрактных методов.

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

Например, вы можете создать класс Animal(животное). Нет смысла создавать экземпляр этого класса: на практике вам нужно будет создавать экземпляры конкретных классов, например, Dog (собака). Но все классы Animal имеют некоторые общие вещи, например, способность издавать звуки. То, чтоAnimal может издавать звуки, еще ни о чем не говорит.

Издаваемый звук зависит от вида животного.

Как это смоделировать?

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

В иерархии могут одновременно находиться как абстрактные, так и конкретные классы.

Использование абстракции:

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

public abstract class Person {
	abstract void move();
	abstract void talk();
}

public class Adult extends Person {
	public Adult() {
	}
	public void move() {
		System.out.println("Walked.");
	}
	public void talk() {
		System.out.println("Spoke.");
	}
}

public class Baby extends Person {
	public Baby() {
	}
	public void move() {
		System.out.println("Crawled.");
	}
	public void talk() {
		System.out.println("Gurgled.");
	}
}

Что мы сделали в приведенном выше коде?

Мы изменили Person и указали методы как abstract, заставив подклассы реализовывать их.
Мы сделали Adult подклассом Person и реализовали эти методы.
Мы сделали Baby подклассом Person и реализовали эти методы.

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

Теперь, поскольку Adult и Baby являются подклассами Person, мы можем обратиться к экземпляру каждого класса как к типу Person.

Урок создан: 19 марта 2013 | Просмотров: 42189 | Автор: Артем Зайцев | Правила перепечатки


Добавить комментарий

Добавить комментарий

Ваш e-mail не будет опубликован.

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

  • 19 апреля 2013 в 02:24

    basedannix

    Спасибо! Очень подробно и понятно написано.

  • 12 февраля 2014 в 03:59

    Alexander

    Amazing!Now i understood something.Thanks guys!

  • 10 декабря 2014 в 12:24

    AD

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

    • 18 декабря 2014 в 18:33

      Артем Зайцев

      В таком случае статья бы называлась «Что такое ПРОГРАМИРОВАНИЕ и с чем его едят?» )

  • 12 февраля 2015 в 21:14

    Лев

    Хочу освоить Java, это мои первые шаги в программировании. Немного запутавшись посоветовался с знакомым WEB-программистом на Java, он сказал что нужно понять что такое ООП. Статья очень помогла хоть и сложно в некоторых местах из-за недостатка знаний по Java. Можно было добавить пояснения отдельных команд Java в др. статье с ссылкой на неё специально для чайников. А в общем большое спасибо за статью

    • 23 февраля 2015 в 15:23

      Артём Зайцев

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

      • 19 октября 2015 в 10:11

        Кирилл

        так он ведь не просит объяснять, а хотя-бы оставить гиперссылку на страницу, где это объясняется.

  • 02 апреля 2015 в 12:22

    Антон

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

    Удачи вам!

  • 05 апреля 2015 в 13:43

    Oleksiy Holyarchuk

    А як щодо вбудованих і анонімних класів? :) Що це таке і з чим його їдять? :)

    • 21 апреля 2015 в 17:22

      Артём Зайцев

      Этот материал будет рассматриватся в другой статье.

    • 21 апреля 2015 в 17:57

      Артём Зайцев

      Но если так уж не в невтерпёж то вот тебе:

      class Base {
      void method1() {
      }

      void method2() {
      }
      }

      class A { // нормальный класс

      static class B {
      } // статический вложенный класс

      class C {
      } // внутренний класс

      void f() {
      class D {
      } // локальный внутренний класс
      }

      void g() {
      // анонимный класс
      Base bref = new Base() {
      void method1() {
      }
      };
      }
      }

      Внутрений класс:
      Грубо говоря это класс в классе. Вложенные классы делятся на два вида: статические и не статические. Основные цели их применения —
      1. Это хороший способ группировки классов, которые используются только в одном местею
      2. Инкапсуляция.
      3. Улучшение читаемости и обслуживаемости кода.

      Анонимный класс:
      Основная особенность — анонимный класс не имеет имени. Анонимный класс является подклассом существующего класса (в примере Base) или реализации интерфейса.
      Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально.
      Пример использования с мого текущего проекта (если поможет) — создание динамического таймера с динамическими условиями: метод call реализован на самом низком уровне и будет запускать таймер в отдельном потоке.
      Структура:
      public class Timer {

      public static void main(String[] args) {
      new Timer().startTimer();
      }

      public void startTimer(Object[] args) {
      . . .
      }
      }
      Реализация:
      Пример 1 — переход в полноекранный режим через 5 сек.
      IXRETaskManager.Timer timer = guide.getTaskManager().createTimer(new IXRETaskManager.Callable() {
      @Override
      public void call(Object… params) {
      guide.getStateController().goToState(StateController.State.FULL_SCREEN);
      }
      }, 5000, 1);
      timer.start();

      Пример 2 — выход из приложения через 15 сек.
      IXRETaskManager.Timer timer = guide.getTaskManager().createTimer(new IXRETaskManager.Callable() {
      @Override
      public void call(Object… params) {
      System.exit(0);
      }
      }, 15000, 1);
      timer.start();

      P.S. Сорри за ошибки, времени исправлять не было.

  • 15 апреля 2015 в 03:20

    dima

    А зачем абстрактный класс,если вместо него можно исползовать интерфейс? Спасибо

    • 21 апреля 2015 в 17:32

      Артём Зайцев

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

      Но если хочешь знать полный перечень отличий — то вот:
      Абстрактный(Abstract) класс — класс, который имеет хотя б 1 абстрактный (не определенный) метод; обозначается как abstract.
      Интерфейс — такой же абстрактный класс,только в нем не может быть свойств и не определены тела у методов.
      Так же стоит заметить, что абстрактный класс наследуется(etxends), а интерфейс реализуется (implements). Вот и возникает разница между ними, что наследовать мы можем только 1 класс, а реализовать сколько угодно.

      • 27 августа 2015 в 12:04

        rmikki

        >> Абстрактный(Abstract) класс — класс, который имеет хотя б 1 абстрактный (не определенный) метод; обозначается как abstract.

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

  • 15 сентября 2015 в 22:39

    Петр.

    На счет «в java запрещено множественное наследование, но любой из классов по умолчанию наследуется то класса Object. То есть при наследовании любого класса у нас получается множественное наследование». Мы екстендим класс, а тот уже наследуется от Object. Теребоньканье иерархии так сказать.

    • 16 апреля 2016 в 01:16

      Mrixs

      Нет в java множественного наследования. То, что предком любого класса является Object, это не множественное наследование, а транзитивное.
      Пример с фигурами:
      есть фигура, от неё наследуется многоугольник, от многоугольника наследуются треугольник и четырехугольник. Тот факт, что треугольник является наследником и многоугольника, и фигуры — является множественным наследованием? Нет. Это транзитивное наследование.
      Так же и с Object — любой класс является наследником Object, потому что суперкласс тоже является наследником Object (напрямую или транзитивно)

  • 04 июля 2016 в 09:53

    Настя

    Спасибо большое!Отличная статья. Четко и понятно изложен материал.