Страницы

Поиск по вопросам

Показаны сообщения с ярлыком полиморфизм. Показать все сообщения
Показаны сообщения с ярлыком полиморфизм. Показать все сообщения

среда, 26 февраля 2020 г.

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

#ооп #разработка_игр #полиморфизм


Делаю игру. Есть общий класс Creature, от него наследуются Monster и Human. Есть
класс Tile (игровая клетка), от нее наследуются DivineTrap (божественная ловушка) и
DivineWall (божественная стена). 

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

У нас есть 4 разных поведения: 


Monster не может пройти на Tile, если он типа DivineWall, а если это DivineTrap,
то монстр, передвинувшись на него, погибает. 
Human может проходить на DivineWall и DivineTrap беспрепятственно. 


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

Понятно, что можно прервать замкнутый круг, задействовав instanseof. Или опросить
состояние объекта. Но мне хотелось бы знать, как называется моя проблема в ООП, и каковы
методы ее решения. Потому что я не уверен, что instanceof и опрос состояния наилучшие
решения.
    


Ответы

Ответ 1



Ваша проблема кроется в нарушении, как минимум принципа единственной ответственности. Клетка не должна позволять или запрещать проходить на себя, пропускать людей и убивать монстров. Клетка, если можно так выразиться, объект-справочник. Она должна знать свои координаты на карте и то, что на ней находится (некий объект-сущность: игрок, монстр, стена, ловушка, ресурсы.. что угодно) и предоставлять эту информацию по требованию других объектов (например, при поиске кратчайшего пути опрашиваем все соседние клетки и анализируем, куда лучше пойти). Если Вы будете добавлять в "клетку" логику, это будет смешиванием ответственностей. Далее: ... логичнее было бы вместе наследования применить композицию (ловушку или стену сделать, как поле игровой клетки)... Это было бы не просто логичнее, наследование здесь семантически некорректно. Ведь если Вы моделируете рабочее место пользователя, вы же не будете монитор, клавиатуру и мышь наследовать от стола только потому, что эти объекты находятся на столе. Ведь так? С ловушкой или стеной, которая находится на клетке, похожая ситуация. Поэтому ловушка должна быть объектом на поле, но не полем. Ближе к проблеме.. не думаю, что у этой проблемы есть какое-то конкретное название и конкретное решение. Это просто проблема проектирования, и методы ее решения зависят от того, чего вы хотите добиться своей программой. Что касается варианта решения, согласен с @Кирилл Малышев по поводу паттерна посетитель. Хотя с другой стороны эту логику можно также реализовать в классах Monster и Human, сделав, например, метод die (умереть) защищенным и вызывать его в случае, когда хп становится равным 0 (в данном случае я имею ввиду эти классы базовыми для конкретных типов монстров и людей). Ловушки же в свою очередь будут только снимать хп. Классы людей могут иметь иммунитет к божественным ловушкам, а монстры - к демоническим. Реализовав такую логику, можно запросто добавить босса, например, с иммунитетом к любым ловушкам. Все зависит от того, насколько детально Вам нужно проработать проект.

Ответ 2



Хороший вопрос. Я не знаю как называется в теории данный паттерн, но я бы решал это таким образом: У нас есть три сущности: Клетка Creature Реакция В соответствии с этим и нужно строить структуру классов. 1 и 2 уже расписаны в вопросе. Остался базовый класс "Реакция" В конструктор должны быть переданы объекты 1 и 2. В зависимости от сочетания типов этих двух объектов создается нужный экземпляр потомка от базового класса Реакция. Ну и потомок реализует уже необходимое действие.

понедельник, 10 февраля 2020 г.

Порядок работы с таблицами виртуальных методов

#cpp #полиморфизм


Добрый вечер. 

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

В момент создания объекта (наследника через указатель на базовый класс), в базовом
классе (совместно с созданием VMT) объявляется виртуальный табличный указатель объекта
или vptr.

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

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

То есть компилятор генерирует «скрытый» код в конструкторе каждого класса для инициализации
vpointer'ов объектов класса адресами соответствующей VMT.


сначала вызывается конструктор базового класса, инициализирующий vptr адресом VMT
базового класса, 
затем вызывается конструктор производного класса, который перезаписывает значение
vptr адресом VMT производного класса. (и так до тех пор пока vptr не будет инициализирован
адресом VMT того производного класса, которому принадлежит созданный объект)


Вопросы в следующем:

1) Что подразумевается под инициализацией vptr адресом VMT производного класса? Понятно,
что у виртуальных методов есть адреса, которые хранятся в таблице, разве адрес есть
непосредственно у VMT класса?

2) В каком порядке при запуске программы происходит создание VMT базового класса,
создание VMT производного класса (которая получает VMT базы при наследовании и инициализирует
ее элементы адресами своих методов), создание vptr и его инициализация таблицами базового
и производных классов?

3) При вызове виртуального деструктора значение vptr тоже инициализируется сначала
адресом VMT производного, а затем базового класса?
    


Ответы

Ответ 1



Если в классе объявлен виртуальный метод, то компилятор создает таблицу виртуальных методов, объявленных в определении этого класса. Производный класс "получает" эту таблицу при наследовании и записывает туда адреса переопределенных (своих реализаций) виртуальных методов. Это верно, с той только оговоркой, что все это делается на этапе компиляции. То есть вышепроцитированное - это то, как может "рассуждать" компилятор в процессе формирования VMT для очередного класса. 1) Что подразумевается под инициализацией vptr адресом VMT производного класса? Понятно, что у виртуальных методов есть адреса, которые хранятся в таблице, разве адрес есть непосредственно у VMT класса? VMT - это таблица в памяти. У нее есть адрес. Вот ее адрес и хранится в указателе vptr каждого объекта. В указателе vptr у объекта типа SomeClass хранится указатель на VMT класса SomeClass. А уж в таблице хранятся указатели на виртуальные методы класса SomeClass (или его предков). 2) В каком порядке при запуске программы происходит создание VMT базового класса, создание VMT производного класса (которая получает VMT базы при наследовании и инициализирует ее элементы адресами своих методов), Вопрос не совсем корректно поставлен. Таблицы VMT для всех классов как правило инициализированы статически, то есть их содержимое известно на стадии компиляции. При запуске программы они уже лежат готовенькие к использованию (в т.наз. сегменте инициализированных данных). Таблицы VMT формирует линкер (хотя возможно и более "позднее" формирование загрузчиком). В любом случае, когда программа фактически запустилась, все VMT уже сформированы. Порядок их формирования никакой роли не играет, ибо от него ничего не зависит. При создании объектов класса идет работа только с указателями на VMT. Сами VMT при этом никто уже не модифицирует (и не "создает"). создание vptr и его инициализация таблицами базового и производных классов? Вы сами совершенно правильно описали этот процесс в последовательности вызова конструкторов создаваемого объекта, от базовых к производным. Последним отрабатывает "самый производный" конструктор - конструктор полного объекта - в результате чего в vptr остается правильный указатель на VMT полного объекта. 3) При вызове виртуального деструктора значение vptr тоже инициализируется сначала адресом VMT производного, а затем базового класса? Деструкция происходит в порядке, обратном конструкции. Сначала отрабатывает тело деструктора полного объекта, а в конце он вызывает деструкторы всех своих базовых подобъектов. Каждый деструктор первым делом ставит vptr на VMT своего класса. Так работает любой деструктор, поэтому не совсем ясно, зачем вы упоминаете вызов именно виртуального деструктора. Когда деструктор уже начал работу, его виртуальность уже не имеет никакого значения.

Ответ 2



В популярных реализациях С++, таблицы виртуальных функций константны. В классе есть скрытый член, который хранит указатель на таблицу виртуальных функций. В констурукторе и деструкторе этот член заменяется на таблицу текущего типа. Допустим есть следующие классы struct A { A(); virtual void f(); virtual ~A(); }; struct B : A { B(); void f() override; virtual void g(); ~B() override; }; Для них будут сгенерированы две таблицы виртуальных функций: void* A_vft[] { &A::~A, &A::f, }; void* B_vft[] { // функции унаследованные от A &B::~B, &B::f, // новые функции &B::g, }; В объектах будет находиться член-указатель на таблицу вирт. функций. struct A { void** vft; }; struct B : A { }; Конструкторы будут сгенерированы примерно следующим образом: A::A() { this->vft = A_vft; // тело к-тора A в коде } B::B() { A::A(); // к-тор родителя this->vft = B_vft; // тело к-тора B в коде } Деструкторы выглядят так же A::~A() { this->vft = A_vft; // тело д-тора A в коде } B::~B() { this->vft = B_vft; // тело д-тора B в коде A::~A(); // д-тор родителя } Вызов A* a; a->f(); компилируется следующим образом: void* f = a->vft[1]; f(a); Вызов delete a; компилируется практически так же (при множественном наследовании всё сложнее): void* dtor = a->vft[0]; dtor(a); operator delete(a); Такая схема позволяет добавлять новые классы без перекомпиляции старых. Для класса C : B надо просто добавить его таблицу виртуальных функций, конструктор и деструктор.

среда, 29 января 2020 г.

Почему в этом методе лучше полиморфизм?

#ооп #полиморфизм


Читаю книгу "Совершенный код", проходя раздел автор привел пример метода, который
лучше было бы заменить полиморфизмом:

switch (shape.type) {
    case Shape_Circle:
        shape.DrawCircle();
        break;
    case Shape_Square:
        shape.DrawSquare();
        break;
    ...
}


Цитата из книги: "Здесь методы shape.DrawCircle() и shape.DrawSquare() следует заменить
на единственный метод shape.Draw(), поддерживающий рисование и окружностей, и прямоугольников."

Не могу понять, смысл от создания нового метода, если все равно придется писать оператор
switch в другом методе, можете подробнее объяснить, чем такой подход лучше? 
    


Ответы

Ответ 1



public abstract class Shape { public abstract void Draw(); } public class Circle : Shape { public override void Draw() { // implement drawing of a circle } } public class Square : Shape { public override void Draw() { // implement drawing of a square } } public class SomeUnforeseenShape : Shape { public override void Draw() { // draw Mona Lisa } } public void DrawShape(Shape shape) { shape.Draw(); }

вторник, 28 января 2020 г.

java полиморфизм

#java #ооп #наследование #полиморфизм



  Использование дочернего класса в качестве родительского класса


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

Теперь вопрос

ЗАчем 

Noodle biangBiang = new Spaghetti();


если мы можем написать и результат получим один и тот же 

Spaghetti biangBiang = new Spaghetti();


Пример всего кода

class Noodle {

  protected double lengthInCentimeters;
  protected double widthInCentimeters;
  protected String shape;
  protected String ingredients;
  protected String texture = "brittle";

  Noodle(double lenInCent, double wthInCent, String shp, String ingr) {

    this.lengthInCentimeters = lenInCent;
    this.widthInCentimeters = wthInCent;
    this.shape = shp;
    this.ingredients = ingr;

  }

  public String getCookPrep() {

    return "Boil noodle for 7 minutes and add sauce.";

  }

  public static void main(String[] args) {
    Noodle n = new Noodle(30.0, 0.2, "round", "semolina flour");
    System.out.println(n.getCookPrep());
    Spaghetti a = new Spaghetti();
    System.out.println(a.getCookPrep());


  }

}

class Spaghetti extends Noodle {

  Spaghetti() {

    super(30.0, 0.2, "round", "semolina flour");

  }

  public String getCookPrep() {

    return "Boil spaghetti for 8 - 12 minutes and add sauce, cheese, or oil and garlic.";

  }

}


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


Ответы

Ответ 1



Например, для этого public static void main(String[] args) { Noodle n = new Noodle(30.0, 0.2, "round", "semolina flour"); Spaghetti a = new Spaghetti(); printCookPrep(n); printCookPrep(a); } public static void printCookPrep(Noodle n){ System.out.println(n.getCookPrep()); }

Ответ 2



Например, у вас имеется база данных сотрудников фирмы. В ней есть абстрактный класс "Сотрудник" (который имеет поля имя и зарплата, а также методы доступа к ним), и от него наследуются более конкретные "Менеджер", "Программист", "Уборщик" и т.д., которые имеют свои более специфические состояния и поведения. И вот задача: вывести список всех сотрудников и их зарплату в один файл. Для абстрактных классов можно создавать объектные переменные, но такие переменные должны ссылаться на объект неабстрактного класса. Если заранее собирать в список всех сотрудников, то такая задача решится за один обход коллекции. public abstract class Employee { private String name; private Integer pay; public void setName(String aName) { name = aName; } public String getName() { return name; } public void setPay(int value) { pay = value; } public Integer getPay() { return pay; } } public class Coder extends Employee { private String position; public Coder(String name, int pay, String _position) { setName(name); setPay(pay); setPosition(_position); } public void setPosition(String value) { position = value; } public String getPosition() { return position; } } public class Manager extends Employee { public Manager(String name, int pay) { setName(name); setPay(pay); } } public class TEST { public static void main(String[] args) { LinkedList employees = new LinkedList(); // Нанимаем менеджера employees.add(new Manager("John", 25000)); // Нанимаем программиста Coder coder1 = new Coder("Nick", 30000, "Junior"); employees.add(coder1); // Нанимаем ещё менеджера Manager manager1 = new Manager("Cameron", 25000); employees.add(manager1); // Выводим список всех сотрудников for (Employee current : employees) { System.out.println(current.getName() + " - " + current.getPay().toString()); } } } Вот пример реализации принципа полиморфизма, к объектам подкласса можно обращаться из ссылочных переменных их суперкласса. НО здесь например нельзя будет из коллекции вызвать метод setPosition для объекта класса Coder, так как класс Employee не имеет о нём понятия. employees.get(1).setPosition("Middle"); //error: cannot find symbol // правильное решение, однако для этого необходимо проверять, является ли данный объект коллекции объектом требуемого класса, например используя instanceof Coder myCoder = employees.get(1); myCoder.setPosition("Middle"); Могу посоветовать книгу Кей Хорстманна "Java Библиотека профессонала" том 1. В главе о "Наследование" об этом рассказывается подробнее и с примерами кода.

Как избежать определения двух методов/конструкторов с одинаковыми параметрами?

#java #методы #конструктор #полиморфизм


Например, пишу класс Vector. Вполне естественно создавать объект, принимая в конструктор
или координаты x и y, или же принимая длину вектора и угол между направлением вектора
и положительным направлением оси OX. Однако я не могу написать так:

public class Vector {
    float x;
    float y;

    public Vector(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public Vector(float length, float alpha) {
        this.x = length*Math.cos(alpha);
        this.y = length*Math.sin(alpha);
    }
}


Компилятор, конечно, будет ругаться на неоднозначность в определении конструкторов.
Я не могу её избежать переставив аргументы местами, так как они одинаковых типов. Что
делать в таких ситуациях, когда метод/конструктор должен принимать два разных по смыслу
набора параметров, но получается так, что они имеют одинаковые типы?
    


Ответы

Ответ 1



В таких случаях не лишним будет использование статических фабричных методов. С использованием этого шаблона ваш код будет выглядить так: public static class Vector { public final float x; public final float y; private Vector(float x, float y) { this.x = x; this.y = y; } public static Vector coordinate(float x, float y) { return new Vector(x, y); } public static Vector radian(float length, float alpha) { return new Vector(length * Math.cos(alpha), length * Math.sin(alpha);) } } Так же, можно воспользоваться шаблоном проектирования builder. Здесь он выглядит немного громоздко, но в принципе, тоже применим: public static class Vector { ... public static VectorBuilder builder() { return new VectorBuilder(); } public static class VectorBuilder { private double length; private double alpha; private float x; private float y; public VectorBuilder setLength(float length) { this.length = length; compute(); return this; } public VectorBuilder setAlpha(float alpha) { this.alpha = alpha; compute(); return this; } private void compute() { this.x = (float) (length * Math.cos(alpha)); this.y = (float) (length * Math.sin(alpha)); } public VectorBuilder setX(float x) { this.x = x; return this; } public VectorBuilder setY(float y) { this.y = y; return this; } public Vector build() { return new Vector(x, y); } } }

Ответ 2



Что-то вроде фабрики: interface SomeInterface { float setX(float x, float y); float setY(float x, float y); } class Vector { float x; float y; static SomeInterface case1 = new SomeInterface() { @Override public float setX(float x, float y) { return x; } @Override public float setY(float x, float y) { return y; } }; static SomeInterface case2 = new SomeInterface() { @Override public float setX(float x, float y) { return x * (float) Math.cos(y); } @Override public float setY(float x, float y) { return x * (float) Math.sin(y); } }; public Vector(float x, float y, SomeInterface someInterface) { this.x = someInterface.setX(x,y); this.y = someInterface.setY(x,y); } } . . . new Vector(1.0f, 2.0f, Vector.case1); new Vector(1.0f, 2.0f, Vector.case2);

Ответ 3



Можете добавить костыль в виде третьего параметра и оставить один конструктор: public Vector(float a, float b, boolean isCoordinates) { if (isCoordinates) { this.x = a; this.y = b; } else { this.x = a*Math.cos(b); this.y = a*Math.sin(b); } }

Ответ 4



Может так? public class Vector { float x; float y; public Vector(float[] ху) { this.x = ху[0]; this.y = ху[1]; } public Vector(float length, float alpha) { this.x = length*Math.cos(alpha); this.y = length*Math.sin(alpha); } }

суббота, 4 января 2020 г.

Правильно понять полиморфизм

#php #ооп #полиморфизм


Всем привет!
Помогите понять полиморфизм правильно. Так как примеров в Интернете много и все они
отличаются друг от друга.

Как я его понимаю. Это когда свойство базового класса может использовать методы производных
классов.

Часто встречаю в Интернет два примера полиморфизма

Пример 1.

class user {
    public $type = 'default_user';
    public function setName (){
    }
    public function Call (){
          return $this->setName();
    }
}
class admin extends user {
     public function setName (){
         return $this->type = "admin";
     }
}
class superUsers extends user {
    public function setName (){
        return $this->type = "superUser";
    }
}
$super = new superUsers;
$admin = new admin;
echo $super->call();
echo $admin->call();


Пример 2.

class user {
    public $type = 'default_user';
    public function setName (){
    }
}
class admin extends user {
     public function setName (){
         return $this->type = "admin";
     }
}
class superUsers extends user {
    public function setName (){
        return $this->type = "superUser";
    }
}
$super = new superUsers;
$admin = new admin;
echo $super->setName();
echo $admin->setName();


Так как я понял и в первом примере и во втором примере показан полиморфизм , а называется
этот вид override.
    


Ответы

Ответ 1



Стоит начать с того, что полиморфизм бывает разный. В ООП полиморфизмом чаще всего называют способность классов с одинаковой спецификацией(интерфейсом) определять различную реализацию, что, в свою очередь, позволяет клиентскому коду абстрагироваться от этой самой реализации и работать с классом, исходя из его спецификации. Например, ваш метод может ожидать получить на входе объект типа UserInterface, при этом не зная, с каким конкретным подтипом типа UserInterface он будет работать. Таким образом вы можете единообразно обрабатывать различные типы данных, полагаясь на то, что каждый входной параметр соответствует спецификации UserInterface. При этом, в некоторых ООП языках используется т.н. "утиная типизация". Она же неявная типизация. Это когда клиентский код ожидает, что у используемого им объекта определен некоторый метод. Это позволяет использовать полиморфизм для обработки объектов, которые даже не обязательно входят в иерархию наследования. Например, если я напишу функцию: function ($object) { echo $object->setName(); } Она сможет корректно работать как с вашими наследниками от user, так и с любым другим объектом, у которого можно вызвать метод setName без параметров, который возвращал бы строку. Например: class DefinitelyNotAUser { public function setName() { return 'haduken!'; } } Анонимная функция описаная выше сможет работать с экземплярами этого класса точно так же как и с вашими наследниками от user. Это называется сигнатурным полиморфизмом. Такой подход широко распространен в Ruby, в PHP я бы не рекоммендовал его использовать. Функция реализованная для произвольного типа данных также будет примером полиморфизма (параметрического). Например: function handler($object, callable $action) { return $action($object); } $result = handler(new admin(), function ($object) { echo $object->setName(); }); Стоит заметить, что полиморфизм не яляется чем-то свойственным исключительно объектно-ориентированной парадигме. Полиморфизм присутствует и в функциональной, и в процедурной парадигме. Параметрический полиморфизм функции описаной для произвольного типа данных - пример для ФП. Вызов конкретной реализации функции в зависимости от типов переданных параметров - пример полиморфизма для процедурного программирования. Главное проявление полиморфизма - позднее связывание вызываемого кода с вызывающим, когда среда запуска умеет определять, какая именно вызывается реализация, исходя из контекста исполнения программы. Самое простое определение полиморфизма, которое вам сейчас необходимо запомнить, учитывая, что в тэгах к вопросу стоят PHP и ООП: "Один интерфейс - много реализаций". Что неправильно в вашем текущем понимании: вы зацикленны на отношениях базового и производных классов. Полиморфизм не об этом. Он о вызывающем (клиентском) коде и вызываемом, а также об их динамическом соответствии.

среда, 1 января 2020 г.

Как на практике применяется полиморфизм?

#java #ооп #полиморфизм #инкапсуляция


Вот само теоретическое понятие инкапсуляции легко запомнить - сокрытие данных - потому
что это применяется на практике, геттеры, сеттеры, приватные методы и переменные и
т.п. А как на практике применяется полиморфизм? Уверен, что я его все время использую,
но не знаю об этом. Может, когда узнаю, быстро запомню.

РЕД: Правильно ли я понимаю, что полиморфизм - это переопределение/перегрузка методов(и
все?)?
    


Ответы

Ответ 1



Полиморфизм - это свойство объекта менять своё поведение во время выполнения. Во время компиляции вызов метода переадресовывыется к классу, который его содержит, а во время выполнения к классу из которого он был создан. Вы часто используете родительские классы и интерфейсы для определения типа переменной, но создаете объекты подклассов и присваивание ссылку этой переменной. Например так: Parent variable = new Child(); Эта переменная может вызывать только методы, определённые в родительском классе, но если эти методы перекрыты в подклассе, то вызов метода переадресовывается в подкласс, в котором этот метод перекрыт. И поскольку родительский класс может иметь множество подклассов, в которых имеются виртуальные методы с такой же сигнатурой, перекрытые в родительском классе, и имеющими свою имплементацию, то вы можете присваивать переменной объект любого из этих подклассов, и тогда будет вызван соответствующий метод подкласса, несмотря на то что в родительском классе этот метод имеет свою имплементацию. Практически, интерфейсы и абстрактные методы чисто виртуальные, потому что не имеют имплементации, и использование их в коде приводит к полиморфизму, поскольку их имплементация находится в подклассах. На практике мы часто расширяем классы и подменяем изначальную имплементацию на другую и хотим, чтобы оно работало. И поскольку оно ничего не знает о наших объектах, то вызывает свои методы не имея ни малейшего понятия, что на самом деле выполнялось. Такой подход часто применяется при тестировании, когда вам нужно изменить поведение объектов, за счёт того что вы используете заготовки в рантайме, и подставляете их вместо реальных объектов.

Ответ 2



Допустим мы пишем генератор отчетности у нас есть некий класс, который принимает на вход один из отчетов и выполняет его построение. Отчеты у нас разные, но в общем то последовательность их построения одна и та же: public class ReportBuilder { public ReportBuilder() { } public createReport(Report report) { data = report.collectData(); excelFile = this.makeExcel(report, report.template()); this.publishAndSave(excelFile); } } Наш класс ReportBuilder запрашивает данные у класса отчета, размещает их в Excel, запросив у класса отчета шаблон и сохраняет результат. Теперь мы можем создать разные классы отчета, которые обладают своими шаблонами своей логикой сбора данных: abstract class Report { DataContainer collectData(); String template() } class ReportA extends Report { DataContainer collectData() { // собираем одни данные } String template() { return 'file1.xls'; } } class ReportB extends Report { DataContainer collectData() { // собираем одни данные } String template() { return 'file1.xls'; } } После этого мы можем делать вот так: reportBuilder = new ReportBuilder(); reportBuilder.createReport(new ReportA()); reportBuilder.createReport(new ReportB()); Таким образом: Общую логику построения отчетов мы разместили в ReportBuidler : он отвечает за работу с Excel, сохранение и публикацию отчета. Логика, отвечающая за сбор данных, расположена в классе каждого отчета. И полиморфизм, это то что позволяет передавать объекты разных классов в метод createReport() и быть уверенным, что в нем вызовутся методы collectData() и template() соответствующего класса.

Ответ 3



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

Ответ 4



Правильно ли я понимаю, что полиморфизм - это переопределение/перегрузка методов(и все?)? Нет, неправильно. Это как бы необходимое условие. Да, в Java перегрузка метода является полиморфизмом - то есть в Java все методы полиморфные, а скажем в С++ это не так. Перегрузка метода не тождественна полиморфности. В C++ есть еще ключевое слово virtual, то есть нужно, чтобы метод был еще обозначен как виртуальный, только тогда он будет обладать полиморфизмом. Самый простой пример полиморфизма, это то что все прогеры бессознательно используют - метод Object.toString(), например: class Child { private int age; @Override String toString() { return "age is "+age; } } Child child=new Child(); Object myObject=child; System.out.println(child); //печатаем объект Child System.out.println(myObject); //печатаем объект Object Выдача в обоих случаях будет одинаковой. Если бы не было полиморфизма, выдача в первом случае была бы: age is 4 Во втором случае: Object@456 то есть хэш значение объекта Но благодаря полиморфизму - выдача одинакова.

суббота, 28 декабря 2019 г.

Особенности полиморфизма

#java #jvm #полиморфизм


Всем доброго времени суток.

Только начал готовиться к собеседованию на Java Junior`а, как произошло небольшое
недопонимание по поводу полиморфизма. Я изучал Java Core по книге Кэти Сьерра и Берта
Бейтса("Изучаем Java. 2-е издание"). 

Там механизм наследования (и вообще хранения объектов в куче JVM) представлялся вот так:

public class A{
//code...
}

public class B extends A{
//code...
}

public class TestApp{

public void init(){
B val = new B();
}

}


И в JVM это все выглядит таким образом:



Допустим, у нас есть такой код:

public class Animal{

public void makeSomthing(){
System.out.println("Animal"); 
} 

}


public class Dog extends Animal{

//переопределенный метод
@Override
public void makeSomthing(){
System.out.println("Dog");
} 

public void fMagic(){

this.makeSomthing(); //Console: Dog    
super.makeSomthing(); //Console: Animal [?]   

}

}


public class TestApp{

public static void main(String[] args){

Dog dogPet = new Dog();

dogPet.fMagic();

}

}


Выходит, что при вызове метода fMagic() у объекта dogPet так же вызывается метод
makeSomthing() у "внешнего" объекта Dog и еще этот же метод вызывается у "внутреннего"
объекта Animal(у суперкласса). Как я понял, по логике этой книги, конструкция переопределения(@Override)
методов заключается в том, что метод по сути один, а когда мы его переопределяем, то
как-бы изменяем его во "внутреннем" объекте c помощью "внешнего". А уже при вызове
dogPet.fMagic() он вызывается изнутри.



Но при этом:

класс Dog

   public void fMagic(){

    this.makeSomthing(); //Console: Dog
    super.makeSomthing(); //Console: Animal [?]

    //Console:  Dog@74a14482 -  адрес Dog. Dog@74a14482 - адрес Animal. 
    System.out.println("links:"+"\n" + this + " -  адрес Dog. "+ super.getObjectLink()
+ " - адрес Animal.");

}


класс Animal

 public Object getObjectLink(){ return this; }


Но самое странное на мой взгляд заключается вот в этом:
При сужении типа до его родителя, вызов функции дает "Dog", а не "Animal". Т.е в
данном случае, переопределение метода в классе Dog изменяет метод saySomthing(). А
вот в случае вызова метода fMagic() у класса Dog, вызываются 2 разные функции И переопределение
функции суперкласса, как я понимаю, ничего не дает. 

 Animal dogPet = new Dog();

 dogPet.makeSomthing(); //Console: Dog


Суть вопроса: Как же именно все эти процессы и механизмы ООП происходят на самом
деле? И почему же при вызове fMagic() на консоль выводится две разные надписи "Dog"
и "Animal", а не одна и та же надпись "Dog", как в случае с Animal dogPet = new Dog();?
    


Ответы

Ответ 1



Во-первых завязывайте с такой терминологией, нет никаких внешних и внутренних объектов. У вас есть класс Dog, который расширяет класс Animal. Наследование представляет собой отношение типа является(is a). Следовательно в Вашем случае объект Dog является объектом Animal, а значит содержит в себе его поведение. Поведение родительского класса может использоваться в подклассе при помощи ключевого слова super, что Вы и продемонстрировали. Ключевое слово this используется для получения объектом ссылки на самого себя, что в данном случае делается по-умолчанию. Т.е. Вы получите такой же результат если опустите его. Чтобы лучше понять преимущества полиморфизма разберите паттерн проектирования "Стратегия". На мой взгляд один из самых ярких примеров. Также если Вы пишете Animal dog = new Dog(); Вы говорите машине: "Создай мне объект Dog и помести его в переменную типа Animal". Вы можете это сделать, поскольку Dog является Animal. Однако обращаясь к этой переменной у Вас будет вызываться метод класса Dog поскольку в ней содержится объект Dog. Animal здесь - это тип переменной, а не объекта.

вторник, 24 декабря 2019 г.

Collection и ArrayList в Java

#java #полиморфизм


В чём преимущество такой записи:

Collection collection = new ArrayList();


перед такой:

ArrayList collection = new ArrayList();


Ведь мы теряем уникальные методы ArrayList в 1-ом варианте?
    


Ответы

Ответ 1



Ведь мы теряем уникальные методы ArrayList в 1-ом варианте? Нет не теряете. В обоих вариантах вы создаете обьект ArrayList. Разница заключается в том, что используя 1-ый вариант вы можете делать так: collection = new LinkedList(); collection = new HashSet(); и т.д (то есть, если в будущем Вы поймете что порядок элементов для Вас не важен и, следовательно, захотите использовать HashSet вместо ArrayList Вам нужно будет изменить меньше кода). Если говорить конкретнее, то в 1-ом варианте Вы указываете, что переменная с именем collection имеет тип Collection. Так как Collection это интерфейс, который реализовуют все коллекции, то Вы можете сослаться на любую коллекцию.

Расширение интерфейса библиотеки

#cpp #visual_cpp #наследование #полиморфизм #множественное_наследование


В книге Брюса Эккеля "Философия С++ часть 2" автор приводит пример использование
множественного наследование в качестве средства для расширения абстрактного класса
библиотеки, к которой нет доступа. 
Суть задачи такова: есть интерфейс Vendor, редактировать код которого нельзя. Есть
класс Pastel, с которым должны использоваться функции из библиотеки, в которой определен
Vendor (принимают ссылку или указатель на Vendor), но функционала этого интерфейса
недостаточно. 
Автор приводит решение:

class MyBase{
public:
    virtual void v()const=0;
    virtual void g()const=0;
}
class Pastel: public MyBase, public Vendor{
/*...*/
}


Вопрос: в чем преимущество такого подхода к расширению над таким:

class MyBase: public Vendor{
public:
    virtual void v()const=0;
    virtual void g()const=0;
}
class Pastel: public MyBase{
/*...*/
}

    


Ответы

Ответ 1



Представьте, что MyDeviceInterface - это интерфейсный класс, который определяет некоторые методы для интеграции классов в проект. class MyDeviceInterface { virtual int device_id() const = 0; virtual char device_name() const = 0; } А где-то в проекте есть общий метод опроса девайсов, типа ... std::vector my_devices = GetAvailableDevices(); for (MyDeviceInterface* device : my_devices) { std::cout << device->device_id() << std::endl; std::cout << device->device_name() << std::endl; } ... Теперь представьте, что у вас есть 5 классов девайсов, которые нельзя редактировать: Device1...Device5, причем каждый из этих классов имеет свою неповторимую реализацию методов :) В первом случае вам необходимо будет написать 5 классов для своего проекта, в которых вы просто опишите реализацию работы интерфейса class MyDevice1 : public MyDeviceInterface, public Device1 { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } } ... class MyDevice5 : public MyDeviceInterface, public Device5 { int device_id() const { return Device5::get_device5_id(); } char device_name() const { return Device5::get_device5_name(); } } Получается чистый и красивый код, интерфейсный класс MyDeviceInterface собственно определяет интерфейс, а реализацию уже можно брать из классов Device1..5. Также наличие класса интерфейса позволяет манипулировать всеми девайсами как некими абстрактными сущностями, которые подчиняются общим правилам поведения, а жесткое разделение наследования защищает наши классы друг от друга (класс MyDevice1 ничего не знает о детялях реализации классов MyDevice2 и т.п.) Также довольно просто добавлять новые девайсы в такой код, мы просто штампуем наследные классы от MyDeviceInterface и DeviceN. Во втором случае у вас такое тоже получится, но придется создать какой-то супер-интерфейс: class MyDeviceInterface : public Device1, ... , public Device5 { virtual int device_id() const = 0; virtual char device_name() const = 0; } и классы реализации class MyDevice1 : public MyDeviceInterface { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } } ... class MyDevice5 : public MyDeviceInterface { int device_id() const { return Device5::get_device5_id(); } char device_name() const { return Device5::get_device5_name(); } } Но, в этом случае все девайсы знают друг про друга, что небезопасно, методы классов Device1..5 могут иметь коллизии имен и тому подобное. Добавление каждого нового девайса будет раздувать MyDeviceInterface все больше и больше, и, в конечном итоге, это все станет невозможно сопровождать и проект умрет. Можно создать просто 5 классов девайсов class MyDevice1 : public Device1 { int device_id() const { return Device1::get_device1_id(); } char device_name() const { return Device1::get_device1_name(); } } Но тогда вы теряете абстрактность, т.к. у классов нету общего родителя. Эту потерю можно компенсировать шаблонами, но опять же при добавлении каждого нового девайса код будет неизбежно раздуваться за счет разворачивания шаблонов, что может быть критично для устройств с ограниченным объемом памяти. Кроме того шаблоны трудны в отладке. Однако такой способ тоже может иметь место на жизнь.

понедельник, 23 декабря 2019 г.

Полиморфизм в реляционных БД. Возможно ли?

#sql #база_данных #полиморфизм


Есть база данных (postgresql), содержащая три таблицы - 
Люди (ID, Имя, Фамилия, Отчество, Наименование), 
Организации (ID, Наименование, Адрес, Расчетный счет), 
Автомобили (ID, Марка, Модель, Пробег, Владелец). 
Суть проблемы:
Необходимо создать связи между автомобилями и владельцами. Владельцем автомобиля
может быть как человек (запись в таблице Люди), так и организация (запись в таблице
Организации).
Вопрос:
Каким образом можно осуществить данную связь? Может ли внешний ключ быть полиморфным
и, следовательно, каким образом во время JOIN узнать с какой таблицей объединятся(как
хранить информацию о типе во внешнем ключе)?
PS:
Заранее извиняюсь за, возможно, глупый вопрос, и прошу учесть, тот факт, что с SQL
и РБД как таковыми только познакомился, и есть острая необходимость решить вышеуказанную
проблему в крайне короткий срок.
Update:
В первой версии вопроса не указал общее поле - Наименование - в случае если Владелец
- человек, его Наименование - например, Иванов И.И.
В качестве примера приведу вымышленный код, думаю так будет понятней:

SELECT Авто.Марка, Авто.Модель, Наименование FROM Авто
INNER JOIN Авто.Внешний_ключ_владельца.Таблица ON Авто.Внешний_ключ_владельца.ID
= Авто.Владелец.ID


И возможный результат:
"Daewoo" "Nexia" "Иванов. И.И."
"Ford" "Focus" "ООО ТОРГОПТ"
"Schevrolet" "Camaro" "Сидоров С.В."
    


Ответы

Ответ 1



Структура данных должна быть такой, чтобы запросы по ней были максимально простыми. Если вам в вашем запросе нужно только Наименование от владельца, то в разделении людей и организаций нет никакого смысла. Это должна быть одна таблица Владельцы. Дополнительные поля могут быть как в той же таблице, так и в дочерних типа "данные юрлиц"/"данные физлиц". Все зависит от запросов, сравните ваш вариант: select cars.*, persons.fullname from cars join persons on cars.owner = persons.id and cars.isorg = 0 union all select cars.*, orgs.fullname from cars join orgs on cars.owner = orgs.id and cars.isorg = 1 и запрос с одной таблицей: select cars.*, owners.fullname from cars join owners on cars.owner = owners.id Если вам понадобятся все дополнительные поля в этом запросе (надо еще придумать как их красиво выводить в одной таблице) вы получите одни и те же данные: будут null для людей в адресе и расчетном счете, и null для фио для организаций. Если вы планируете более сложную логику запросов (которую вы в вопросе не указали) то идите от нее. И идите путем простоты.

Ответ 2



Я бы предложил добавить в таблицу Автомобили поле "Тип Владельца", и в нем хранить лишь два варианта: "чел" и "орг". В зависимости от содержимого этого поля связывать поле Владелец либо с таблицей Людей, либо Организаций.

суббота, 21 декабря 2019 г.

В каких случаях использовать указатель на базовый класс, а в каких на наследник?

#cpp #полиморфизм


Не могли бы вы прокомментировать этот момент: 


  Используя виртуальные функции для обеспечения полиморфизма необходимо использовать
указатель именно на базовый класс.


В каких случаях необходимо использовать указатель на класс наследник, а в каких на
базовый класс?

Просто недавно наткнулся на такой пример, он корректно реализован?

#include 
using namespace std;

class Base { //Базовый класс
public:
   int base_data;
   Base(int base_data);//Конструктор класса
   virtual void Virtual_method();   // Виртуальный метод
   void Nonvirtual_method();   // Не виртуальный метод
   virtual~Base(); //Виртуальный деструктор
};

Base::Base(int base_data){
    this->base_data = base_data;
    cout << "Конструктор базового" << endl;
}

void Base::Virtual_method() {
    cout << "Виртуальный метод базового класса\n";
}

void Base::Nonvirtual_method() {
    cout << "Не виртуальный метод базового класса\n";
}

Base::~Base(){
    cout << "Деструктор базового класса\n";
}

class Derived : public Base {//Класс наследник
public:
    double derived_data;
    Derived(double derived_data, int base_data);
    void Virtual_method();   // Переопределённый виртуальный метод базового класса
    void Nonvirtual_method();   //Не виртуальна функция
    ~Derived();
};

Derived::Derived(double derived_data, int base_data):Base(base_data){
    this->derived_data = derived_data;
    cout << "Конструктор наследника" << endl;
}


void Derived::Virtual_method() {
    cout << "Переопределённый виртуальный метод класса наследника\n";
}

void Derived::Nonvirtual_method() {
    cout << "Не виртуальный метод класса наследника\n";
}

Derived::~Derived(){
    cout << "Деструтор класса наследника\n";
}

int main() {
    setlocale(LC_ALL, "RUS");
    Derived derived(2.0, 1);//Объявили объект класса наследника

    Derived *pDerived = &derived;//Объявили указатель на класс наследник, присвоив
ему ссылку на объект класса наследника
    Base    *pBase    = &derived;//Объявили указатель на базовый класс, присвоив
ему ссылку на объект класса наследника

    pBase->Virtual_method(); // Вызов виртуального метода
    pBase->Nonvirtual_method(); // Вызов не виртуального метода
    pDerived->Virtual_method(); // Вызов виртуального метода
    pDerived->Nonvirtual_method(); // Вызов не виртуального метода
    cout << "base_data = " << derived.base_data << "\nderived_data = " << derived.derived_data
<< "\n";
    return 0;
}

    


Ответы

Ответ 1



Обычно, когда говорят о полиморфизме в программировании, то пытаются дать определение этого слова в терминах языков программирования. Однако на мой взгляд более удачное определение полиморфизма дается в биологии. Только слово организм в этом определении следует заменить словом объект в терминах программирования.:) Полиморфи́зм в биологии (от др.-греч. πολύμορφος — многообразный) — способность некоторых организмов существовать в состояниях с различной внутренней структурой или в разных внешних формах В вашей демонстрационной программе определяется указатель типа Base *, то есть статический тип объектов, адресуемых этим указателем является тип Base. Base *pBase; Когда такому указателю присваивается адрес объекта производного класса, то объект, к которому происходит обращение через этот указатель, рассматривается как объект типа Base. Фактически, вся информация о том, что этот объект на самом деле является производного класса, а не базового, теряется. так как статический тип указателя Base *. Однако благодаря наличию такого средства, как виртуальные функции, позволяет получать многообразие поведения адресуемого указателем объекта в зависимости от того, какой тип на самом деле имеет адресуемый объект. То есть через виртуальные функции имеется возможность различать реальный тип адресуемых объектов и обращаться к их уникальным свойствам. И это демонстрируется ваша программа на примере данных предложения Base *pBase = &derived;//Объявили указатель на базовый класс, присвоив ему ссылку на объект класса наследника pBase->Virtual_method(); // Вызов виртуального метода pBase->Nonvirtual_method(); // Вызов не виртуального метода Как видно указатель типа Base * на самом деле адресует объект производного класса. Но если вызывать не виртуальный метод pBase->Nonvirtual_method(); // Вызов не виртуального метода то вся информация о производном классе недоступна, так как компилятор вызывает функции в соответствии со статическим типом объекта, адресуемого указателем, то есть в соответствии с типом Base. Компилятор при поиске имени функции, которую следует вызвать смотрит определение базового класса. Он ничего не знает о производных классах. Ежели вы вызываете виртуальную функцию pBase->Virtual_method(); // Вызов виртуального метода то компилятор также при поиске имени вызываемой функции смотрит определение базового класса. Однако тут происходит некоторый фокус. Производный класс подменил определение виртуальной функции в базовом классе своим определением этой функции. Технически это делается следующим образом. Если класс объявляет виртуальную функцию, то он создает таблицу указателей на виртуальные функции, объявленные в своем определении. Производные же классы, которые переопределяют виртуальные функции подставляют в эту таблицу базового класса адреса своих определений функций. Поэтому динамически во время выполнения программы вызывается та функция, чей адрес находится в таблице адресов виртуальных функций. Что касается вашего вопроса В каких случаях необходимо использовать указатель на класс наследник, а в каких на базовый класс? То когда вы объявляете указатель на класс наследник, то вы теряете полиморфизм, так как вы можете работать только с объектами класса наследника (я предполагаю, что наследник в свою очередь не наследуется другими классами). А когда вы хотите достичь поведение, соответствующее полиморфизму, то 1) базовый класс должен содержать виртуальные функции т 2) следует объявить указатель или ссылку, имеющую статический тип указателя или ссылки на объекты базового класса, а инициализировать их объектами производных классов. И тогда вы получите способность некоторых объектов (организмов) существовать в состояниях с различной внутренней структурой или в разных внешних формах Примечание. Если при вызове виртуальной функции вы указываете ее квалифицированное имя, то "виртуальность" исчезает. Вызывается функция класса, определенная в этом же классе. Рассмотрите пример #include struct Base { virtual ~Base() { } virtual void virtual_function() const { std::cout << "Base::virtual_function() is called" << std::endl; } }; struct Derived : Base { void virtual_function() const override { std::cout << "Derived::virtual_function() is called" << std::endl; } }; int main() { Derived d; Base &rBase = d;; rBase.virtual_function(); rBase.Base::virtual_function(); return 0; } Ее вывод на консоль будет следующим Derived::virtual_function() is called Base::virtual_function() is called В первом случае вступает в игру полиморфизм. Вызывается переопределенная виртуальная функция производного класса Derived, хотя базовый тип ссылки - это класс Base. Во втором случае, когда используется квалифицированное имя, "виртуальность" фукнции пропадает, и вызывается реализация функции базового класса Base.

Ответ 2



Механизм виртуальных функций позволяет обеспечить так называемое "позднее связывание". Т.е. какая конкретно функция будет вызвана становится известно только в момент выполнения программы. В коде это выглядит как вызов виртуальной функции через указатель или ссылку на базовый класс. Т.о. базовый класс нужно использовать тогда, когда заранее неизвестно, вызов какой функции (т.е. какого конкретного наследника) нужен. Если же заранее известно, какой код надо вызвать, то использовать механизм виртуальных функций не требуется. Т.е. вызываем невиртуальную функцию на том типе, на каком хотим чтобы она была вызвана.

пятница, 20 декабря 2019 г.

Реализация механизма полиморфизма в Java

#java #jvm #полиморфизм


Что из себя представляет механизм полиморфизма в Java и как он работает? Если это
зависит от реализации конкретной JVM, то хотелось бы увидеть это на примере какой-нибудь
JVM, например HotSpot. 

Для уточнения, в С++ полиморфизм реализуется за счет таблицы виртуальных функций
для каждого класса, указатель на которую неявно хранится в каждом экземпляре класса.
А как это сделано в Java? 
    


Ответы

Ответ 1



Полиморфизм метода, определённого в классе, достигается за счёт выбора по таблице виртуальных методов примерно как в C++. То есть накладные расходы на вызов — это взять у объекта classword (который записан в его заголовке), добавить к нему известное заранее смещение и вызвать метод по указанному адресу. Полиморфизм метода, определённого в интерфейсе, достигается более сложным путём: в структуре описания класса ищется запись, относящаяся к данному интерфейсу, и в ней уже ищется нужный метод. То есть, скажем, public static T getFirst(List list) { return list.get(0); } public static T getFirst(AbstractList list) { return list.get(0); } Первый случай может оказаться медленнее, потому что мы вызываем метод интерфейса. Хотя метод один и тот же, но разница есть. Даже в байткоде две разные инструкции — invokeinterface и invokevirtual. JIT-компилятор HotSpot агрессивно использует технику девиртуализации. Естественно в обычный статический вызов превратится вызов final-метода или метода final-класса. Также если runtime-таблица типов говорит, что данный метод нигде не переопределён, вызов будет статическим: public static T getFirst(ArrayList list) { return list.get(0); } Хотя ArrayList не final-класс и метод get тоже не final, если мы знаем, что он не переопределён на данный момент ни в одном загруженном классе, мы можем сделать вызов статическим. Если будет загружен новый класс, который нарушит это условие, JIT-компилятор перекомпилирует этот метод. Если есть варианты, используется профиль типов. К примеру, если этот код уже выполнялся 5000 раз и из них в 4990 случаях вызывался конкретный метод ArrayList.get, то JIT-компилированный код станет примерно таким: public static T getFirst(List list) { if(list.getClass() == ArrayList.class) { return ((ArrayList)list).get(0); } // обновить профиль типов return list.get(0); } Проверка list.getClass() == ArrayList.class весьма быстрая — это достать classword (по скорости как прочитать поле объекта) и сравнить с константой (на момент JIT-компиляции точно известен classword для класса ArrayList). Branch-prediction тоже хорошо отработает, если условие в подавляющем большинстве случаев выполняется. Если из 5000 вызовов было 3000 ArrayList и 1990 LinkedList, код будет примерно таким: public static T getFirst(List list) { if(list.getClass() == ArrayList.class) { return ((ArrayList)list).get(0); } else if(list.getClass() == LinkedList.class) { return ((LinkedList)list).get(0); } // обновить профиль типов return list.get(0); } Это биморфный вызов. Если же популярных вариантов было больше двух, то тогда уж вызов останется честным виртуальным. Разумеется, если вы в одном методе вызываете несколько методов неизвестного объекта, переданного параметром (или в цикле вызываете метод много раз), то тип проверяться будет только один раз. Если вызов удалось девиртуализовать (хотя бы в биморфный вариант), то дальше агрессивно применяется инлайнинг (видал своими глазами как в один метод инлайнилось штук 70 других на глубину вызовов до 8-9: в первый инлайнится второй, в него третий и т. д.). Инлайнинг открывает дорогу к тонне других оптимизаций. Как вы уже поняли, первоначально код выполняется во-первых, медленнее, а во-вторых в режиме профилирования. То есть при каждом вызове метода не просто происходит вызов, но и обновляется таблица статистики, где указывается, какой конкретно класс тут был. Когда статистика собрана, метод перекомпилируется с учётом неё. При этом если есть быстрая и медленная ветка, то медленная будет обновлять статистику дальше. Например, если сценарий использования программы поменялся, то метод может быть снова перекомпилирован.

Java - обращение к полю подкласса, если его экземпляр присвоен ссылке на супер-класс и поля имеют одинаковые имена и дефолтный модификатор доступа

#java #ооп #полиморфизм


Изучая наследование и полиморфизм в java наткнулся на такой пример:

class A {
 int a = 5;
 String doA() {
  return“ a1“;
 }
 protected static String doA2() {
  return“ a2“;
 }
}
class B extends A {
 int a = 7;
 String doA() {
  return“ b1“;
 }
 public static String doA2() {
  return“ b2“;
 }
 void go() {
  A myA = new B();
  System.out.print(myA.doA() + myA.doA2() + myA.a);
 }
 public static void main(String[] args) {
  new B().go();
 }
}


Результат выполнения программы: "b1 a2 5"


b1 - тут понятно. фактический тип класса является B, через динамическое связывание
компилятор вызывает метод doA(), определенный в классе B.
a2 - метод doA2() определен статическим, соответственно происходит ранее связывание,
компилятор вызывает метод класса A, а не экземпляра
5 - вот тут-то для меня и происходит магия. почему не 7? переменная не static и не
final? фактический тип класса B

    


Ответы

Ответ 1



A myA = new B(); ^ Потому что поля в Java не являются полиморфными, соответственно используется класс указателя, в данном случае это класс A. По поводу методов: в Java все методы являются виртуальными, соответственно используется динамическое (или, как его еще называют, позднее) связывание. Поэтому на этапе выполнения JVM определяет тип объекта B, на который ссылается указатель myA и вызывает соответствующую реализацию метода doA() класса B.

Ответ 2



Если вкратце, то всё дело в том, что у вас одинаковые имена переменных и вы фактически имеете две переменные с именем a и происходит Variable shadowing, т.е. когда одна переменная перекрывает другую. В вашем же случае происходит еще и случай описанный в документации: If an expression name consists of a single Identifier, then there must be exactly one visible declaration denoting either a local variable, parameter or field in scope at the point at which the the Identifier occurs. Otherwise, a compile-time error occurs. If the declaration declares a final field, the meaning of the name is the value of that field. Otherwise, the meaning of the expression name is the variable declared by the declaration. Т.е. переменная ищется в скопе класса A, даже если это фактически является классом B. Если же вы хотите, чтобы менялось значение переменной для класса B. То надо сделать, например, так: class B extends A { //int a = 7; B() { a = 7; } // весь остальной код }

Ответ 3



Тип класса B, но в данном случае он скастован к классу A, а поскольку и в A и в B поле a приватное (по умолчанию, без модификатора, оно приватное), то мы обращаемся к переменной a именно класса A, где оно равно 5

суббота, 14 декабря 2019 г.

Не могу понять полиморфный вызов метода

#java #ооп #полиморфизм


У меня есть класс Pair:

public class Pair {
   public void getObject(Object o){
       System.out.println("Text from Pair");}
 }


От него наследуется класс Detail, у которого есть такой же метод, но он принимает
объект другого типа:

public class Detail extends Pair {
  public void getObject(Date o){
    System.out.println("Text from Detail");}
}


В Main у меня вот это:

Detail d = new Detail();
Pair p = d;
p.getObject(new Date());


И по сути должен исполняться полиморфный вызов метода, но для объекта Detail вызывается
метод getObject с класса Pair, почему так? Как именно здесь работает вызов метода?
    


Ответы

Ответ 1



В java есть 3 поведения, относящиеся к Вашей теме: overriding (официальная документация здесь) overloading (официальная документация здесь - секция "Overloading Methods") hiding (официальная документация здесь) Поскольку в документации сказано, что: The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called a covariant return type. и в Вашем случае у Вас нарушено правило number and type of parameters, то получается это не override. В то же время, касательно overload написано следующее: The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists (there are some qualifications to this that will be discussed in the lesson titled "Interfaces and Inheritance"). Что относится именно к Вашей ситуации. Таким образом, вызывая метод по ссылке Pair p Вы будете вызывать метод класса Pair, не смотря на то, что фактически это объект Detail. Кроме того, в java есть механизм отслеживания подобных недоразумений в виде @Override аннотации, пометив ей метод компилятор выдаст ошибку в случае, если это не override. В Вашем случае: @Override public void getObject(Date o){ System.out.println("Text from Detail");} Компилятор выдаст ошибку "Method doesn't override method from its superclass". Ну и в конце концов, чтобы добиться настоящего overrid'a в классе Detail метод должен выглядеть следующим образом: public void getObject(Object o){ System.out.println("Text from Detail");} В таком случае Вы получите ожидаемый результат. Надеюсь удалось подробно все объяснить. Удачи в дальнейшем изучении =).

Ответ 2



Для того, чтобы функция производного класса считалась полиморфным вариантом функции базового класса, у них должны совпадать не только имена, но и типы аргументов. В вашем случае две функции считаются всего лишь перегрузкой имени getObject. И в p.getObject(new Date()); происходит вызов класса Pair. похожий вопрос

Ответ 3



Что бы понять что происходит нужно уточнить несколько понятий: Перезрузка. Вы можете иметь в одном классе 2 метода с одинаковыми именем но разными параметрами. Переопределение. Необходимо полное совпадение типов входящих параметров. Наследование. Предоставляет поля и методы родительского класса Это не определяния понятий перегрузка, переопределение и наследование а только некоторые моменты имеющие отношение к вопросу. В вашем случае объект Detail имеет два варианта метода getObject, один тот который вы определили явно прямо в классе : public void getObject(Date o), и второй который он получил от родителя в результате наследования : public void getObject(Object o). Когда VM нужно определить какой метод вызывать, она смотрит какого типа параметр вы отправили в метод, и по типу этого параметра выбирает подходищий выриант перегрузки. Следуя этой логике можно подумать что должен быть вызван метод с параметром типа Date. Но вы делайте приведение типов: Detail d = new Detail(); Pair p = d; //Вот здесь. Это равнозначно Pair p = (Pair) d; p.getObject(new Date()); А список методов для объекта всегда определяется по типу указателя. И так как тип указателя у вас теперь Pair а у него есть только один вариант этого метода то он и вызвался. Ну а так как он принимает Object то и Date "заглотил не морщась". И вызвался единственный имеющийся у Pair метод getObject(Object).

пятница, 13 декабря 2019 г.

Почему полиморфизм это удобно? [дубликат]

#java #полиморфизм


        
             
                
                    
                        
                            This question already has answers here:
                            
                        
                    
                
                        
                            Почему необходимо инициализировать коллекции именно так?
                                
                                    (4 ответа)
                                
                        
                        
                            Правильно понять полиморфизм
                                
                                    (1 ответ)
                                
                        
                                Closed 1 год назад.
            
                    
На собеседовании по java спросили - зачем писать, к примеру, 

Фигура треугольник = new Треугольник(), 


а не просто 

Треугольник треугольник = new Треугольник() 


и почему это удобно?
    


Ответы

Ответ 1



Можно здесь много антимоний развести, конечно: что это ни фига не полиморфизьм, что это вообще не об этом, что это и т.д. Отвечу так: Когда пишем: Фигура треугольник = new Треугольник(), мы подразумеваем, что треугольник рассматриваем как наследник класса Фигура (ну или что Треугольник реализует интерфейс Фигура) Что подразумевает, что методы класса/интерфейса Фигура реализованы в классе Треугольник (например Фигура.площадь() - реализуется в разных классах по разному) Кроме этого мы неявно подразумеваем, что возможно где-то есть класс(ы) Четырехугольник, Многоугольник или даже Серо-буро-малиновый-овал, которые также наследники класса Фигура Все это вместе дает нам право сказать, что вместо Треугольник мы переходим на другой уровень абстракции Фигура и можем например рассматривать не отдельные коллекции треугольников, многоугольников, а сразу 1 коллекцию фигур - ну и т.д. и т.п. Далее переходим к заключении, что написавший сию строку прогер - невероятно крут и умеет абстрактно мыслить и все такое прочее.

Ответ 2



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

воскресенье, 8 декабря 2019 г.

Создание объекта класса в Java

#java #ооп #полиморфизм


начал разбираться с полиморфизмом и нашел следующий пример:
есть класс Soldier, и General - его наследник. У первого есть метод getHealth(),
у второго - getSlogan().

Не понятен принцип создания объекта класса.
Есть следующие строчки кода:

Soldier warrior1 = new Soldier();
General warrior2 = new Soldier();
Soldier warrior3 = new General();


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

И второй вопрос ориентируясь по третей строчке я не могу применить warrior3.getSlogan(),только
warrior3.getHealth() ? А если бы метод getSlogan() был в солдате, то такой вызов был
бы возможен? И это благодаря полиморфизму?
    


Ответы

Ответ 1



Давайте начнем с понятия полиморфизм. Полиформизм - это такой свойство в данном случае метода, когда один и тот же метод ведет себя по разному. Возмем пример из жизни. Есть класс Животное. Есть класс Утка, которая насаледутся от Животное. Есть еще один класс Собака, которое тоже наследуется от Животное. Животное это абстракция, где мы можем просто определить метод Голос(). Мы его объявим абстрактным или виртуальным. Зависит от языка. Живтоных много, и каждый класс Утка и Собака будут иметь свои реализации для метода Голос(). Теперь когда мы можем создать Утку и Собаку и привести их к Живтоному. Псевдо код будет выглятеть так: Живтоное animals[2]; animals[0] = new Утка(); animals[1] = new Собака(); for (animal in animals) { animal.Голос(); } Т.е. при вызове метода Голос() мы не знаем реально, с каким объектом мы имеем дело, но можем вызвать именно его реализацию. В этом вся сила полиморфизма. Теперь ко вопросу про доступные методы. Тип переменной animal определяет доступные методы. Именно поэтому ни один метод из класса Утки не известен классу Животное. Приведение типов. Это отдельная большая тема. У нас есть возможно приводить типы двигаясь вверх или вниз по дереву наследования. Замечу, что двигаться вниз безопаснее в силу того, что мы знаем текущий тип объекта и можем точно знать от кого он наследуюется. Движение же верх (от абстракции к конкретике) более опасно и именно тут лучше использовать явное приведение типа, что бы подсказать компилятору. Все конечно зависит от языка. В вашем случае приведение к типу генерал невозможно т.к. объекта генерал в принцепе не существует. Есть только объект типа Солдат. Я очень рекомендую для начала разобраться в понятиях объект и тип. Тогда многое в ООП станет более понятным.

Ответ 2



Первая строчка создаст экземпляр Soldier и присвоит его переменной warrior1 Вторая строчка даже не скомпилируется, т.к. создается объект типа Soldier, а присваивается переменной warrior2 более специализированного типа General. Третья строчка создаст экземпляр General и присвоит его переменной warrior3 типа Soldier. Все нормально, генерал является солдатом в данной модели. Почему так и когда надо подобное приведение? Непосредственно в данном примере это приведение особого смысла не имеет. Оно лишь демонстрирует, что такое приведение возможно. В более общей картине мира, у вас может быть где-то еще метод kill(Soldier soldier), и, благодаря наследованию, вы сможете передавать в него и солдат и генералов. Внутри же методу kill на аргументе soldier будут доступны только те методы, которые объявлены в классе Soldier. Говорят, что метод kill абстрагируется от деталей реализации конкретного солдата. я не могу применить warrior3.getSlogan(), только warrior3.getHealth()? А если бы метод getSlogan() был в солдате, то такой вызов был бы возможен? И это благодаря полиморфизму? Все верно. И да, если бы в классе Soldier был аналогичный метод getSlogan(), то был бы возможен вызов warrior.getSlogan(). Причем, благодаря полиморфизму, был бы вызван именно генеральский getSlogan().

Ответ 3



Полиморфизм позволяет обращаться с объектом наследника так как будто это объект класса родителя. public class A { private int var = 1; int getVar() { return this.var; } } public class B extends A { } и где-то в коде: A a = new B(); a.getVar(); И это будет работать. НО! на оборот нельзя: B a = new A(); сразу ошибку выдаст так работать не будет! То есть указатель от более высокого по генеалогии класса может служить указателем для всех его наследников.

среда, 27 ноября 2019 г.

Зачем класс реализует интерфейс, который наследуется другим интерфейсом этого класса?


Просматривая исходник AutoMapper, наткнулся на интересную вещь:

Класс Mapper:

public class Mapper : IRuntimeMapper, IMapper
{
//...


Интерфейс IRuntimeMapper:

public interface IRuntimeMapper : IMapper
{
//...




Вопрос

Зачем Mapper реализует IMapper, если IRuntimeMapper уже наследует его?
Такая "ошибка" возникла в ходе расширения библиотеки или это нормальная практика? 
    


Ответы

Ответ 1



Если вы просматривали декомпилированный исходник, то это нормально. Для сравнения: Исходники BlockingCollection и описание на МСДН. JetBrains decompiler показывае состояние как в справке, несмотря на то, что сорцы явно другие. У меня есть две основных идеи, почему так. Либо информация о всех реализуемых интерфейса хранится в одном месте и не пытается строить дерево наследования каждый раз(и по этой инфе декомпилят инструменты), либо это сделано просто для удобства, чтобы не пытаться вспоминать каждый раз, а реализует ли каждый из этих интерфейсов ещё какие то.

Ответ 2



В исходниках автомаппера на гитхабе строчки public class Mapper : IRuntimeMapper, IMapper нет и никогда не было, судя по логам. Декомпиляторы могут показывать всю цепочку наследования интерфейсов интерфейсов п достаточно странной причине - наследование классов и интерфейсов в C# работает по разному. В случае иерархии классов каждый потомок имеет ровно одного явного родителя: class A {} class B : A {} class C : B {} В случае интерфейса срабатывает механизм схлопывания иерархии: A class or struct that directly implements an interface also directly implement all of the interface's base interfaces implicitly. This is true even if the class or struct doesn't explicitly list all base interfaces in the base class list. Компилятор ищет все базовые интерфейсы, строит из них полный список и дописывае их в качестве реализуемых. Так что он превращает interface A { } interface B : A { } public class SomeClass : B { } в public class SomeClass : B, A { } Именно в таком виде список унаследованных интерфейсов сохраняется в метаданных, декомпиляторы просто не могут выяснить - было ли упоминание двух интерфейсов в оригинальном коде, или их дописал компилятор.

Ответ 3



Хотя, как заметил @PashaPash строки public class Mapper : IRuntimeMapper, IMapper на GitHub никогда не было. Подобные ситуации далеко не редки. Например, стандартный класс List реализует следующие интерфейсы: public class List : IList, System.Collections.IList при этом в документации указан гораздо больший список: public class List : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable, ... причем что важно: IList наследует ICollection и IEnumerable ICollection наследует IEnumerable и IEnumerable IEnumerable наследует IEnumerable Казалось бы к чему все это, какая цель? Ответ на подобный вопрос Эрик Липперт объясняет так, когда похожий код встречается в: коде другого разработчика, автор преследует цель (на взгляд автора) сделать код проще для понимания и более самодокументированным когда дело касается документации преследуется цель предоставить максимум информации и избавить вас от головной связанной с самостоятельным выяснением цепочки наследования если речь заходит об инструментах декомпиляции, преследуется цель показать больш информации, нежели скрыть необходимую ее часть от вас. Кроме того, поскольку подобны инструменты опираются только на метаданные, а указание полного списка интерфейсов не является обязательным, инструмент может не знать, содержит ли исходный код весь список или нет. Поэтому лучше ошибиться в сторону избытка информации

суббота, 13 июля 2019 г.

Принцип подстановки Лисков и предусловия

Принцип подстановки Лисков прямо подразумевает, что предусловия не должны усиливаться в подклассах. Это логично (потому что переопределённый метод подкласса, для которого входные данные окажутся неприемлимыми, будет работать некорректно/выбросит эксепшн). Но как тогда спроектировать следующую архитектуру:
Есть класс скажем Vehicle (можно его сделать абстрактным, не суть важно), у него есть виртуальный метод Weight (пусть этот метод делает какие-то заумные вычисления для каждого типа транспортного средства в зависимости от массы, не столь важно):
public abstract class Vehicle { public virtual void Weight(int w) {
} }
Теперь у нас есть класс Motorcycle, который мы наследуем от Vehicle, но мы должны ограничить его макс массу скажем в 200 кг.
public class Motorcycle : Vehicle { public override void Weight(int w) { if (w > 200) throw new WeightOverflowException(); } }
вроде всё прекрасно, логичное наследование, с наследованием всех свойств и поведения, но нарушает же LSP:
List list2 = new List(); list2.Add(new Motorcycle());
foreach (var item in list2) { item.Weight(400); }
полетит же эксепшн WeightOverflowException, о котором базовый класс не должен вообще ничего знать. Как правильно спроектировать такую задачу?


Ответ

В проектировании по контракту существует понятие "доступности предусловия". Это означает, что клиентский код должен иметь возможность узнать, а удовлетворяет ли он предусловия или нет.
Тут можно сказать, что поскольку в предусловии участвует аргумент, который клиент и передает, то это правило соблюдается. Но само предусловие меняется в зависимости от типа и не является явным.
В этом случае мы можем выделить предусловие в отдельный метод и сделать само предусловие полиморфным:
public abstract class Vehicle { public virtual bool IsWeightValid(int w) {return true;} public virtual void Weight(int w) { Contract.Requires(IsWeightValid(w)); } }
Теперь, мы говорим нашим клиентам: "прежде чем дергать метод Weight убедитесь самостоятельно, что аргумент валиден". Да, это может выглядеть перебором. И я бы прибегал к этому трюку в крайнем случае, чтобы не усложнять API. Но это довольно распространенный паттерн. Например, коллекции в BCL выставляют свойство IsReadOnly, которое (теоретически) должно быть проверено перед вызовом метода Add.
Кто-то считает поведение из BCL нарушением LSP, но я так не думаю: метод имеет право выражать свои предусловия в более абстрактном виде, через свои собственные свойства или методы. Contract.Requires(ImInValidState) ничем не хуже (теоретически), чем Contract.Requires(methodArgument != null)

вторник, 9 июля 2019 г.

Получение типа из подкласса. Полиморфизм C#

Доброго времени суток, столкнулся с такой проблемой. Есть абстрактный класс ViewModel, который содержит логику добавления данных в коллекцию для отображения в GUI.
public abstract class ViewModel {
public ObservableCollection Data { get; set; } protected abstract IModel createObject(SqlDataReader reader);
protected void selectData(string query) { // ... // Логика получения reader // ... IModel item = this.createObject(reader); this.Data.Add(item); } }
Также есть классы моделек, все они реализую интерфейс IModel, код приводить не буду, в нем нет необходимости. Класс реализующий ViewModel выглядит так:
public class UsersViewModel : ViewModel {
public UsersViewModel() { string query = "SELECT TOP 300 * FROM users ORDER BY reputation DESC"; this.selectData(query); }
protected override IModel createObject(SqlDataReader reader) { return new User(reader); } }
Проблема собственно в методе createObject. Во всех подклассах он делает одну единственную вещь: возвращает экземпляр класса модели. Мне бы хотелось от него избавится. И сделать так, что бы в абстрактном классе ViewModel метод selectData сам создавал тот объект модели, какой нужно, а какой именно, он бы узнавал из подкласса. Метод должен выглядеть примерно так:
protected void selectData(string query) { // Логика получения reader IModel item = new Тип_Модели_из_подкласса(reader); this.Data.Add(item); }
Кажется мне, что проблему можно решить с помощью обобщений, но не знаю как. Или создать в подклассе свойство, которое будет уточнять какую именно реализацию интерфейса IModel нужно использовать. Как мне это реализовать? Заранее спасибо.


Ответ

Ну например, один из методов такой:
public abstract class ViewModelImpl : ViewModel where T : IModel { protected void selectData(string query) { // Логика получения reader IModel item = (T)Activator.CreateInstance(typeof(T), reader); this.Data.Add(item); } }
Ну и наследуйте конкретные классы от ViewModelImpl с нужным типом T
(Если бы конструктор T был без параметров, можно было бы обойтись без рефлексии.)

Для лучшей читаемости можно вынести метод:
private T CreateModelItem(SqlDataReader reader) { return (T)Activator.CreateInstance(typeof(T), reader); }
protected void selectData(string query) { // Логика получения reader IModel item = CreateModelItem(reader); this.Data.Add(item); }
Впрочем, у вас этот вспомогательный метод уже есть, он называется createObject