Парадигмы программирования

Реализация ООП

Описание стека

  • Переменные
    • size — число элементов
    • elements — массив элементов
  • Методы
    • push — добавить элемент
    • pop — удалить элемент
    • peek — получить элемент на вершине
    • size — число элементов
    • isEmpty — проверка на пустоту

Модель

  • Последовательность чисел
    • $a[1], a[2], \ldots, a[n]$
    • Операции с последним элементом
  • Инвариант
    • $n \ge 0$
    • $∀i=1..n: a_i ≠ null$

Контракты (1)

  • push
    • // pre: element ≠ null
      // post: n = n' + 1 ∧ ∀i=1..n' : a[i]' = a[i] ∧ a[n] = element
      void push(Object element)
      
  • pop
    • // pre: n > 0
      // post: ℝ = a[n] ∧ n = n' − 1 ∧ ∀i=1..n : a[i]' = a[i]
      Object pop()
      
  • peek
    • // pre: n > 0
      // peek: ℝ = a[n] ∧ n = n' ∧ ∀i=1..n : a[i]' = a[i]
      Object peek()
      

Контракты (2)

  • size
    • // post: ℝ = n ∧ n = n' ∧ ∀i=1..n : a[i]' = a[i]
      int size()
      
  • isEmpty
    • // post: ℝ = n > 0 ∧ n = n' ∧ ∀i=1..n : a[i]' = a[i]
      boolean isEmpty()
      
  • Дублирование постусловий
    • // post: n = n' ∧ ∀i=1..n : a[i]' = a[i]
    • // post: immutable 

Заголовок

  • Объявление модуля
    public class ArrayStackModule {
    
  • Данные
    static int size;
    static Object[] elements = new Object[5];
    

Функция push

  • static void push(Object element) {
        assert element != null;
        ensureCapacity(size + 1);
        elements[size++] = element;
    }
    static void ensureCapacity(int capacity) {
        if (capacity <= elements.length) {
            return;
        }
        elements = Arrays.copyOf(
                elements, 2 * capacity);
    }
    

Функции pop и peek

  • static Object pop() {
        assert size > 0;
        return elements[--size];
    }
    static Object peek() {
        assert size > 0;
        return elements[size - 1];
    }
    

Функции size и isEmpty

  • static int size() {
        return size;
    }
    static boolean isEmpty() {
        return size == 0;
    }
    

Пример использования

  • Добавление элементов
    for (int i = 0; i < 10; i++) {
        ArrayStackModule.push(i);
    }
    
  • Получение элементов
    while (!ArrayStackModule.isEmpty()) {
        System.out.println(
            ArrayStackModule.size() + " " +
            ArrayStackModule.peek() + " " +
            ArrayStackModule.pop()
        );
    }
    

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

  • Сокрытие деталей реализации
    • Доступ на уровне модуля
    • Способ поддерживать контракт
  • Пример
    • private static int size
      private static Object[] elements
      public push(Object element)
      public Object pop()
      public Object peek()
      public int size()
      public boolean isEmpty()
      private void ensureCapacity()
      

Заголовок

  • Объявление структуры
    public class ArrayStackADT {
    
  • Данные
    private /*static*/ int size;
    private /*static*/ Object[] elements =
            new Object[5];
    

Преобразование

  • Добавление аргументов функций
    • Первый аргумент — ссылка на структуру
    • ArrayStackADT stack
  • Доступ к переменным
    • Добавление префикса stack.
  • Вызов функций
    • Передача первым аргументом ссылки

Функция push

  • public static void push(ArrayStackADT stack, Object element) {
        assert element != null;
        ensureCapacity(stack, stack.size + 1);
        stack.elements[stack.size++] = element;
    }
    private static void ensureCapacity(
        ArrayStackADT stack, int capacity
    ) {
        if (capacity <= stack.elements.length) {
            return;
        }
        stack.elements = Arrays.copyOf
                (stack.elements, capacity * 2);
    }
    

Функции pop и peek

  • public static Object pop(ArrayStackADT stack) {
        assert stack.size > 0;
        return stack.elements[--stack.size];
    }
    public static Object peek(ArrayStackADT stack) {
        assert stack.size > 0;
        return stack.elements[stack.size - 1];
    }
    

Функции size и isEmpty

  • public static int size(ArrayStackADT stack) {
        return stack.size;
    }
    public static boolean isEmpty(
        ArrayStackADT stack
    ) {
        return stack.size == 0;
    }
    

Пример использования

  • Добавление элементов
    ArrayStackADT stack = new ArrayStackADT();
    for (int i = 0; i < 10; i++) {
        ArrayStackADT.push(stack, i);
    }
    
  • Получение элементов
    while (!ArrayStackADT.isEmpty(stack)) {
        System.out.println(
            ArrayStackADT.size(stack) + " " +
            ArrayStackADT.peek(stack) + " " +
            ArrayStackADT.pop(stack)
        );
    }
    

Заголовок

  • Объявление класса
    public class ArrayStack {
    
  • Данные
    private int size;
    private Object[] elements = new Object[10];
    

Преобразование функций в методы

  • Изменение модификаторов
    • Убрать static
  • Замена первого аргумента
    • Замена на аргумента на неявный (this)
  • Доступ к переменным
    • Замена префикса stack. на this.
    • Префикс this. можно опускать
  • Вызов метода
    • method(stack, ...)stack.method(...)

Метод push

  • public void push(Object element) {
        assert element != null;
        ensureCapacity(size + 1);
        elements[size++] = element;
    }
    private void ensureCapacity(int capacity) {
        if (capacity <= elements.length) {
            return;
        }
        elements = Arrays.copyOf(elements, capacity * 2);
    }
    

Методы pop и peek

  • public Object pop() {
        assert size > 0;
        return elements[--size];
    }
    public Object peek() {
        assert size > 0;
        return elements[size - 1];
    }
    

Методы size и isEmpty

  • public int size() {
        return size;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    

Пример использования

  • Добавление элементов
    ArrayStack stack = new ArrayStack();
    for (int i = 0; i < 10; i++) {
        stack.push(i);
    }
    
  • Получение элементов
    while (!stack.isEmpty()) {
        System.out.println(stack.size() + " " +
            stack.peek() + " " + stack.pop());
    }
    

Элемент связного списка

  • Объявление класса
    // inv: value != null
    public class Node {
       Object value;
       Node next;
    }
    
  • Инициализация
    public init(Object value, Node next) {
        assert value != null;
        this.value = value;
        this.next = next;
    }
    

Инициализация

  • Значения по умолчанию
    value = null;
    next = null;
    
    • Инвариант нарушен
  • Метод init
    assert value != null;
    this.value = value;
    this.next = next;
    
    • Инвариант соблюдается

Конструктор

  • Соблюдение инварианта при создании
  • Пример конструктора
    public Node(Object value, Node next) {
        assert value != null;
        this.value = value;
        this.next = next;
    }
    
  • Вызов конструктора
    Node node = new Node(1, null);
    

Как работало раньше?

  • Конструктор по умолчанию
    • Без аргументов
    • Ничего не делает
    • Создается, если нет других

Устройство

  • Исходное состояние
  • push(v¹)
  • push(v²)
  • pop()

Реализация

  • Объявление класса
    public class LinkedStack {
    
  • Данные
    private int size;
    private Node head;
    

Метод push

  • // pre: element ≠ null
    // post: n = n' + 1 ∧ ∀i=1..n' : a[i]' = a[i]∧
    //          ∧ a[n] = element
    public void push(Object element) {
        assert element != null;
        size++;
        head = new Node(element, head);
    }
    

Метод pop

  • // pre: n > 0
    // post: ℝ = aₙ₊₁ ∧ n = n' − 1 ∧
    //          ∧ ∀i=1..n : a[i]' = a[i]
    public Object pop() {
        assert size > 0;
        size--;
        Object result = head.value;
        head = head.next;
        return result;
    }
    

Метод peek

  • // pre: n > 0
    // post: ℝ = a[n] ∧ immutable
    public Object peek() {
        assert size > 0;
        return head.value;
    }
    

Методы size и isEmpty

  • // post: ℝ = n ∧ immutable
    public int size() {
        return size;
    }
    
  • // post: ℝ = n > 0 ∧ immutable
    public boolean isEmpty() {
        return size == 0;
    }
    

Пример использования

  • Добавление элементов
    LinkedStack stack = new LinkedStack();
    for (int i = 0; i < 10; i++) {
        stack.push(i);
    }
    
  • Получение элементов
    while (!stack.isEmpty()) {
        System.out.println(stack.size() + " " +
            stack.peek() + " " + stack.pop());
    }
    

Проблема

  • Одинаковый контракт стеков
  • Одинаково использование
    • Почти одинаковый код
  • Код знает тип списка
    • ArrayStack
    • LinkedStack

Решение

  • Интерфейс
    • Набор методов
    • Контракт
  • Пример
    public interface Stack {
        /*public*/ void push(Object element);
        /*public*/ Object pop();
        /*public*/ Object peek();
        /*public*/ int size();
        /*public*/ boolean isEmpty();
    }
    

Реализация интерфейса

  • Синтаксис
    public class ArrayStack implements Stack { ... }
    public class LinkedStack implements Stack { ... }
    
  • Реализация методов
    • Проверяется компилятором
  • Выполнение контракта
    • Гарантируется программистом

Пример использования

  • Добавление элементов
    public static void fill(Stack stack) {
        for (int i = 0; i < 10; i++) {
            stack.push(i);
        }
    }
    
  • Получение элементов
    public static void dump(Stack stack) {
        while (!stack.isEmpty()) {
            System.out.println(stack.size() + " " +
                stack.peek() + " " + stack.pop());
        }
    }
    

Проверка типа

  • a instanceof T
    • a может быть приведено к T
  • Примеры
    • new ArrayStack() instanceof ArrayStacktrue
    • new ArrayStack() instanceof LinkedStackfalse
    • new ArrayStack() instanceof Stacktrue
    • new ArrayStack() instanceof Objecttrue
    • new Object() instanceof Stackfalse

Реализация на основе instanceof

class StackImpl {
    public static void pop(Stack this) {
        if (this instanceof ArrayStack) {
            return ArrayStack.pop(this);
        } else if (this instanceof LinkedStack) {
            return LinkedStack.pop(this);
        } else {
            // ?
        }
    }
    …
}

Свойства

  • Достоинства
    • Простота реализации
  • Недостатки
    • Разбухание кода
    • Линейное время

Таблицы виртуальных функций

Свойства

  • Достоинства
    • Константные трудозатраты
  • Недостатки
    • Затраты памяти
    • Проблемы с множественным наследованием

Полиморфизм

  • Один код, разные типы
  • Ad-hoc
    • Для каждого типа свое поведение
  • Универсальный
    • Одинаковое поведение для всех типов

Пример полиморфизма (1)

  • Операция сложения (+)
  • Примеры использования
    • 1 + 1
    • 1.0 + 1
    • 1 + 1.0
    • 1.0 + 1.0
  • Перегрузка времени компиляции по обоим параметрам
    • Ad-hoc полиморфизм
  • Перегрузка для разных типов + автоматизированное приведение типов
    • Ad-hoc полиморфизм

Пример полиморфизма (2)

  • Перегрузка функций
    void test(LinkedStack stack) { … }
    void test(ArrayStack stack) { … }
    …
    test(new LinkedList());
    test(new ArrayList());
    
  • Ad-hoc полиморфизм

Пример полиморфизма (3)

  • Функция printf
  • Примеры использования
    • printf("%d", 10);
    • printf("%s", "hello");
  • Перегрузка времени исполнения по всем параметрам, кроме первого + явная передача информации о типах
    • Ad-hoc полиморфизм

Пример полиморфизма (4)

  • Наследование в ООП
    • drawWithColor(Shape shape, Color color) {
          shape.setColor(color);
          shape.draw()
      }
      
  • drawWithColor – полиморфизм включения по первому параметру
    • Параметрический полиморфизм
  • draw(this) – перегрузка времени исполнения по первому параметру
    • Ad-hoc полиморфизм

Пример полиморфизма (5)

  • Определение типа во время исполнения
  • Пример использования
    • void draw(Shape shape) {
          if (shape instanceof Rect) …
          if (shape instanceof Circle) …
      }
      
  • draw() – перегрузка времени исполнения
    • Ad-hoc полиморфизм

Общее поведение

  • Управление размером
    • size
    • isEmpty
    • push
    • pop
  • Обработка null
    • push
  • Обработка пустого стека
    • pop
    • peek

Вынос общего поведения

  • Полное дублирование
    • public isEmpty() {
          return size == 0;
      }
      
    • Общая функция/метод
  • Частичное дублирование
    • public peek() {
          assert size > 0;
          // Получить элемент
          // Зависит от реализации
      }
      
    • Что делать?

Устранение дублирования

  • Метод для дублирующейся части
    • Вызывает методы для различных частей
    • public peek() {
          assert size > 0;
          doPeek();
      }
      
    • protected abstract Object doPeek();
      
  • Методы для различающихся частей
    • protected doPeek() { // ArrayStack
          return elements[size - 1];
      }
      
    • protected doPeek() { // LinkedStack
          return head.value;
      }
      
  • Как реализовать?
    • Указатели на функции
    • Таблица функций

Интерфейс Copiable

  • Определение
    public interface Copiable {
        public Copiable makeCopy();
    }
    

Стек на массиве

  • Множественная реализация интерфейсов
    public class ArrayStack
        implements Stack, Copiable {
    
  • Реализация
    public Copiable makeCopy() {
        final ArrayStack copy = new ArrayStack();
        copy.size = size;
        copy.elements = elements;
        return copy;
    }
    

Проблема

  • Общий массив элементов
  • makeCopy()
  • pop()

Копирование массива

public Copiable makeCopy() {
    final ArrayStack copy = new ArrayStack();
    copy.size = size;
    copy.elements = Arrays.copyOf(elements, size);
    return copy;
}

Соглашения Java

  • Ссылка передается по значению
    • Объект – не копируется
    • При изменении – изменяется по всем ссылкам
  • Поверхностное копирование
    • Только ссылок на объекты
  • Глубокое копирование
    • Создание копий объектов
    • В явном виде

Сборка мусора

  • Получение ссылки
    • new
    • присваивание
    • вызов метода
  • Мусор
    • Нет ссылок
    • ⇒ никогда не будет
    • ⇒ можно удалить объект
  • Сборщик мусора
    • Удаляет мусор
    • delete не нужен

Уточнение возвращаемого значения

  • Проблема
    • ArrayStack copy = (ArrayStack) stack.makeCopy();
  • Решение
    public ArrayStack makeCopy() { ... }
    
  • Почему это работает?
    • ArrayStack реализует Copiable

Стек на связном списке

  • Объявление
    public class LinkedStack
        implements Stack, Copiable {
    
  • Реализация
    public LinkedStack makeCopy() {
        final LinkedStack copy = new LinkedStack();
        copy.size = size;
        copy.head = head;
        return copy;
    }
    
  • Почему это работает?
    • Node не изменяется

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

  • Конкретный стек можно скопировать
    • Stack нельзя скопировать
  • Решение
    public interface Stack extends Copiable { ... }
    
  • Уточнение возвращаемого значения
    public interface Stack extends Copiable {
        Stack makeCopy();
        ...
    
  • Пример использования
    Stack stack = ...;
    Stack copy = stack.makeCopy();
    

Метод toString

  • Возвращает строку, описывающую объект
  • Наивная реализация
    public String toString() { // ArrayStack
        String result = "";
        for (int i = 0; i < size; i++) {
            result += " " + elements[i];
        }
        return "[" + result.substring(1) + "]";
    }
    
    • Оцените время работы
    • $O(n^2)$

Класс StringBuilder

  • Эффективное конструирование строк
    • Использовать в циклах
  • Применение
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(" ").append(elements[i]);
        }
        return "[" + sb.substring(1) + "]";
    }
    
    • Оцените время работы
    • $O(n)$

Класс StringJoiner

  • Слияние строк
  • Применение
    final StringJoiner sj
            = new StringJoiner(",", "[", "]");
    for (int i = 0; i < size; i++) {
        sj.add(elements[i].toString());
    }
    return sj.toString();
    
    • Время работы $O(n)$

Принципы SOLID

  • Single Responsibility
  • Open/Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

Single Responsibility

  • Принцип единственной ответственности
    • Один класс – одна ответственность
    • Одно изменение - одно место

Open/Closed

  • Принцип открытости/закрытости
    • Открытость для расширения
    • Закрытость для изменения

Liskov Substitution

  • Принцип подстановки Лисков
    • Тип заменяеется любым подтипом
    • Ослабление предусловий
    • Сохранение инвариантов
    • Усиление постусловий
    • Ограничение истории

Interface Segregation

  • Принцип разделения интерфейсов
    • Зависимость только от используемого

Dependency Inversion

  • Принцип инверсии зависимостей
    • Клиенты зависят от абстракций
    • Реализация зависит от абстракций

Постановка задачи

  • Прямоугольник
    • Ширина
    • Высота
  • Квадрат
    • Сторона

Является (is-a)

  • Квадрат является прямоугольником
  • Следовательно
    Square extends Rectange
  • Как реализовать setWidth?
    • Изменить сторону
    • Перестать быть квадратом

Расширение

  • Интерфейс прямоугольника «шире»
  • Следовательно
    Rectangle extends Square
  • Как реализовать getSide?
    • Не понятно

Возможные решения

  • Неподдерживаемые действия
  • Ослабление контракта
  • Возврат нового
  • Выделение общего предка
  • Вынос изменений
  • Отказ от наследования
  • Подмена класса

Неподдерживаемые действия

  • Не все действия поддерживаются
    • boolean setWidth(int w)
  • Square extends Rectangle
  • Постоянные проверки
  • Поддержка со стороны Rectangle

Ослабление контракта

  • Действия имеют побочные эффекты
    • setWidth ≡ setSide
  • Square extends Rectangle
  • Как этим пользоваться?

Возврат нового

  • Операция возвращает новый объект
  • Rectangle setWidth(int w)
  • Square extends Rectangle
  • Неизменяемые объекты

Вынос изменений

  • Изменения выносятся в отдельный класс
  • Square extends Rectangle
    MutableRectangle extends Rectangle
    MutableSquare extends Square
    
  • Много классов
  • Комбинаторный взрыв

Выделение общего предка

  • Выделить общего предка
  • Square extends SquareOrRectangle
    Rectangle extends SquareOrRectangle
    

Отказ от наследование

  • Два независимых класса
    Square::asRectangle
    Rectangle::boundingSquare
    

Подмена класса

  • Класс может меняться
  • setWidth преобразует в Rectangle
  • Square extends Rectangle
  • Не поддерживается популярными языками

Сущности и значения

  • Изменение частей
    • Сущность не изменяется
    • Новое значение
  • Примеры
    • Студент
    • Точка
    • Адрес

Поддержка в Java

  • Чему соответствует ==?
    • Сущностям
  • Поддержка значений
    • public boolean equals(Object o)

Реализация equals

  • Точка
    • class Point {
          int x, y;
      
  • Сравнение
    • public boolean equals(Object o) {
          if (o instanceof Point) {
              Point that = (Point) o;
              return this.x == that.x && this.y == that.y;
          }
          return false;
      }
      
    • Почему нет проверки на null?
    • null instanceof Tfalse

Свойства равенства

  • Рефлексивность
    • x.equals(x)true
  • Симметричность
    • x.equals(y) y.equals(x)
  • Транзитивность
    • x.equals(y) && y.equals(z)x.equals(z)
  • Устойчивость
    • x.equals(y) не изменяется, если x и y не изменяются
  • Обработка null
    • x.equals(null) false

Реализация equals (2)

  • Цветная точка на плоскости
    • public class ColorPoint extends Point {
          protected int c;
          public boolean equals(Object o) {
              if (o instanceof ColorPoint) {
                  ColorPoint that = (ColorPoint) o;
                  return this.x == that.x && this.y == that.y && this.c == that.c;
              }
              return false;
          }
      }
      
  • Где проблема?
    • Несимметричность

Пример equals (3)

  • Цветная точка на плоскости
    • public class ColorPoint extends Point {
          protected int c;
          public boolean equals(Object o) {
              if (o instanceof ColorPoint) {
                  ColorPoint that = (ColorPoint) o;
                  return this.x == that.x && this.y == that.y && this.c == that.c;
              }
              return super.equals(o);
          }
      }
      
  • А теперь?
    • Нетранзитивность

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

  • equals и наследование ⇒ проблемы
  • Варианты решения
    • Сравнивать объекты только одинакового класса
    • Сравнивать как предков
    • Послойное сравнение
      • canEquals

Ссылки

Вопросы

???