Preskoči na sadržaj

Objektno orijentirano modeliranje i programiranje web aplikacija. Objektno-relacijsko preslikavanje

doc. dr. sc. Vedran Miletić, vmiletic@inf.uniri.hr, vedran.miletic.net

Fakultet informatike i digitalnih tehnologija Sveučilišta u Rijeci, akademska 2021./2022. godina


Objektno orijentirano modeliranje i programiranje

Za modeliranje koristit ćemo po potrebi Allen Holub's UML Quick Reference:

  • Use-Case (Story) Diagram
  • Static-Model (Class) Diagram
  • Interaction (Dynamic-Model) Diagrams

Objektno orijentirano programiranje (prema Wikipediji):

  • programska paradigma temeljena na objektima koji sadrže podatke (polja, atribute ili svojstva) i kod (procedure ili metode)
  • četiri temelja: enkapsulacija, apstrakcija, nasljeđivanje i polimorfizam

Enkapsulacija (1/2)

🙋 Pitanje: Je li ovo primjer enkapsulacije?

class Account < ApplicationRecord
  def initialize(iban, balance)
    @iban = iban
    @balance = balance
  end

  attr_accessor :iban, :balance
end

account = Account.find_by(iban: 'HR1234567890')
account.balance += 1000
account.save!

Enkapsulacija (2/2)

🙋 Pitanje: Je li ovo primjer enkapsulacije?

<?php

class Account extends Model {
  private string $iban; private int $balance;
  public function __construct(string $iban, int $balance) { /* ... */ };

  public function getBalance(): int
  {
    return $this->balance;
  }

  public function alterBalance(int $amount): void
  {
    $this->iban += $amount;
  }
}

Apstrakcija

<?php

interface EmailProvider
{
  public function send();
}

class SmtpEmailProvider implements EmailProvider
{
  public function send() {
    // ...
    $smtp = Mail::factory('smtp', ['host' => $host, 'port' => $port,
      'auth' => true, 'username' => $username, 'password' => $password]);
    $mail = $smtp->send($to, $headers, $email_body);
    // ...
  }
}

Ready to use bg 95% left:55%

Ponovno iskoristive komponente

Izvor: Reusable Components (MonkeyUser, 7th September 2021)


Nasljeđivanje

class PrintJob {
  constructor(printer, numberOfPages) {
    this.printer = printer;
    this.numberOfPages = numberOfPages;
  }
}

class PaidPrintJob extends PrintJob {
  constructor(printer, paymentAccount) {
    this.paymentAccount = paymentAccount;
    super(printer);
  }

  chargeAccount() {
    let pricePerPage = this.printer->getPricePerPage();
    this.paymentAccount->pay(this.numberOfPages * pricePerPage);
  }
}

Polimorfizam

from django.db import models

class Question(models.Model):
  question_text = models.CharField(max_length=200)
  pub_date = models.DateTimeField('date published')

class Article(models.Model):
  article_text = models.TextField()
  pub_date = models.DateTimeField('date published')

q = Question(question_text="What's new?", pub_date=timezone.now())
a = Article(article_text="Lorem ipsum dolor sit amet, consectetur...",
            pub_date=timezone.now())

objs = [q, a]
for obj in objs:
  print(obj.pub_date)

There is a time consuming difference between running a feature and having a runnable feature bg 95% left:55%

Rubni slučajevi

Izvor: Web App - Edge Cases (MonkeyUser, 15th October 2019)


SOLID (1/2)

Prema Wikipediji, SOLID je skup načela koji je 2000. godine predložio američki softverski inženjer Robert C. Martin (kolokvijalno Uncle Bob) u svome radu Design Principles and Design Patterns:

What goes wrong with software? The design of many software applications begins as a vital image in the minds of its designers. At this stage it is clean, elegant, and compelling. (...)

But then something begins to happen. The software starts to rot. At first it isn't so bad. An ugly wart here, a clumsy hack there, but the beauty of the design still shows through. Yet, over time as the rotting continues, the ugly festering sores and boils accumulate until they dominate the design of the application. The program becomes a festering mass of code that the developers find increasingly hard to maintain. (...)


SOLID (2/2)

Michael Feathers je uveo mnemoničku kraticu za pet načela dizajna čiji je cilj softver učiniti razumljivijim, fleksibilnijim i lakšim za održavanje:

  • (S) Načelo pojedinačne odgovornosti (engl. single-responsibility principle)
  • (O) Načelo otvoreno-zatvoreno (engl. open-closed principle)
  • (L) Liskovino načelo zamjene (engl. Liskov substitution principle)
  • (I) Načelo razdvajanja sučelja (engl. interface segregation principle)
  • (D) Načelo inverzije zavisnosti (engl. dependency inversion principle)

Načelo pojedinačne odgovornosti (1/4)

(S) Načelo pojedinačne odgovornosti (engl. single-responsibility principle): klasa bi trebala imati samo jednu odgovornost, odnosno samo promjene na jednom dijelu specifikacije softvera trebale bi utjecati na specifikaciju klase (prema Wikipediji).


Načelo pojedinačne odgovornosti (2/4)

🙋 Pitanje: Zadovoljava li ova klasa načelo pojedinačne odgovornosti?

<?php

class Article {
  private Author $author;
  private $title;

  public function getAuthorAsJson() {
    return json_encode(["name" => $this->author->getName(),
                        "surname" => $this->author->getName()]);
  }

  public function getTitle() {
    return $this->title;
  }
}

Načelo pojedinačne odgovornosti (3/4)

🙋 Pitanje: Zadovoljava li ova klasa načelo pojedinačne odgovornosti?

<?php

class Article {
  private Author $author;
  private $title;

  public function getAuthor() {
    return $this->author;
  }

  public function getTitle() {
    return $this->title;
  }
}

Kako ćemo dobiti podatke o autoru u formatu JSON?


Načelo pojedinačne odgovornosti (4/4)

Jedno moguće rješenje:

<?php

class Author {
  private $name;
  private $surname;

  public function asJson() {
    return json_encode(["name" => $this->name,
                        "surname" => $this->surname])
  }
}

Načelo otvoreno-zatvoreno (1/4)

(O) Načelo otvoreno-zatvoreno (engl. open-closed principle): dijelovi softvera trebali bi biti otvoreni za proširenje, ali zatvoreni za izmjene (prema Wikipediji).


Načelo otvoreno-zatvoreno (2/4)

🙋 Pitanje: Zadovoljava li ova klasa načelo otvoreno-zatvoreno?

<?php

class Auth {
  public function login($user) {
    if ($user instanceof Student) {
      $this->loginStudent($user);
    } else if ($user instanceof Professor) {
      $this->loginProfessor($user);
    }
  }

  // ...
}

Načelo otvoreno-zatvoreno (3/4)

🙋 Pitanje: Zadovoljava li ova klasa načelo otvoreno-zatvoreno?

<?php

class Auth {
  public function login($user) {
    $user->autheticate();
  }

  // ...
}

Načelo otvoreno-zatvoreno (4/4)

<?php

interface AuthInterface {
  public function authenticate();
}

class Student implements AuthInterface {
  public function authenticate() {
    // ...
  };
}

class Professor implements AuthInterface {
  public function authenticate() {
    // ...
  };
}

Liskovino načelo zamjene (1/3)

(L) Liskovino načelo zamjene (engl. Liskov substitution principle): objekti u programu trebali bi biti zamjenjivi primjercima svojih podtipova bez promjene ispravnosti tog programa (prema Wikipediji).


Liskovino načelo zamjene (2/3)

🙋 Pitanje: Zadovoljavaju li ove klase Liskovino načelo zamjene?

<?php

interface FileRepository {
  public function getFiles();
}

class UserUploads implements FileRepository {
  public function getFiles() {
    return File::select('name')->where('user_id', $this->user->id)->get();
  }
}

Liskovino načelo zamjene (3/3)

<?php

class PublicUploads implements FileRepository {
  public function getFiles() {
    $files = array();
    $handle = opendir('/var/www/html/uploads');

    if ($handle) {
      while (($entry = readdir($handle)) !== FALSE) {
        $files[] = $entry;
      }
    }

    closedir($handle);
  }
}

Načelo razdvajanja sučelja (1/4)

(I) Načelo razdvajanja sučelja (engl. interface segregation principle): mnoga sučelja specifična za pojedinog klijenta bolja su od jednog sučelja opće namjene (prema Wikipediji).


Načelo razdvajanja sučelja (2/4)

🙋 Pitanje: Zadovoljavaju li ove klase načelo razdvajanja sučelja?

<?php

interface User {
  public function authenticate();
  public function getUserEmail();
}

Načelo razdvajanja sučelja (3/4)

<?php

class Student implements User {
  public function authenticate() {
    // ...
  }

  public function getUserEmail() {
    // ...
  }
}

Načelo razdvajanja sučelja (4/4)

<?php

class Guest implements User {
  public function authenticate() {
    return null;
  };

  public function getUserEmail() {
    // ...
  };
}

Načelo inverzije zavisnosti (1/3)

(D) Načelo inverzije zavisnosti (engl. dependency inversion principle): ovisnost o apstrakcijama, [ne] o konkretizacijama (prema Wikipediji).


Načelo inverzije zavisnosti (2/3)

🙋 Pitanje: Zadovoljavaju li ove klase načelo inverzije zavisnosti?

<?php

class MySqlConnection {
  public function connect() { /* ... */  }
}

class PageLoader {
  private $dbConnection;
  public function __construct(MySqlConnection $dbConnection) {
      $this->dbConnection = $dbConnection;
  }

  // ...
}

Načelo inverzije zavisnosti (3/3)

<?php

interface DbConnectionInterface {
  public function connect();
}

class MySqlConnection implements DbConnectionInterface {
  public function connect() { /* ... */  }
}

class PageLoader {
  private $dbConnection;
  public function __construct(DbConnectionInterface $dbConnection) {
      $this->dbConnection = $dbConnection;
  }

  // ...
}

Primjena SOLID načela u suvremenim aplikacijama

Katerina Trajchevska iz Adeve u objavi na blogu pod naslovom SOLID Design Principles: The Guide to Becoming Better Developers piše:

Product owners don't always understand the implications of bad software design principles. It's on us, as engineers, to consider the best design practices when estimating and make sure we write code that's easy to maintain and extend. SOLID design principles can help us achieve that.

Snimka predavanja na temu: Becoming a better developer by using the SOLID design principles by Katerina Trajchevska (Laracon EU, 28th January 2019)


Relacijski model

Recimo da je neka teretana predstavljena relacijskim modelom (stil označavanja: primarni ključ, vanjski ključ):

  • Član (ID člana, Ime, Adresa, Grad, Poštanski broj, Županija, Telefonski broj, E-mail) (npr. Hrvoje, Dora)
  • Članarina (Kod članarine, Opis članarine) (npr. mjesečna, godišnja)
  • Uplata članarina (Broj uplate članarine, ID člana, Datum) (npr. 1 godišnja + 2 mjesečne na 14. listopada 2021.)
  • Stavka uplate članarine (Broj uplate članarine, Broj stavke uplate članarine, Kod članarine, Količina) (npr. godišnja članarina na specifičnoj uplati)

Za vizualizaciju možete iskoristiti npr. PonyORM Entity Relationship Diagram Creator koji može i generirati kod za PonyORM (u Pythonu, donekle sličan Djangu).


Pretvorba relacijskog modela u Django modele

from django.db import models

class Member(models.Model):
  name = models.CharField(max_length=50)
  address = models.CharField(max_length=50)
  city = models.CharField(max_length=30)
  postal_code = models.IntegerField()
  county = models.CharField(max_length=30)
  phone_number = models.CharField(max_length=20)

# class Membership

class MembershipPayment(models.Model):
  member = models.ForeignKey(Member, on_delete=models.CASCADE)
  date = models.DateField()

# class MembershipPaymentItem

Pretvorba Django modela u SQL (1/2)

Django će na temelju definiranih modela generirati SQL. Oblik (tzv. dijalekt) SQL-a ovisit će o korištenom sustavu za upravljanju bazom podataka, a podržani sustavi su:

  • MariaDB/MySQL (prvi primjer u nastavku)
  • PostgreSQL (ostali primjeri u nastavku)
  • SQLite
  • Oracle

Pretvorba Django modela u SQL (2/2)

CREATE TABLE members (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  address VARCHAR(50),
  city VARCHAR(30),
  postal_code INT,
  county VARCHAR(30),
  phone_number VARCHAR(20)
);

CREATE TABLE membership_payment (
  id INT PRIMARY KEY,
  member_id INT UNSIGNED NOT NULL,
  date DATE,
  CONSTRAINT `fk_membership_payment_member`
    FOREIGN KEY (member_id) REFERENCES member (id)
    ON DELETE CASCADE
    ON UPDATE RESTRICT
);

Upiti u Djangu i relacijskoj bazi (1/2)

Koristimo ranije prikazane klase i podatke spremamo korištenjem sustava za upravljanje bazom podataka PostgreSQL. Na temelju koda:

members = Member.objects.all()

Django generira upit:

SELECT * FROM members;

Na temelju koda:

ivan_horvat = Member.objects.get(name="Ivan Horvat")

Django generira upit:

SELECT * FROM members WHERE name = "Ivan Horvat";

Upiti u Djangu i relacijskoj bazi (2/2)

Na temelju koda:

ivans = Member.objects.filter(name__contains="Ivan")

Django generira upit:

SELECT * FROM members WHERE name LIKE "%Ivan%";

Na temelju koda:

payments_ivan_horvat = MembershipPayment.objects.get(member=ivan_horvat)

Django generira upit:

SELECT * FROM membership_payments WHERE member_id IN
    (SELECT id FROM members WHERE name = "Ivan Horvat");

Objektno-relacijsko preslikavanje

Prema Wikipediji:

  • programska tehnika za pretvorbu između nekompatibilnih sustava tipova korištenjem objektno orijentiranih programskih jezika
  • unos u tablici u relacijskoj bazi podataka postaje objekt u programu i obrnuto
  • npr. Hibernate (Java), PonyORM (Python), Sequelize (JavaScript) i Doctrine (PHP)
  • čitavi okviri uglavnom integriraju ORM, primjerice:
  • generira upite za dohvaćanje podataka i migracije (promjene sheme)

Migracije u Djangu

$ ./manage.py makemigrations
Migrations for 'members':
  members/migrations/0002_auto.py:
    - Add field e-mail on member
from django.db import migrations, models

class Migration(migrations.Migration):
  dependencies = [('migrations', '0001_initial')]
  operations = [
    migrations.AddField('Member', 'e-mail',
                        models.CharField(max_length=30)),
  ]

Na temelju ove migracije Django generira upit:

ALTER TABLE members ADD COLUMN e_mail VARCHAR(30);

Migracije u web aplikacijama otvorenog koda


Nerelacijske baze podataka

Prema Wikipediji, dio Yenove taksonomije NoSQL baza podataka:

Author: Vedran Miletić