Object Orientation

Fortran 2003 made the language fully object-oriented — without abandoning the value model from the derived-types lesson. You get type-bound procedures (methods), inheritance by extending types, polymorphic dispatch on class(name), and optional finalization. The style stays Fortran: objects are still values that copy, and dynamic dispatch is explicit.

Type-bound procedures

A procedure :: name => implementation line inside the type binds a module procedure to the type. Calls use the component selector: obj%name(args). The bound procedure automatically receives the object as its first argument — declared via class(t), intent(inout) :: self — which is how one body serves every instance:

module bank
  implicit none
  private
  public :: account, credit, balance_of

  type :: account
    private
    real :: balance = 0.0
  contains
    procedure :: credit => credit_acct
    procedure :: report => report_acct
  end type account

                

Inheritance: extends

type, extends(parent) :: child reuses the parent's components and procedures and adds new ones. A child is also a parent: any variable declared class(parent) can hold a child instance, and assignment moves the whole record. Drawing on the derived-types lesson, this is inheritance by value extension rather than by reference to shared objects.

Type hierarchy with abstract base and extensions

Fig. 1 — abstract parent, concrete children, polymorphic class variable.

module shapes
  implicit none
  private
  public :: shape, circle, rectangle, area_of

  type, abstract :: shape
    private
    character(len=16) :: name = "generic"
  contains
    procedure(area_iface), deferred :: area   ! no body here
  end type shape

  abstract interface
    real function area_iface(self)
      import :: shape
      class(shape), intent(in) :: self
    end function area_iface
  end interface

  type, extends(shape) :: circle
    private
    real :: radius = 1.0
  contains
    procedure :: area => circle_area
  end type circle

  type, extends(shape) :: rectangle
    private
    real :: width = 1.0, height = 1.0
  contains
    procedure :: area => rect_area
  end type rectangle

contains
  real function circle_area(self)
    class(circle), intent(in) :: self
    circle_area = 3.14159265358979 * self%radius**2
  end function circle_area

  real function rect_area(self)
    class(rectangle), intent(in) :: self
    rect_area = self%width * self%height
  end function rect_area

  real function area_of(s)
    class(shape), intent(in) :: s     ! ANY extension fits here
    area_of = s%area()                ! dispatches to the concrete type
  end function area_of
end module shapes

program shape_app
  use shapes
  implicit none
  type(circle) :: c
  type(rectangle) :: r
  c = circle(radius=2.0)              ! constructor with keyword component
  r = rectangle(width=3.0, height=4.0)
  print '(a,f8.3)', 'circle area:   ', area_of(c)   ! 12.566
  print '(a,f8.3)', 'rectangle area:', area_of(r)   ! 12.000
end program shape_app

How dispatch works

An class(shape) argument holds a hidden type descriptor; when area_of calls s%area(), the runtime follows the descriptor to the child's binding. This is dynamic dispatch — the same mechanism as a virtual table in C++. The cost is one indirect jump per polymorphic call; the compiler records the cost, and performance-sensitive inner loops keep concrete type(...) arguments to avoid it.

Polymorphism by select type

When behaviour depends on which concrete type sits inside a class(...) variable, select type opens branches per type — the Fortran form of pattern matching. It pairs with reallocation of a class variable, the standard way to build heterogeneous containers:

subroutine describe(s)
  class(shape), intent(in) :: s
  select type (s)
  type is (circle)
     print '(a)', 'it is a circle'
  type is (rectangle)
     print '(a)', 'it is a rectangle'
  class default
     print '(a)', 'some other shape'
  end select
end subroutine describe

Finalization

A type may declare a final procedure, invoked automatically when an instance goes out of scope or is overwritten — the RAII-ish hook for closing files, deallocating resources, or logging. Like destructors, final procedures must be careful: they run implicitly, and debugging code that depends on exact finalization ordering is a known sport.

type :: file_log
  integer :: unit
contains
  final :: close_log
end type file_log

subroutine close_log(self)
  type(file_log), intent(inout) :: self
  close (self%unit)
end subroutine close_log
OOP style guide: keep inner loops on type(...); use class(...) only at interface boundaries; make components private behind a module; prefer generic interfaces (modules lesson) over inheritance when the family is small. The study-projects page builds a full object-oriented application to cement these patterns.
contains subroutine credit_acct(self, amount) class(account), intent(inout) :: self real, intent(in) :: amount self%balance = self%balance + amount end subroutine credit_acct real function balance_of(a) class(account), intent(in) :: a balance_of = a%balance end function balance_of subroutine report_acct(self) class(account), intent(in) :: self print '(a,f10.2)', 'balance: ', self%balance end subroutine report_acct end module bank program banking use bank implicit none type(account) :: a call a%credit(100.0) ! method call bound to the object call a%credit(50.0) call a%report() ! "balance: 150.00" print '(a,f10.2)', 'read via accessor: ', balance_of(a) end program banking

Note the encapsulation: private components are invisible outside the module, so the balance can only change through credit. The public API — types and procedures — is exactly what the use bank line in a consumer sees. This is textbook data hiding with zero framework.