Přeskočit na obsah

Objektově orientované programování v jazyce Fortran

Objektově orientované programování (OOP) je tzv. programátorské paradigna. Stejně jako strukturovaní programování. Strukturované programování a OOP k sobě mají v zásadě celkem blízko. Základní myšlenka je stejná rozdělit kod do malých struktur pro zvýšení přehlednosti, jednoduchosti, znovupoužitelnosti, … Ovšem OOP se dívá na svět jinak. OOP rozeznává objekty, které mají nějaké vlastnosti (proměnné, parametry, …) a metody (funkce, subroutiny). Každý objekt spadá do nějaké třídy, např. všechny kočky spadají do třídy “kočka”. To znamená, že všechno co je kočka můžeme popsat stejnými vlastnostmi (hmotnost, barva, počet nohou = 4) a metodami (zamnoukej(), pred(), …). V numerických aplikacích by třídou objektů mohla být např. mřížka na které počítáme, molekula, atom, …

Další pilíř na kterém OOP stojí je zapouzdření. To se provádí pomocí tzv. privátních vlastností a metod, které již známe. V OOP jde o to, že objekty si zkrývají svůj vlastní stav (tj. proměnné). Navenek máme k těmto datům přístup jen skrze procedury, které objekt implementuje.

Objekty můžeme skládat dohromady (kompozice). Např. Molekulu si můžeme představit jako kompizice atomů a vazeb mezi nimi. Tj. máme objekt molekula, který se skládá z objektů Atom a z objektů Vazba.

Objekty mohou využívat služeb (metod (procedur)) jiných objektů tzv. delegace. Představme si, že máme objekt Zobrazovač, který se nám starý o zobrazování všeho na monitor. Žádný jiný objekt to neumí a pokud nějaký objekt pořebuje něco zobrazit musí použít objekt Zobrazovač a některou z jeho metod pro zobrazování. Další příkladem by mohl být objekt pro čtení … ???Čtenář???. Tento postup je zcela běžný a je v soulasu s principem, že každá část programu se stará jen o jednu věc.

Objekty od sebou mohou dědit vlastnosti a metody. Např. mějme třídu Člověk a ta má nějaké vlastnosti (věk, výška, hmotnost, pohlaví, …) a metody (jez, spi, …) a mějme třídu Programátor. Programátor je určitě člověk takže může zdědit všechny vlastnosti a metody od člověka. Navíc mu můžeme přidat vlastnosti a metoda navíc (programuj, …). To nám umožňuje nepsat kód pro Člověka a Programátora dvakrát.

Posledním důležitým pilířem OOP je polymorfismus. Tj. můžeme k objektům různých tříd přistupovat stejně. Např. je mi jedno jestli objekt je Člověk nebo Programátor. Oba dva můžu uspat pomocí procedury spi.

Ohladně OOP se toho dá napsat mnoho a mnoho a to co jsem napsal je jen základní představa. Proto je tato část spíše pro ty, kteří již nějaké zkušenosti s OOP mají. Pokud to člověk myslí s OOP vážně měl by si přečíst něco UML class diagramech popř. ještě UML sequenc diagramech. OOP není lékem na všechny problémy spojené s návrhem a programování velkých programů. Pokud navrhneme strukturu tříd špatně můžeme získat kód podobající se více špagetovému než kdybychom jej programovali strukturovaně a žádnou z výhod OOP bychom nezískali. Po čase proto vznikly tzv. Návrhové vzory. To jsou jednoduché vzory jak spolu mohou objekty spolupracovat a komunikovat aby z toho nebyl chaos. Každý kdo dělá OOP by na ně měl při navrhu programu myslet.

Skvělý zdroj: Object-Oriented Programming in Fortran 2003 Part 1.

Třídy

Třída je základní jednotkou OOP. V příkladu máme třídu obj

    module mod_obj_1
      implicit none
      
      private ! vse bude soukrome
      
      public :: obj ! konstruktorova fce i type budou verejne - jsou pretizene
      
      type obj
          integer :: a                             ! vlastnosti obj
          real, allocatable :: b(:)   ! vlastnosti obj
          
        contains ! metody objektu
          procedure :: change_obj
          procedure :: del_obj
      end type obj
      
      interface obj ! jmeno konstuktorove funkce
          module procedure init_obj
      end interface
      
      contains
      !--------------------------------------------
      type(obj) function init_obj(r,i) ! konstruktorova funkce
          real,intent(in) :: r(:)
          integer,intent(in) :: i
          integer :: n
          
          n = size(r)
          
          allocate (init_obj%b(n))
          init_obj%a = i
          init_obj%b = r
      end function init_obj
      !--------------------------------------------
      subroutine change_obj(this, r, i)
          class(obj), intent(inout) :: this ! this je odkaz objektu, pri volani funkce se nezadava
          integer, intent(in) :: i
          real, intent(in) :: r(:)
          
          deallocate(this%b)
          allocate(this%b(size(r)))
          
          this%a = i
          this%b = r
      end subroutine change_obj
      !--------------------------------------------
      subroutine del_obj(this) ! this je odkaz objektu, pri volani funkce se nezadava
          class(obj) :: this
  
          deallocate(this%b)
      end subroutine del_obj
  end module mod_obj_1
  !================================================
  program prog_obj_1
  use mod_obj_1
  implicit none
  
      type(obj) :: muj_obj
      
      muj_obj = obj([1.,2.,3.],4) ! vytvorime obj
      
      write(*,*) muj_obj%a
      write(*,*) muj_obj%b
      
      call muj_obj%change_obj([0.,1.], 2) ! zmenime obj
      
      write(*,*) muj_obj%a
      write(*,*) muj_obj%b
      
      call muj_obj%del_obj ! smazeme resp. dealokujeme obj
  
  end program
  

operace s objekty

U objektů se nám vyplatí přetížit některé základní operátory. To nám umožňuje sčítat, očítat, porovnávat, … uživatelské typy.

    module mod_obj_2
  implicit none
  
    private
  
    public :: obj
  
    type obj
      integer :: a
      real,  allocatable :: b(:)
  
      contains
        procedure :: change_obj
        procedure :: del_obj
        procedure,private :: plus_obj
        procedure,private :: minus_obj
        procedure,private :: rovnaSe_obj
        generic, public :: operator(+) => plus_obj ! misto plus_obj pouzijeme znamenko +
        generic, public :: operator(-) => minus_obj ! misto minus_obj pouzijeme znamenko -
        generic, public :: assignment(=) => rovnaSe_obj ! misto rovnaSe_obj pouzijeme znamenko =
    end type obj
  
    interface obj
      module procedure init_obj
    end interface
  
  contains
  !--------------------------------------------
  type(obj) function init_obj(r,i)
    real,intent(in) :: r(:)
    integer,intent(in) :: i
  
    allocate (init_obj%b,mold=r)
    init_obj%a = i
    init_obj%b = r
  end function init_obj
  !--------------------------------------------
  subroutine change_obj(this, r, i)
    class(obj), intent(inout) :: this
    integer, intent(in) :: i
    real, intent(in) :: r(:)
          
    deallocate(this%b)
    !allocate(this%b(size(r)))
    allocate(this%b, mold=r)
          
    this%a = i
    this%b = r
  end subroutine change_obj
  !--------------------------------------------
  subroutine del_obj(this)
    class(obj), intent(inout) :: this
  
    deallocate(this%b)
  end subroutine del_obj
  !--------------------------------------------
  type(obj) function plus_obj(obj1,obj2)
    class(obj), intent(in) :: obj1, obj2
  
    allocate(plus_obj%b, mold=obj1%b)
    
    plus_obj%a=obj1%a+obj2%a
    plus_obj%b=obj1%b+obj2%b
  end function plus_obj
  !--------------------------------------------
  type(obj) function minus_obj(obj1,obj2)
    class(obj), intent(in) :: obj1, obj2
  
    allocate(minus_obj%b, mold=obj1%b)
  
    minus_obj%a=obj1%a-obj2%a
    minus_obj%b=obj1%b-obj2%b
  end function minus_obj
  !--------------------------------------------
  subroutine rovnaSe_obj(new_obj, old_obj)
    class(obj), intent(in) :: old_obj
    class(obj), intent(out) :: new_obj
  
    new_obj%a=old_obj%a
    new_obj%b=old_obj%b
  end subroutine rovnaSe_obj
  end module mod_obj_2
  !================================================
  program prog_obj_2
  use mod_obj_2
  implicit none
  
    type(obj) :: muj_obj1, muj_obj2
    type(obj) :: muj_obj3
      
    muj_obj1 = obj([1.,2.,3.],4)
    muj_obj2 = obj([5.,6.,7.],8)
      
    muj_obj3 = muj_obj1 + muj_obj2
    write(*,*) muj_obj3%a
    write(*,*) muj_obj3%b
    write(*,*) 
    
    muj_obj3 = muj_obj1 - muj_obj2
    write(*,*) muj_obj3%a
    write(*,*) muj_obj3%b
    write(*,*) 
    
    muj_obj1 = muj_obj2
    write(*,*) muj_obj1%a
    write(*,*) muj_obj2%a
    write(*,*) muj_obj1%b
    write(*,*) muj_obj2%b
    write(*,*) 
  
  end program prog_obj_2
  

Dědičnost

Dedicnost se provadi pomoci extend. Navíc můžeme použít pro inicializaci zděděných hodnot konstruktor předka.

    module zvire_class
  implicit none
  
      private
      integer, parameter :: char_len = 20
      
      public :: zvire
  
      type zvire
          private
          character(len=char_len), public :: druh
          contains
              procedure, public, nopass :: mluv !specifikace nopass rika ze objekt neni argumentem funkce mluv
              procedure, public :: write_muj_druh
      end type zvire
  
      interface zvire
          module procedure init_zvire
      end interface
  
  contains
  !--------------------------------------------
  type(zvire) function init_zvire(jake_zvire)
      character(len=char_len), intent(in) :: jake_zvire
  
      init_zvire%druh = jake_zvire
      
  end function init_zvire
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'Nevim co jsem, nevim co mam delat.'
      
  end subroutine mluv
  !--------------------------------------------
  subroutine write_muj_druh(this)
      class(zvire), intent(in) :: this
  
      write(*,*) trim(this%druh)
      
  end subroutine write_muj_druh
  end module zvire_class
  !================================================
  module kocka_class
  use zvire_class
  implicit none
  
      private
  
      public :: kocka
  
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: kocka ! kocka dedi od zvirete
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: name
  
          contains
              procedure, nopass :: mluv 
              procedure :: print=>write_me ! subroutinu write_me budeme volat pomoci print
      end type kocka
  
      interface kocka
          module procedure init_kocka
      end interface
  
  contains
  !--------------------------------------------
  type(kocka) function init_kocka(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_kocka%zvire = zvire('Kocka') !na zdedene vlastnosti se muzeme odkazovat pod nazvem predka
                                        ! a pouzit jeho konstruktor
      init_kocka%name = moje_jmeno
      init_kocka%rasa = rasa
  end function init_kocka
  !--------------------------------------------
  subroutine write_me(this)
      class(kocka), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%name)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'mnau'
  end subroutine mluv
  end module kocka_class
  !================================================
  module pes_class
  use zvire_class
  implicit none
  
      private
  
      public :: pes
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: pes
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: name
  
          contains
              procedure, nopass :: mluv
              procedure :: print=>write_me
      end type pes
  
      interface pes
          module procedure init_pes
      end interface
  
  contains
  !--------------------------------------------
  type(pes) function init_pes(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_pes%zvire = zvire('Pes')
      init_pes %name = moje_jmeno
      init_pes%rasa = rasa
      
  end function init_pes
  !--------------------------------------------
  subroutine write_me(this)
      class(pes), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%name)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'haf'
  end subroutine mluv
  end module pes_class
  !================================================
  program prog_obj_3
      use kocka_class, only : kocka
      use pes_class, only : pes
      implicit none
  
      type(kocka) :: Fousek
      type(pes) :: Ratafak
  
      Fousek=kocka('Fousek','Bengálská kočka')
      Ratafak=pes('Ratafak','Boxer')
      call Fousek%mluv()
      call Ratafak%mluv()
      call Fousek%write_muj_druh()
      call Fousek%print()
      call Ratafak%print()
  
  end program prog_obj_3
  

Abstraktní třídy

Abstraktní třída je vzor objektu. Nemůžeme je vytvořit jako objekt (instanci). Pomocí atributu deferre můžeme zařídit, že potomci musí definovat funkce s interfacem, který definujeme ve specifikaci procedure. Specifikace non_overridable říká že proceduru nesmí žádný z potomků měnit.

    module zvire_class
  implicit none
  
      private
      integer, parameter :: char_len = 20
      
      public :: zvire
  
      type, abstract :: zvire
          private
          character(len=char_len), public :: druh
          contains
              
            procedure (zvire_mluv), nopass, deferred :: mluv
            procedure (write_me),deferred :: print
            procedure, non_overridable :: write_muj_druh
      end type zvire
  
      abstract interface
        subroutine zvire_mluv()
        end subroutine zvire_mluv
  
        subroutine write_me(this)
          import :: zvire
          class(zvire), intent(in) :: this
        end subroutine write_me
      end interface
  
  contains
  !--------------------------------------------
  subroutine write_muj_druh(this)
      class(zvire), intent(in) :: this
  
      write(*,*) trim(this%druh)
      
  end subroutine write_muj_druh
  end module zvire_class
  !================================================
  module kocka_class
  use zvire_class
  implicit none
  
      private
  
      public :: kocka
  
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: kocka
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: jmeno
  
          contains
              procedure, nopass :: mluv
              procedure :: print=>write_me
      end type kocka
  
      interface kocka
          module procedure init_kocka
      end interface
  
  contains
  !--------------------------------------------
  type(kocka) function init_kocka(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_kocka%druh = "Kocka"
      init_kocka%jmeno = moje_jmeno
      init_kocka%rasa = rasa
  end function init_kocka
  !--------------------------------------------
  subroutine write_me(this)
      class(kocka), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%jmeno)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'mnau'
  end subroutine mluv
  end module kocka_class
  !================================================
  module pes_class
  use zvire_class
  implicit none
  
      private
  
      public :: pes
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: pes
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: jmeno
  
          contains
              procedure, nopass :: mluv
              procedure :: print=>write_me
      end type pes
  
      interface pes
          module procedure init_pes
      end interface
  
  contains
  !--------------------------------------------
  type(pes) function init_pes(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_pes%druh = "Pes"
      init_pes %jmeno = moje_jmeno
      init_pes%rasa = rasa
      
  end function init_pes
  !--------------------------------------------
  subroutine write_me(this)
      class(pes), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%jmeno)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'haf'
  end subroutine mluv
  end module pes_class
  !================================================
  program prog_obj_4
      use kocka_class
      use pes_class
      implicit none
  
      type(kocka) :: Fousek
      type(pes) :: Ratafak
  
      Fousek=kocka('Fousek','Bengálská kočka')
      Ratafak=pes('Ratafak','Boxer')
      call Fousek%mluv()
      call Ratafak%mluv()
      call Fousek%write_muj_druh()
      call Fousek%print()
      call Ratafak%print()
  
  end program prog_obj_4
  

příkaz select type

V příkazu používáme type a class. Type odkazuje na konkrétní typ. Class zastupuje celou rodinu tj. všechny potomky.

    module zvire_class
  implicit none
  
      private
      integer, parameter :: char_len = 20
      
      public :: zvire
  
      type, abstract :: zvire
          private
          character(len=char_len), public :: druh
          contains
              
            procedure (zvire_mluv), nopass, deferred :: mluv
            procedure (write_me),deferred :: print
            procedure, non_overridable :: write_muj_druh
      end type zvire
  
      abstract interface
        subroutine zvire_mluv()
        end subroutine zvire_mluv
  
        subroutine write_me(this)
          import :: zvire
          class(zvire), intent(in) :: this
        end subroutine write_me
      end interface
  
  contains
  !--------------------------------------------
  subroutine write_muj_druh(this)
      class(zvire), intent(in) :: this
  
      write(*,*) trim(this%druh)
      
  end subroutine write_muj_druh
  end module zvire_class
  !================================================
  module kocka_class
  use zvire_class
  implicit none
  
      private
  
      public :: kocka
  
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: kocka
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: jmeno
  
          contains
              procedure, nopass :: mluv
              procedure :: print=>write_me
      end type kocka
  
      interface kocka
          module procedure init_kocka
      end interface
  
  contains
  !--------------------------------------------
  type(kocka) function init_kocka(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_kocka%druh = "Kocka"
      init_kocka%jmeno = moje_jmeno
      init_kocka%rasa = rasa
  end function init_kocka
  !--------------------------------------------
  subroutine write_me(this)
      class(kocka), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%jmeno)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'mnau'
  end subroutine mluv
  end module kocka_class
  !================================================
  module pes_class
  use zvire_class
  implicit none
  
      private
  
      public :: pes
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: pes
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: jmeno
  
          contains
              procedure, nopass :: mluv
              procedure :: print=>write_me
      end type pes
  
      interface pes
          module procedure init_pes
      end interface
  
  contains
  !--------------------------------------------
  type(pes) function init_pes(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_pes%druh = "Pes"
      init_pes %jmeno = moje_jmeno
      init_pes%rasa = rasa
      
  end function init_pes
  !--------------------------------------------
  subroutine write_me(this)
      class(pes), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%jmeno)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'haf'
  end subroutine mluv
  end module pes_class
  !================================================
  module slon_class
  use zvire_class
  implicit none
  
      private
  
      public :: slon
      integer, parameter :: char_len = 20
  
      type, extends(zvire) :: slon
          private
          character(len=char_len) :: rasa
          character(len=char_len) :: jmeno
  
          contains
              procedure, nopass :: mluv
              procedure :: print=>write_me
      end type slon
  
      interface slon
          module procedure init_slon
      end interface
  
  contains
  !--------------------------------------------
  type(slon) function init_slon(moje_jmeno,rasa)
      character(len=*), intent(in) :: rasa
      character(len=*), intent(in) :: moje_jmeno
  
      init_slon%druh = "Slon"
      init_slon %jmeno = moje_jmeno
      init_slon%rasa = rasa
      
  end function init_slon
  !--------------------------------------------
  subroutine write_me(this)
      class(slon), intent(in) :: this
  
      write(*,*) 'Ja jsem ',trim(this%jmeno)//", Jsem "// trim(this%rasa)
      
  end subroutine write_me
  !--------------------------------------------
  subroutine mluv()
  
      write(*,*) 'Pawoo'
  end subroutine mluv
  end module slon_class
  !================================================
  program prog_obj_5
      use kocka_class
      use pes_class
      use slon_class
      use zvire_class
      implicit none
  
      type(kocka) :: Fousek
      type(pes) :: Ratafak
      type(slon) :: Hanno
  
      Fousek=kocka('Fousek','Bengálská kočka')
      Ratafak=pes('Ratafak','Boxer')
      Hanno=slon('Hanno','Slon indicky')
  
      call coJsem(Fousek)
      call coJsem(Ratafak)
      call coJsem(Hanno)
  
  contains
  !------------------------------------------
  subroutine coJsem(obj)
    class(*) obj
    
    select type(obj)
      class is (zvire)
        write(*,*) 'Jsem zvire: '
        call obj%write_muj_druh()
      class default
        write(*,*) 'Nejsem asi nic'
    end select
    
    select type(obj)
      type is (kocka)
        write(*,*) 'Umim mnoukat: '
        call obj%mluv()
      type is (pes)
        write(*,*) 'Umím stekat: '
        call obj%mluv()
      class is (zvire)
        write(*,*) 'Nejsem ani kocka ani pas, umim: '
        call obj%mluv()
      class default
        write(*,*) 'Nejsem asi nic'
    end select
  
  end subroutine
  end program prog_obj_5