Professional Documents
Culture Documents
Ilovepdf Merged
Ilovepdf Merged
Lisp
perl
C#
Python 3
ОО-декомпозиція
Одним з базових способiв керування складними системами є декомпозицiя, тобто iтерацiйне або рекурсивне
роздiлення системи на простiшi пiдсистеми, що пов’язанi мiж собою: “розділяй та володарюй”
Структурне програмування дотримується засад алгоритмiчної декомпозицiї: розбиття складної задачi (алгоритму) на
бiльш простi та дрiбнi алгоритми, органiзованi в загальну структуру за допомогою конструкцiй послiдовного, циклiчного
та умовного виконання
Поява та розвиток ОО-парадигми обумовленi обмеженнями iнших бiльш раннiх парадигм програмування. Процедурнi
мови програмування (FORTRAN, Pascal, C) вирішують проблему складності ПЗ поєднанням модульної, процедурної
та структурної парадигм, що дає багаторiвневу (iєрархiчну) декомпозицiю: розбиття програми на окремi модулi,
модулi — на процедури, процедури — на структурнi блоки.
Основною iдеєю ОО-пiдходу є об’єднання даних та дiй над цими даними у єдине цiле пiд назвою об’єкт
ОО-парадигма
Об’єктно-орiєнтоване програмування — це
парадигма програмування, що заснована на
представленнi програми у виглядi сукупностi
взаємодiючих об’єктiв («об’єктна програмна модель»),
кожен з яких є екземпляром певного класу, а класи
формують певну iєрархiю відношень та наслiдування
властивостей.
Абстрагування (метод вирiшення складних задач) — процес видiлення абстракцiй у предметнiй областi.
Абстракцiя — сукупнiсть суттєвих характеристик об’єкта, що вiдрiзняють його вiд усiх iнших видiв об’єктiв, таким
чином чiтко встановлюючи його концептуальнi межi.
ООП передбачає об’єднання усiх властивостей абстракцiї об’єкта в єдину одиницю “клас” — абстрактний тип даних,
що описує будову об’єкта та його поведiнку.
Рівень
абстрагування
повинен відповідати
вирішуваному
завданню
ОО принципи
З точки зору споживача функцiональностi, представленої об’єктом (програмного коду, що є “клiєнтом” цього об’єкта),
зовнiшня поведiнка об’єкта характеризується множиною послуг, якi вiн надає iншим об’єктом, та операцiй, якi вiн
виконує над iншими об’єктами. Ця множина називається контрактом або iнтерфейсом об’єкта.
Iнтерфейс — це набiр операцiй, що характеризує зовнiшню поведнiку об’єкта. Внутрiшня реалiзацiя визначає
механiзми для забезпечення цiєї поведiнки.
Iнкапсуляцiя — це механiзм вiдокремлення елементiв об’єкта, що визначають його будову та поведiнку. Мета
iнкапсуляцiї — iзолювати абстракцiю поведiнки (iнтерфейс) об’єктiв класу вiд її реалзiацiї.
Складовою цього механiзму є обмеження доступу до внутрiшньої будови (даних) об’єкта, приховування усiх
внутрiшнiх деталей, що не впливають на зовнiшню поведiнку.
Важливе завдання інкапсуляції - зберегти стан об'єкту (тобто множину значень усіх полів) несуперечливим при дії
на об'єкт через його інтерфейс. Рівень
абстрагування
повинен відповідати
вирішуваному
завданнюЯк досягти суперечливого стану:
змінили одне поле,
але не обчислили повторно
решту залежних полів
void set(double x, double y) int main()
#include <iostream>
{ {
#include <cmath>
this->x = x; Vector v1;
this->y = y; // std::cout << "x: " << v1.x
class Vector
sz = calcSize(x, y); << ", y:" << v1.y
{
} << std::endl;
public:
std::cout << "x: " << v1.get_x()
Vector(double x = 0, double y = 0)
private: << ", y:" << v1.get_y()
: x(x), y(y), sz(calcSize(x, y))
double x, y, sz; << std::endl;
{}
double calcSize(double x, double y) std::cout << "size: " << v1.size()
double getX() const
{ << std::endl;
{
return x; return sqrt(x * x + y * y);
} v1.set(3,4);
}
}; std::cout << "x: " << v1.get_x()
double getY() const
<< ", y:" << v1.get_y()
{
<< std::endl;
return y; ідея: не треба обчислювати повторно std::cout << "size: " << v1.size()
} довжину, якщо не змінювались координати. << std::endl;
double size() const Але тепер слід убезпечити координати та довжину }
{ від “розсинхронізації” у випадку зміни координат
return sz;
}
Рекомендація: не переобтяжуйте
Типи доступу:
+ public (відкритий): доступ з будь-якої частини програми
прості класи (структури) зайвими
# protected (захищений): доступ з методів цього класу та його наслідників “гетерами-сетерами”, якщо не
- private (закритий): доступ лише з методів цього ж класу треба забезпечувати
несуперечливість стану об'єкта
інкапсуляція ефективно обмежує коло пошуку помилок
void readRatings ()
void addVote ()
struct Rating void deleteRating () {
{ r.mark = r.sum + new_mark;
int id; r.mark = r.mark / (++votes);
int votes; void removeVote () }
double sum;
double mark;
} r;
void findRatings ()
{…
void analyzeRatings () if (r.id = id)
…
void sortRatings () else filterRatings();
void printRatings () }
void saveRatings ()
void filterRatings () і -коду
задача - лічильник оцінок: { ... о с пагет
р но г
if (r.votes = criteria) ду
у пр оце
мент
...
} ф раг
л ад
прик
Java та C# - “чисті” ОО-мови, без функцій поза межами класів
# pseudo-private field
self.__private = 0
def get_data(self):
print(f'{self.real}+{self.imag}j')
num1 = ComplexNumber(2, 3)
num1.get_data()
num2 = ComplexNumber(5)
num2.attr = 10 динамічне створення атрибутів (полів)
print((num2.real, num2.imag,
num2.attr))
# print(num1.attr)
# print(num1.__private)
print(num1._ComplexNumber__private)
2+3j
(5, 0, 10)
0
function Person(name) { JavaScript - прототипно-орієнтована мова
this.name = name;
}
Person.prototype.sayHi = function() {
alert(this.name);
};
ES6
class Person {
class Person{ name: string;
gender: string = 'Male';
constructor(name) { private id: number;
this.name = name;
} constructor (name: string, id: number, gender?: string) {
this.name = name;
sayHi() { this.id = id;
alert(this.name);
} if (gender) this.gender = gender;
} }
}
let bob = new Person("Bob");
bob.sayHi();
UML (англ. Unified Modeling Language) —
уніфікована мова моделювання, використовується
у парадигмі об'єктно-орієнтованого програмування.
Є невід'ємною частиною уніфікованого процесу
розробки програмного забезпечення. UML ...
використовує графічні позначення для створення
абстрактної моделі системи, яка називається UML-
моделлю. UML був створений для визначення,
візуалізації, проектування й документування, в
основному, програмних систем. UML не є мовою
програмування, але в засобах виконання UML-
моделей як інтерпретованого коду можлива
кодогенерація (wiki)
https://app.diagrams.net/
http://www.umlet.com/umletino/umletino.html
...вона ж Діаграма варіантів використання
Iєрархiя — багаторiвнева упорядкована система абстракцiй.
В ООП розрiзняють iєрархiчнi вiдношення типу:
https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository
javac HelloWorld.java
java -classpath . HelloWorld
managed-код
С++
компілятор C++
Один вихідний код → один скомпільований проміжний код (MSIL) → JIT компілятор (під час виконання)→
виконуваний код для конкретної платформи (Windows):
Java
class Person {
public Person (); // без параметрів (аргументів)
public Person (String name); // з параметром
public Person (Person orig); // copy-ctor
}
Сигнатура методу - назва методу класу та перелік типів його параметрів
Base
base
members ...
Derived
base
members ...
(new
members …)
class Person {
private String name;
public Person(String n) { public class Main {
name = n; public static void main(String[] args) {
} Person p = new Person ("Bob");
public String greeting() { System.out.println(p.greeting());
return "Hi, I'm " + name; Student s = new Student ("Bill", 2);
} System.out.println(s.greeting());
} }
}
class Student extends Person { Hi, I'm Bob
Hi, I'm Bill, 2 grade
public int grade;
public Student(String n, int g) {
super(n);
grade = g;
}
public String greeting() {
return super.greeting() + ", " + grade + " grade";
}
}
#include <string>
class Person
{
private:
#include ”person.h” std::string name;
public:
#include <sstream> Person(std::string n) : name(n) {}
std::string greeting()
class Student : public Person
{
{
return "Hi, I'm " + name;
private:
}
int grade;
};
public:
Student(std::string n, int g) : Person(n), grade(g) {}
std::string greeting()
{
std::stringstream greeting;
greeting << Person::greeting() << ", " << grade << " grade";
return greeting.str();
}
};
#include “student.h” Видимість членів класу
public protected private
#include <iostream>
члени самого класу або друзі доступ є доступ є доступ є
int main ()
{ члени похідного класу доступ є доступ є закрито
Person p("Bob");
std::cout << p.greeting() << std::endl; інше доступ є закрито закрито
Student *s = new Student("Bill", 2);
std::cout << s->greeting() << std::endl;
Зміна видимості членів базового класу
delete s;
Наслідування / Члени public protected private
}
public = public = protected = private
альтернатива switch-case
class Rectangle implements Shape { interface Shape {
private double height, width; public double area ();
public Rectangle(double w, double h) { }
height = h;
width = w;
}
public double area() {
return height * width;
}
}
2
6.28
class Shape
class Rectangle: public Shape {
{ public:
double height, width; virtual double area () const = 0;
public: };
Rectangle(double w, double h) : height(h), width(w) {}
virtual double area() const
{
return height * width;
}
};
int main()
{
Shape *p = new Rectangle(1, 2);
Viewer::displayArea(*p);
delete p;
p = new Ellipse(2, 4);
Viewer::displayArea(*p);
delete p;
}
2
6.28
Віртуальні методи слід явно
позначити:
derived
IBase IBase2 base2
implements extends
/* do printing */
}
}
abstract class Document { class Document
abstract void saveAs (string fileName); {
} public:
virtual void saveAs (std::string fileName) = 0;
Document d = new Document (); // not possible // хоча б один чистий віртуальний метод
};
RealDocument d;
d.saveAs(“file2.txt”);
base
base1 base2
derived
accelerate ()
{
Boat::speed++;
GasolinePoweredTransportation::speed++;
}
class Transportation
{
protected:
int speed;
};
згадується в лекції
принципи ООП
пакет
компіляція інтерпретація
абстракція інкапсуляція наслідування поліморфізм
збирач сміття
множинне ромбовидне
на рівні пакету відкритий захищений
“дружній” віртуальне
пам'ять ресурс закритий
агрегація
типізація базові поняття відношення
стекова (авто) динамічна
композиція
жорстка
посилання вказівник
інтерфейс
члени
реалізація
статичні
ініціалізація поле метод
нестатичні
деструктор неконстантні
копіювання
селектор
Stud: Ben
Ben
Namespace. Init list
#include <string>
namespace Example // namespaces can be nested приклад оголошення свого простору імен
{
class Sir
{ увага: порядок ініціалізації
private: визначається порядком оголошення полів у класі,
а не списком ініціалізації
std::string fullName, shortName;
public:
Sir(std::string n = "John Doe")
: shortName(n), fullName("Sir" + shortName) {}
std::string getName() { return fullName; }
};
}
#include <iostream>
int main ()
{
Example::Sir x("Ben");
помилка, значення shortName не ініціалізоване
std::cout << x.getName() << std::endl; std::bad_alloc
}
Init list. Посилання, що повертаються з методу
#include <iostream>
int main ()
{
#include <string> Example::Sir x("Ben");
namespace Example x.getName() = “Sir Ben2”;
{ std::cout << x.getName() << std::endl;
class Sir }
Sir Ben2
{
private:
std::string shortName, fullName;
public:
Sir(std::string n = "John Doe")
: shortName(n), fullName("Sir " + shortName) {}
. .* :: ?:
Не перевантажуються
sizeof typeid
Перевантаження операторів
std::ostream& operator<<(std::ostream& os, const Point & p)
{ Перевантажений оператор передачі в потік
os << "(" << p.x << "," << p.y << ")"; (напр. для спрощення виведення на екран стану
return os; об'єктів)
}
#include “Point.h”
default:
System.out.println("Midweek days are so-so.");
break;
Переліки (перелічення)
public enum Planet {
MERCURY (3.303e+23, 2.4397e6), public static void main(String[] args) {
VENUS (4.869e+24, 6.0518e6), double earthWeight = Double.parseDouble(args[0]);
EARTH (5.976e+24, 6.37814e6), double mass = earthWeight/EARTH.surfaceGravity();
MARS (6.421e+23, 3.3972e6), for (Planet p : Planet.values())
JUPITER (1.9e+27, 7.1492e7), System.out.printf("Your weight on %s is %f%n",
SATURN (5.688e+26, 6.0268e7), p, p.surfaceWeight(mass));
URANUS (8.686e+25, 2.5559e7), }
NEPTUNE (1.024e+26, 2.4746e7); }
Приклад розширених можливостей enum у Java
private final double mass; // in kilograms (множина об'єктів-констант)
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius; $ java Planet 175
} Your weight on MERCURY is 66.107583
private double mass() { return mass; } Your weight on VENUS is 158.374842
private double radius() { return radius; } Your weight on EARTH is 175.000000
// universal gravitational constant (m3 kg-1 s-2) Your weight on MARS is 66.279007
public static final double G = 6.67300E-11; Your weight on JUPITER is 442.847567
Your weight on SATURN is 186.552719
double surfaceGravity() { Your weight on URANUS is 158.397260
return G * mass / (radius * radius); Your weight on NEPTUNE is 199.207413
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
Переліки (перелічення)
void fun() {
// examples of bad use of plain enums: // examples of good use of enum classes(safe)
Color color = Color::red; Animal a = Animal::deer;
Card card = Card::green_card; Mammal m = Mammal::deer;
if __name__ == '__main__':
do_something_for_cmd_line()
class Cypher {
class ReverseCypher extends Cypher {
encrypt(text) {
encrypt(text) {
return text;
return text.split("").reverse().join("");
}
}
decrypt(text) {
decrypt(text) {
return text;
return this.encrypt(text);
}
}
}
}
class RotateCypher extends Cypher {
function test(algorithm, text) {
constructor(key) {
result = algorithm.encrypt(text);
super();
console.log(result);
this.key = key;
console.log(algorithm.decrypt(result));
}
}
encrypt(text) {
return text.substr(this.key)
test(new RotateCypher(3), "Hello world");
+ text.substr(0,this.key);
test(new ReverseCypher(), "Goodbye cruel world");
}
decrypt(text) {
return text.substr(text.length - this.key)
lo worldHel
+ text.substr(0,text.length - this.key);
Hello world
}
dlrow leurc eybdooG
}
Goodbye cruel world
switch(): ElectricDevice {
enum Color { if (this.powered) {
NO_COLOR = "-", switch(this.color) {
RED = "R", case Color.GREEN:
YELLOW = "Y", this.color = Color.YELLOW;
GREEN = "G" break;
} case Color.YELLOW:
this.color = Color.RED;
abstract class ElectricDevice { break;
protected powered: boolean; default:
constructor() { this.color = Color.GREEN;
this.powered = false; }
} }
switchOnOff(): void { else this.color = Color.NO_COLOR;
this.powered = !this.powered; return this;
} }
abstract switch(): ElectricDevice; }
}
let tl: TrafficLight;
class TrafficLight extends ElectricDevice { tl = new TrafficLight();
color: Color; console.log(tl.color);
constructor() { tl.switchOnOff();
super(); console.log(tl.color);
this.color = Color.NO_COLOR; [LOG]: -
tl.switch().switch();
} [LOG]: G
console.log(tl.color);
switchOnOff(): void { [LOG]: R
tl.switchOnOff();
super.switchOnOff(); [LOG]: -
console.log(tl.color);
this.switch();
}
“Кухар”. Визначити ієрархію
продуктів, приготувати з них
страву - салат, розрахувати його
калорійність. Провести
сортування складових салату за
різними параметрами.
Організувати пошук продуктів у
страві.
“Хрестики-нулики”.
Реалізувати
консольну гру Х-О з В даному випадку, ідея декомпозиції коду базується на використанні патерну
іншим гравцем- MVC (model-view-controller) або MVP (model-view-presenter):
людиною, чи з
комп'ютером.
(код прикладів додається)
Масиви, динамічні масиви
#include <iostream> public class Main {
#include <string> public static void main(String[] args) {
int main() int m[] = new int [3];
{ int []n = { 1, 2 };
int m[5] = { 1, 2, 3, 4, 5 }; for (int i = 0; i < 2; ++i)
std::string * array; System.out.println(n[i]);
std::string ** matrix;
for (int i = 0; i < 5; ++i) String [] array;
std::cout << m[i] << std::endl; array = new String [5];
String [] array2 = {"1", "2", "3"};
array = new std::string[5]; for (String i : array2)
for (auto i = 0; i < 5; ++i) System.out.println(i);
std::cout << array[i] << std::endl;
delete [] array; int matrix[][] = new int [5][5];
for (int i = 0; i < matrix.length; ++i)
matrix = new std::string* [2]; matrix[i][i] = i + 1;
for (int i = 0; i < 2; ++i)
matrix[i] = new std::string[i + 1]; int matrix2[][] = { {1,2}, {3,4}};
int [][] matrix3 = new int [2][];
for (int i = 0; i < 2; ++i) for (int i = 0; i < matrix3.length; ++i)
delete [] matrix[i]; matrix3[i] = new int [i + 1];
delete [] matrix;
} }
}
Варіації new throwing (1):
void* operator new (std::size_t size) throw (std::bad_alloc);
int main () {
MyClass * p1 = new MyClass;
MyClass * p2 = new (std::nothrow) MyClass;
new (p2) MyClass; // новий об’єкт за адресою старого
p2->~T(); // явно викликаємо деструктор
MyClass * p3 = (MyClass*) ::operator new (sizeof(MyClass)); // тут конструктор
не викликається. “Звичайний” new викликає operator new + конструктор
}
Перевантаження new
#include <iostream>
#include <iostream>
struct X {
#include <algorithm>
static void* operator new(std::size_t sz)
{
void* operator new[](std::size_t sz, char c)
std::cout << "custom new for size " << sz << '\n';
{
return ::operator new(sz);
void* p = operator new[](sz);
}
std::fill_n(reinterpret_cast<char*>(p), sz, c);
static void* operator new[](std::size_t sz)
return p;
{ дещо з того, що ще буде розглянуто:
} приведення типів та алгоритми
std::cout << "custom new[] for size " << sz << '\n';
return ::operator new(sz);
int main()
}
{
};
char* p = new('*') char[6];
int main() {
p[5] = '\0';
X* p1 = new X;
std::cout << p << '\n';
delete p1;
delete[] p;
X* p2 = new X[10];
}
delete[] p2;
}
Можливе “ручне” розміщення масиву у пам'яті
#include <new>
class A
{
public:
A(int x){}
~A(){}
};
const int n = 50;
A* placementMemory = static_cast<A*>(operator new[] (n * sizeof(A)));
for (int i = 0; i < n; i++)
{
new (placementMemory + i) A(rand());
}
for (int i = 0; i < n; i++)
{
placementMemory[i].~A();
}
operator delete[] (placementMemory);
Memory leaks - втрачена пам'ять
void f()
{
int* p = new int(7);
} // memory leak
void f()
{ Виключні ситуації будуть розглянуті в наступній лекції. Ідея в тому,
int* p = new int(7); що при виключних ситуаціях виконання блоку коду переривається
g(); // тут може бути throw та оператор delete може залишитись невиконаним
delete p;
} // memory leak якщо виключення на g()
Віртуальний деструктор На замітку: Правило ТРЬОХ
якщо у класі визначається хоча б один з цих методів:
- деструктор
- конструктор копіювання
- оператор присвоювання
class Derived то слід визначити усі три.
class Base { Важливо, коли клас динамчіно взаємодіє з ресурсами
{ public:
public: Derived(int size)
Base() {
{ data = new int [size];
for (auto i = 0; i < 10; ++i) }
data[i] = i; ~Derived()
} {
private: delete [] data;
int data [10]; }
} private:
увага: якщо деструктор не віртуальний, при роботі
int [] data;
через вказівник на базовий клас буде викликаний }
лише деструктор базового класу,
і пам'ять не буде вивільнено (в найкращому випаку)
int main()
Оголошуйте деструктор (навіть порожній) як віртуальний, {
якщо клас передбачає наслідування Base *p = new Derived(10);
delete p;
}
Віртуальний деструктор
class Derived: public Base
{
#include <iostream>
public:
Derived(int size)
{
class Base
data = new int[size];
{
std::cout << "Derived()\n";
public:
}
Base()
~Derived()
{
{
for (auto i = 0; i < 10; ++i)
delete[] data;
data[i] = i;
std::cout << "~Derived()\n";
std::cout << "Base()\n";
}
}
private:
virtual ~Base()
int * data;
{
};
std::cout << "~Base()\n";
}
int main()
private:
{ результат:
int data[10]; Base()
Base *p = new Derived(10);
}; Derived()
delete p; ~Derived()
} ~Base()
Код без RAII (Resource acquisition is initialization)
https://www.tomdalling.com/blog/software-design/resource-acquisition-is-initialisation-raii-explained/
File f;
f.open("boo.txt");
//UNSAFE - an exception here means the file is never closed
loadFromFile(f);
f.close();
std::string readLine() {
return _file.readLine();
}
private:
File _file;
};
“Розумні вказівники” (smart pointers). auto_ptr
Найпростіший розумний вказівник,
обгортка для звичайного вказівника в автоматичній пам'яті
int main () {
std::auto_ptr<int> p1 (new int);
// або так:
*p1.get()=10;
std::auto_ptr<int> x_ptr(new int(42));
std::auto_ptr<int> p2 (p1);
std::auto_ptr<int> y_ptr;
std::cout << "p2 points to " << *p2 << '\n';
y_ptr = x_ptr; // !!!
// (p1 is now null-pointer auto_ptr)
// segmentation fault
return 0;
std::cout << *x_ptr << std::endl;
}
Розумні вказівники є шаблонами (templates).
Шаблони є реалізацією парадигми узагальненого програмування,
коли алгоритми описуються незалежними від типів даних, що вони
обробляють. Шаблони та їх бібліотеки будуть розглянуті пізніше.
Синтактично конкретний тип даних, для яких використовується шаблон,
вказується у <>
“Розумні вказівники” (smart pointers). unique_ptr
Додатковий захист від випадкового присвоювання
std::unique_ptr<int> x_ptr(new int(42)); Див. також фнкцію make_unique
std::unique_ptr<int> y_ptr;
#include <iostream>
#include <memory> // for std::shared_ptr
#include <string>
int main()
{
int i, j, *p;
// Correct usage: the variable i is an lvalue.
i = 7;
// Incorrect usage: The left operand must be an lvalue (C2106).
7 = i; // C2106
j * 4 = 7; // C2106
// Correct usage: the dereferenced pointer is an lvalue.
*p = i;
const int ci = 7;
// Incorrect usage: the variable is a non-modifiable lvalue (C3892).
ci = 9; // C3892
// Correct usage: the conditional operator returns an lvalue.
((i < 3) ? i : j) = 7;
}
R-value reference (C++11)
#include <iostream>
#include <string>
int main() {
std::string s1 = "Test";
// std::string&& r1 = s1; // error: can't bind to lvalue
class IntBox
// додамо по “правилу трьох”:
{
IntBox(const IntBox& other)
public:
{
IntBox(int size)
m_data = new int[other.m_size];
{
std::copy(other.m_data, other.m_data + other.m_size, m_data);
m_data = new int[size];
m_size = other.m_size;
m_size = size;
}
}
IntBox& operator=(const IntBox& other)
~IntBox()
{
{
if(this == &other) return *this;
delete[] m_data;
delete[] m_data;
}
m_data = new int[other.m_size];
private:
std::copy(other.m_data, other.m_data + other.m_size, m_data);
m_size = other.m_size;
int* m_data;
return *this;
size_t m_size;
}
};
Приклад, доповнення На замітку: Правило П'ЯТЬОХ
якщо у класі визначається хоча б один з цих методів:
- деструктор
- конструктор копіювання
class IntBox IntBox(IntBox&& other)
- оператор присвоювання
{ { - конструктор переміщення
public: m_data = other.m_data; - оператор переміщення
IntBox(int size) m_size = other.m_size;
то слід визначити усі п'ять.
{ other.m_data = nullptr; Важливо, коли клас динамчіно взаємодіє з ресурсами
m_data = new int[size]; other.m_size = 0; Правило 5ти доповнює правило 3х для кращої
m_size = size; } оптимізації керування ресурсами
} IntBox& operator=(IntBox&& other)
Правило ШЕСТИ: згадується конструктор
~IntBox() { за замовчанням (без параметрів)
{ if (this == &other) return *this;
delete[] m_data; delete[] m_data; На замітку: Правило НУЛЯ
} m_data = other.m_data; якщо у клас не потребує керування ресурсами, бо це
private: m_size = other.m_size; вже реалізовано іншими класами (напр. об'єкти яких
входять до даного класу як поля), то не слід
other.m_data = nullptr; визначати жодного елемента з “правила 6ти”
int* m_data; other.m_size = 0;
size_t m_size; return *this;
}; }
Примітка: RVO (Return Value Optimization) має вужче застосування: при поверненні значення з методу.
Оптимізація через семантику переміщення може застосовуватись і при передачі тимчасових об’єктів-аргументів
Ще приклад... Використання swap
t2 = cp.copy(t1)
t1.driver.name = 'Ben'
print(t2.driver.name)
t2 = cp.deepcopy(t1)
t1.co_driver.name = 'Bart'
print(t2.co_driver.name)
Виключення C++
int main () {
try
{
throw myex;
}
catch (exception& e)
{
cout << e.what() << '\n';
}
return 0;
Виключення Java
try {
// Protected code
} catch (ExceptionType1 e1) {
// Catch block
} catch (ExceptionType2 e2) {
// Catch block
} catch (ExceptionType3 | ExceptionType4 e3) {
// Catch block
} finally {
// The finally block always executes.
}
-------------------------------
import java.io.*;
public class className {
тип виключення - частина оголошення методу
struct A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor
operator bool() const { return true; }
};
struct B
{
explicit B(int) { }
explicit B(int, int) { }
explicit operator bool() const { return true; }
};
explicit (C++)
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
if (a1) ; // OK: A::operator bool()
bool na1 = a1; // OK: copy-initialization selects A::operator bool()
bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization
class Calculus
{
mutable int cachedValue;
mutable bool isChanged;
...
public:
int calculate() const
{
if(!isChanged) return cachedValue;
struct A
// Java:
{
// mark method as a superclass method
virtual void foo();
// that has been overridden:
void bar();
};
@Override
int overriddenMethod() { }
struct B : A
{
void foo() const override;
// Error: B::foo does not override A::foo
// (signature mismatch)
застосовний до:
- конструктор за замовчанням (без агрументів),
- конструктор копіювання,
- конструктор переміщення
- оператор присвоювання,
- оператор переміщення
- деструктор
delete (C++)
заборона використання методу, дозволяє не ховати його у private з ризиком
все ж викликати цей метод з інших методів класу
class Foo
{
public:
Foo() = default;
Foo(const Foo&) = delete;
void bar(int) = delete;
void bar(double) {}
};
class Foo
{
public:
void *operator new(std::size_t) = delete;
void *operator new[](std::size_t) = delete;
};
Вкладені (inner, nested) класи
про доступ:
struct enclose
{
class Enclosing {
struct inner private:
{ int x;
static int x;
class Nested {
void f(int i);
int y;
}; void NestedFun(Enclosing *e) {
}; cout << e->x; // ОК, вкладений клас - звичайний член зовнішнього класу
int enclose::inner::x = 1; // definition }
};
void enclose::inner::f(int i) {} // definition public:
void EnclosingFun(Nested *n) {
cout<< n->y; // Не ОК: y для зовнішнього класу закритий
}
};
Вкладені (inner, nested) класи
class OuterClass { - локальні класи:
Екземпляр статичного вкладеного класу може бути створений без створення екземпляру зовнішнього класу.
Статичний вкладений клас має доступ лише до статичних членів зовнішнього класу.
Нестатичний вкладений клас (внутрішній клас) має доступ і до нестатичних членів зовнішнього класу.
Анонімний клас Java
void methodWithAnonymousClass(final int interval) {
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("action!");
}
};
Магічна кнопка (Magic pushbutton): Кодування логіки реалізації класу безпосередньо в коді
елементів інтерфейсу, без використання абстракції.
Стан гонитви (Race hazard): Результат програми залежить від послідовності неконтрольованих
подій.
Зайнятий очікуванням (Busy waiting): Використання CPU під час очкування якоїсь дії,
зазвичай для тривалих циклів перевірки, замість використання повідомлень про події
Жорсткий код (Hard code): Вкладення припущень про середовище системи у її реалізації.
Повторення коду (Repeating yourself): Написання коду, який повторюється знову і знову, замість організації підпрограм.
Програмування вставкою (Copy and paste programming): Копіювання (і зміна) існуючого коду, замість того, щоб створювати нове
рішення.
Код Спагеті (Spaghetti code): Програми, структури яких ледь зрозумілі через неправильне застосування структур коду.
Код Лазаньї (Lasagna code): Програма, структура якої містить забагато рівнів.
Передчасна оптимізація (Premature optimization): Завчасне програмування для підвищення ефективності, жертвуючи хорошим
дизайном, гнучкістю, а інколи навіть реальною ефективністю.
Циклічні залежності (Circular dependency): Представлення непотрібних прямих чи непрямих взаємних залежностей між об'єктами та
модулями програми.
Божественний об'єкт (God object): Концентрування функціоналу в одній частині проекту (класу, методі).
Полтергейсти (Poltergeists): Недовговічні об'єкти, єдиною метою яких є ініціалізація інших об'єктів (назва типу "manager_", "controller_",
"start_process", ...).
Послідовне з'єднання (Sequential coupling): Клас вимагає, щоб його методи викликалися у певному порядку
GoF патерни
Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. (“Gang of Four”).
Приемы объектно-ориентированного проектирования. Паттерны
проектирования
Приклади: одинак (singleton)
Задача: забезпечити існування лише одного екземпляру об'єкта
Класичний
int main() {
int a=1, b=4; Макроси - не найкраща ідея:
double c=2.3, d=3.4; - проблеми з відлагодженням
- розширення макро-визначень іноді дає дивні результати:
SWAP(a, b, int); SWAP(a++,b++,int) --> ?
printf("%i %i\n", a, b); - немає перевірки типів (можна випадково передати хибні
SWAP(c, d, double); параметри і код не міститиме синтаксичних помилок)
printf("%f %f\n", c, d);
return 0;
}
Пригадуємо динамічні структури даних у C...
Спроба універсалізації
class Box {
private Object object;
...
public class Main {
public static void main(String[] args) {
Box b = new Box();
// b.set(10);
b.set(new Integer(10).toString());
String s = (String)b.get(); // Runtime-помилка, якщо b.set(10)
s += "test";
System.out.println(s);
}
}
Java Generics (параметризовані типи)
class Box<T> {
// T - параметр типу
private T t;
private K key;
private V value;
OrderedPair<String, Box<Integer>> p =
new OrderedPair<>("primes", new Box<Integer>(3)); // якщо додати конструктор
Параметризовані методи
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
…
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
або:
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
// ...
}
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
https://hajsoftutorial.com/java-bridge-method/
С++ Templates (шаблони)
template <typename T>
class Unsigned
{
public:
Unsigned(T number = 0)
: number(number < 0 ? -number : number)
{}
operator T() const { return number; }
Unsigned & operator *= (Unsigned multiplier)
{
number *= multiplier.number;
return *this;
}
private:
T number;
};
С++ Templates (шаблони)
○ Використайте діаграму прецедентів (обов'язкове завдання ЛР1), опишіть “чернетку технічного завдання” своїми
словами. В ТЗ зазвичай окремо описують функціональні вимоги - що і як має робити програма. Візьміть за основу
коротке формулювання варіанту завдання і розширте його.
● Грубо кажучи, діаграма прецедентів описує те, що можуть користувачі (інші програмні
компоненти поки не чіпаємо) робити з допомогою конктретного ПЗ
○ В усіх варіаінтах є стандартні дії: завантажити налаштування, зберегти налаштування чи поточний стан у файл. Їх
треба передбачити, але не треба реалізовувати в ЛР1, оскільки будова класів ще зазнає змін.
○ Інші типові дії (прецеденти): запустити процес моделювання або гри або обробки зображення, отримати статистику,
додати нову подію в календар, знайти нотатку по ключовим словам, зробити хід у грі тощо.
○ В результаті, маємо набір дій, який формує набір методів класу-фасаду, через який відбувається взаємодія з
логікою ПЗ. Тобто: “прецедент Запуск симуляції” --> Sim.Run(), “прецедент Налаштування” -->
Sim.SetParams(params) тощо.
○ Фасад надалі оперуватиме іншими об'єктами, які будуть формувати логіку. Через фасад відбуватиметься взаємодія
з логікою чи з консолі, чи через графічний інтерфейс, чи через HTTP/REST/SOAP/будь-який-інший-протокол
Чи обов'язково реалізовувати MVC / MVVM / MVP і т.п.?
● Головна ідея: відокремити логіку від конкретного інтерфейсу користувача.
○ У вас це консоль ТА віконний інтерфейс, керований подіями. Принципи роботи з консоллю (зчитати дані з
блокуванням виконання) та з віконними подіями (обробити подію, що настала та пройшла крізь цикл обробки подій)
- відрізняються. Тому змішування консольного вводу-виводу в коді методів логіки є ПОГАНОЮ ПРАКТИКОЮ, що
призведе до багатьох змін в коді при написанні курсової. Слід розробити такий API, щоб зміна та отримання стану
системи не залежало від стилю роботи інтерфейсу користувача.
○ Найпростіше рішення - фасад. У вас буде незалежна від UI Модель (Фасад + інші класи логіки) та код UI. У випадку
консольного додатку можна не реалізовувати діалог з користувачем - просто набір викликів методів Фасаду з
конкретними параметрами (тестовий сценарій) та виведення на екран стану об'єктів. Якщо хочеться діалог - тоді
цикл зчитування вводу користувача та виконання відповідних дій через Фасад та інші об'єкти. В рамках курсової
(GUI) у Вас будуть обробники подій (натискання кнопок мишею тощо), в яких ви знову таки викликаєте методи того
самого Фасаду. При потребі “промалювати картинку” - дані вилучаються з Фасаду, як і для діагностичних
повідомлень в рамках ЛР.
○ В ідеалі, слід розділити також шар роботи з UI та правила реакції на дії користувача (контролер, презентер). Тоді
маємо 3 складові: M-V-P, M-V-C, M-V-VM тощо. Не наполоягаю.
○ Наполягаю: не використовуйте сторонніх бібліотек для GUI поки не готова “абстрактна” логіка програми. Досить
лише консолі. Навіть якщо у Вас “псевдо-real-time” задача, а не покрокова.
○ Див. також Unit Testing, Test-driven-development. В ЛР програма - більше набір тестів. В КР - вже має бути
інтерактивна програма.
Ще раз про SOLID SOLID Principles:
Single responsibility
Open–closed
Liskov substitution
● Принцип інверсії Interface segregation
залежностей є Dependency inversion
непоганим
прикладом для
використання.
● Реалізовуйте
залежності через
інтерфейси
Алгоритми
sort --> NlogN
introsort =
quicksort + heapsort + insertion sort
приклади
int main () {
std::vector<int> foo = {25,15,5,-5,-15};
std::vector<int> bar (foo.size());
return 0;
}
Java Collections Framework
- Interfaces,
- Implementations,
- Algorithms
Object[] a = c.toArray();
import java.util.*;
for-each:
for (Person p : roster) {
if (p.getGender() == Person.Sex.MALE) {
System.out.println(p.getName());
}
}
pipeline: стиль оформлення коду,
roster не патерн pipeline
.stream()
.filter(e -> e.getGender() == Person.Sex.MALE)
.forEach(e -> System.out.println(e.getName()));
iterator:
for (Iterator<?> it = c.iterator(); it.hasNext(); ) {
if (it.next().getGender() == Person.Sex.MALE) {
System.out.println(it.next().getName());
}
}
pipeline як патерн
прохід колекцією, типові приклади
myShapesCollection.stream() видалення:
.filter(e -> e.getColor() == Color.RED) for (Iterator<?> it = c.iterator(); it.hasNext(); )
.forEach(e -> System.out.println(e.getName())); if (!cond(it.next()))
it.remove();
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
прохід колекцією, типові приклади
import java.util.*;
import java.util.stream.*;
sort — sorts a List using a merge sort algorithm, which provides a fast, stable sort. (A stable
sort is one that does not reorder equal elements.)
shuffle — randomly permutes the elements in a List.
reverse — reverses the order of the elements in a List.
rotate — rotates all the elements in a List by a specified distance.
swap — swaps the elements at specified positions in a List.
replaceAll — replaces all occurrences of one specified value with another.
fill — overwrites every element in a List with the specified value.
copy — copies the source List into the destination List.
binarySearch — searches for an element in an ordered List using the binary search algorithm.
indexOfSubList — returns the index of the first sublist of one List that is equal to another.
lastIndexOfSubList — returns the index of the last sublist of one List that is equal to another.
Словники Multi-map??
Map<String, List<String>> m = new
import java.util.*; HashMap<String, List<String>>();
class Program
{
static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
Non-generic collections (старий підхід)
static void ProcessItems<T>(IList<T> coll)
{
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);
M = E − N + 2P
де
M - цикломатична складність,
E - кількість ребер в графі,
N - кількість вершин в графі,
P - кількість компонент зв'язності
M = 10 − 9 + 2*1 = 3
Пов’язаність (згуртованість, зчеплення, cohesion): Зв’язність (coupling): міра в якій модуль програми
якісна міра того, наскільки пов’язаним є код в одному залежить від кожного іншого модуля
модулі програми (від гіршої до кращої):
якісно (від сильної до слабкої):
випадкова
логічна (спільне призначення) за вмістом
темпоральна (часова, спільний час виклику) процедурна
https://ru.wikipedia.org
SRP.OCP.LSP.ISP.DIP
Inversion of control
Dependency injection
- Library vs. Framework, в чому різниця?
Dependency injection
D F
D F функція як об'єкт
D делегування
D
Функція як об'єкт
Основна ідея:
int main()
{
PaintablesFactory *f = new PaintedFactory(new ConsolePainter());
... std::vector<Point> v = { Point(0,0), Point(1,1), Point(1,0) };
Paintable *p = f->create(ShapeName::Triangle, v);
class ShapeGroup : public Paintable ...
{
std::vector<Paintable*> groupedShapes; ShapeGroup grp;
public: grp.add(p);
void add(Paintable* p)
{ ShapeGroup grp2;
groupedShapes.push_back(p); grp2.add(p);
} grp2.add(&grp);
void paint() grp2.paint();
{
for (int i = 0; i < groupedShapes.size(); ++i) grp.add(&grp);
groupedShapes[i]->paint(); // infinite loop: grp.paint();
} // we need extra check on adding itself
};
return 0;
... }
Продовження GoF патернів
Будівельник / Builder
*https://towardsdatascience.com/10-common-software-architectural-patterns-in-a-nutshell-a0b47a1e9013
Розподілені обчислення
Триланкова архітектура /
3 (multi) - tiered:
- Клієнт
- Сервер бізнес-логіки
- БД
“пубілкація-підписка”
*https://dzone.com/articles/software-architecture-the-5-patterns-you-need-to-k
Сервісно-орієнтована архітектура
Мікросервіси / Microservices
DB
DB
Сервісна шина /
Enterprice Service CRUD DB
Bus
DB
*https://dzone.com/articles/software-architecture-the-5-patterns-you-need-to-k
Модельно-орієнтована архітектура