观察者模式

正文:https://refactoringguru.cn/design-patterns/observer

Go代码:https://refactoringguru.cn/design-patterns/observer/go/example

!> Reference: https://refactoring.guru/design-patterns/observer/go/example
module observer_pattern

    implicit none
    private

    public :: item_type, customer_type, new_item

    !> Abstract classes
    type, abstract :: subject_type
    contains
        procedure(register_procedure), deferred :: register
        procedure(deregister_procedure), deferred :: deregister
        procedure(notify_all_procedure), deferred :: notify_all
    end type subject_type

    type, abstract :: observer_type
    contains
        procedure(update_procedure), deferred :: update
        procedure(get_ID_procedure), deferred :: get_ID
    end type observer_type

    !> We cannot directly use `class(observer), allocatable :: o_list(:)`
    !>  instead of `type(node), allocatable :: o_list(:)`.
    type node_type
        class(observer_type), allocatable :: o
    end type node_type

    abstract interface
        subroutine register_procedure(self, o)
            import subject_type, observer_type
            class(subject_type), intent(inout) :: self
            class(observer_type), intent(inout) :: o
        end subroutine register_procedure
        subroutine deregister_procedure(self, o)
            import subject_type, observer_type
            class(subject_type), intent(inout) :: self
            class(observer_type), intent(inout) :: o
        end subroutine deregister_procedure
        subroutine notify_all_procedure(self)
            import subject_type
            class(subject_type), intent(inout) :: self
        end subroutine notify_all_procedure
        subroutine update_procedure(self, s)
            import observer_type
            class(observer_type), intent(inout) :: self
            character(len=*), intent(inout) :: s
        end subroutine update_procedure
        function get_ID_procedure(self) result(result)
            import observer_type
            class(observer_type), intent(inout) :: self
            character(len=:), allocatable :: result
        end function get_ID_procedure
    end interface

    !> Specific objects

    type, extends(subject_type) :: item_type
        type(node_type), allocatable :: o_list(:)
        character(len=:), allocatable :: name
        logical :: in_stock
    contains
        procedure :: update_availability
        procedure :: register
        procedure :: deregister
        procedure :: notify_all
    end type item_type

    type, extends(observer_type) :: customer_type
        character(len=:), allocatable :: ID
    contains
        procedure :: update
        procedure :: get_ID
    end type customer_type

contains

    !> Constructor of `item`.
    function new_item(name) result(i)
        character(*), intent(in) :: name
        type(item_type) :: i
        i%name = name
    end function new_item

    !> Remove a object from the subscription array.
    function remove_from_slice(o_list, o_to_remove) result(result)
        type(node_type), intent(inout) :: o_list(:)
        class(observer_type), intent(inout) :: o_to_remove
        type(node_type), allocatable :: result(:)
        character(len=:), allocatable :: id
        integer :: i, j
        i = size(o_list)
        id = o_to_remove%get_ID()
        do j = 1, i
            if (o_list(j)%o%get_ID() == id) then
                allocate (result(i - 1), source=[o_list(:j - 1), o_list(j + 1:)])
                return
            end if
        end do
        result = o_list
    end function remove_from_slice

    !> Append a object to the subscription array.
    function append_slice(o_list, o_to_append) result(result)
        type(node_type), intent(inout), allocatable :: o_list(:)
        class(observer_type), intent(inout) :: o_to_append
        type(node_type), allocatable :: result(:)
        integer :: i
        if (.not. allocated(o_list)) then
            allocate (result(1))
            allocate (result(1)%o, source=o_to_append)
        else
            i = size(o_list)
            allocate (result(i + 1))
            result(1:i) = o_list
            allocate (result(i + 1)%o, source=o_to_append)
        end if
    end function append_slice

    subroutine update_availability(self)
        class(item_type), intent(inout) :: self
        print *, "> Item "//self%name//" 👔 is now in stock."
        self%in_stock = .true.
        call self%notify_all()
    end subroutine update_availability

    subroutine register(self, o)
        class(item_type), intent(inout) :: self
        class(observer_type), intent(inout) :: o
        self%o_list = append_slice(self%o_list, o)
    end subroutine register

    subroutine deregister(self, o)
        class(item_type), intent(inout) :: self
        class(observer_type), intent(inout) :: o
        self%o_list = remove_from_slice(self%o_list, o)
    end subroutine deregister

    subroutine notify_all(self)
        class(item_type), intent(inout) :: self
        integer :: i
        do i = 1, size(self%o_list)
            call self%o_list(i)%o%update(self%name)
        end do
    end subroutine notify_all

    subroutine update(self, s)
        class(customer_type), intent(inout) :: self
        character(len=*), intent(inout) :: s
        print *, "Sending email to customer "//self%ID//" 📨 for item "//s//"."
    end subroutine update

    function get_ID(self) result(result)
        class(customer_type), intent(inout) :: self
        character(len=:), allocatable :: result
        result = self%ID
    end function get_ID

end module observer_pattern
!> Reference: https://refactoring.guru/design-patterns/observer/go/example
program test_observer

    use observer_pattern, only: item_type, customer_type, new_item
    type(item_type) :: shirt_item
    type(customer_type) :: observer_first, observer_second, observer_third

    !> A shirt item
    shirt_item = new_item("A Shirt")

    !> Some customers
    observer_first = customer_type(ID="abc@gmail.com")
    observer_second = customer_type(ID="def@gmail.com")
    observer_third = customer_type(ID="xyz@foxmail.com")

    !> Scene 1
    call shirt_item%register(observer_first)
    call shirt_item%register(observer_second)
    call shirt_item%update_availability()

    !> Scene 2
    call shirt_item%deregister(observer_first)
    call shirt_item%register(observer_third)
    call shirt_item%update_availability()

end program test_observer

!> Results shall be:

!  > Item A Shirt 👔 is now in stock.
!  Sending email to customer abc@gmail.com 📨 for item A Shirt.
!  Sending email to customer def@gmail.com 📨 for item A Shirt.
!  > Item A Shirt 👔 is now in stock.
!  Sending email to customer def@gmail.com 📨 for item A Shirt.
!  Sending email to customer xyz@foxmail.com 📨 for item A Shirt.