5.2 设计模式在数值框架中的应用


5.2 设计模式在数值框架中的应用

本节摘要:单个类型会写了,多个类型怎么组织成框架?本节把两种最实用的模式搬进 Fortran:策略模式让求解算法可替换,工厂模式把对象创建收进单一入口。同时讲清多态对象的内存所有权纪律——谁分配、谁释放必须写明白。最后给出判断标准:模式是为了澄清意图,不是为了增加抽象层。

求解器为什么要换策略

线性方程组 Ax=b 的解法没有银弹:矩阵稠密用直接法,大型稀疏用迭代法,迭代收敛慢再换预处理。同一个求解流程,算法在运行时才确定。如果主程序写死某个算法,换方法就得改代码重编译。设计模式要解决的正是这种"变化"——把算法变成可替换的对象,主逻辑不动。

策略模式:把算法变成可替换的对象

策略模式的骨架:一个抽象策略类型声明接口,多个具体策略实现算法,上下文持有抽象策略的引用。用第 5 章的类型扩展就能搭起来。

module solver_mod use iso_fortran_env, only: real64 implicit none type, abstract :: solver_t contains procedure(solve_i), deferred :: solve end type solver_t abstract interface subroutine solve_i(self, a, b, x) import :: solver_t, real64 class(solver_t), intent(in) :: self real(real64), intent(in) :: a(:,:), b(:) real(real64), intent(out) :: x(:) end subroutine solve_i end interface type, extends(solver_t) :: jacobi_solver_t integer :: max_iter = 1000 contains procedure :: solve => jacobi_solve end type jacobi_solver_t type, extends(solver_t) :: gauss_seidel_t integer :: max_iter = 1000 contains procedure :: solve => gs_solve end type gauss_seidel_t contains subroutine jacobi_solve(self, a, b, x) class(jacobi_solver_t), intent(in) :: self real(real64), intent(in) :: a(:,:), b(:) real(real64), intent(out) :: x(:) integer :: n, i, it real(real64), allocatable :: xnew(:) n = size(b) allocate(xnew(n)) x = 0.0_real64 do it = 1, self%max_iter do i = 1, n xnew(i) = (b(i) - sum(a(i,:) * x) + a(i,i) * x(i)) / a(i,i) end do if (maxval(abs(xnew - x)) < 1.0e-10_real64) exit x = xnew end do end subroutine jacobi_solve subroutine gs_solve(self, a, b, x) class(gauss_seidel_t), intent(in) :: self real(real64), intent(in) :: a(:,:), b(:) real(real64), intent(out) :: x(:) integer :: n, i, it n = size(b) x = 0.0_real64 do it = 1, self%max_iter do i = 1, n x(i) = (b(i) - sum(a(i,:) * x) + a(i,i) * x(i)) / a(i,i) end do if (maxval(abs(matmul(a, x) - b)) < 1.0e-10_real64) exit end do end subroutine gs_solve end module solver_mod

调用方只持有 class(solver_t), allocatable :: s,运行期决定 allocate 哪个子类。换算法 = 换一行 allocate,主流程一行不改。这就是策略模式的收益:算法实验的成本降到最低——想对比 Jacobi 和高斯赛德尔的收敛速度,写两个 allocate 跑两遍就行。

工厂模式:把创建逻辑收进一个入口

创建对象往往不只是 allocate:还要读配置、分配工作数组、设置并行上下文。把这些散在业务里,重复又难维护。工厂函数把这些收进一个入口,按配置返回具体对象。

module solver_factory_mod use solver_mod use iso_fortran_env, only: real64 implicit none contains ! 工厂:按配置字符串创建具体求解器 function make_solver(kind, max_iter) result(s) character(*), intent(in) :: kind integer, intent(in) :: max_iter class(solver_t), allocatable :: s select case (kind) case ('jacobi') allocate(jacobi_solver_t :: s) case ('gauss_seidel') allocate(gauss_seidel_t :: s) case default error stop 'unknown solver kind' end select select type (s) type is (jacobi_solver_t) s%max_iter = max_iter type is (gauss_seidel_t) s%max_iter = max_iter end select end function make_solver end module solver_factory_mod program use_factory use solver_factory_mod, only: make_solver use solver_mod, only: solver_t use iso_fortran_env, only: real64 implicit none class(solver_t), allocatable :: s real(real64) :: a(2,2), b(2), x(2) a = reshape([4.0_real64, 1.0_real64, 1.0_real64, 3.0_real64], [2,2]) b = [1.0_real64, 2.0_real64] s = make_solver('jacobi', 200) ! 从配置字符串创建 call s%solve(a, b, x) write(*,*) 'x =', x end program use_factory

工厂的意义不在省几行代码,而在把"创建策略"集中管理:以后创建流程加参数校验、加资源预加载,只改工厂一处。调用方与具体类型彻底解耦——这也是插件式架构的雏形:第三方实现自己的 solver_t 扩展,通过工厂接入,核心代码不动。

内存纪律:多态对象的所有权

多态对象必须用 allocatable 或 pointer 承载,这就把内存所有权摆上台面。两条铁律:一是谁分配谁负责(或委托给 finalizer);二是工厂返回的对象,调用方要清楚自己持有并最终释放。allocatable 多态在离开作用域时自动释放,比指针省心——所以能 allocatable 就别 pointer。若用指针,给类型配 finalizer 释放内部资源,否则泄漏会在长跑模拟里慢慢吃掉内存。

承载方式 自动释放 适合场景
allocatable 多态 工厂返回、局部使用、生命周期与作用域一致
pointer 多态 否,需 finalizer 对象跨作用域共享、链式结构

别为模式而模式

设计模式是给"变化"准备的,没有变化就别硬套。判断标准有三条:加新实现时,主逻辑是否真的不用改?模式有没有让意图更清楚,而不是多一层看不懂的跳转?抽象的开销(多态调用、间接寻址)是否落在非热路径上?三条全过,值得用;有一条不满足,别用。Fortran 工程师对抽象有天然警惕是对的,但把"策略化求解器"这类成熟做法拒之门外,同样会错过它换来的可维护性。

学习目标

阅读完本节,你应当能够:

  1. 用抽象求解器 + 多态指针实现策略模式,运行期切换算法
  2. 用工厂函数按配置创建具体对象,调用方不依赖具体类型
  3. 说明多态对象的分配与释放责任归属
  4. 判断一个场景是否真的需要设计模式

本节要点回顾

  • 策略模式让算法可替换:抽象求解器 + 多态分配,换算法只改一行 allocate
  • 工厂模式集中创建逻辑:配置驱动创建,调用方不依赖具体类型
  • 内存所有权要写明白:allocatable 优先、指针配 finalizer,谁分配谁负责
  • 模式的判断标准是"变化":主逻辑不改、意图更清、开销在非热路径,三条过才用
  • 框架扩展性依赖 5.1 的机制:设计模式是类型扩展与多态的组合应用
  • 下一步的泛型是另一条路:当"多类型重复"来自精度而非行为差异时,参数化类型更对路

行为差异用继承解决,类型差异呢?参数化派生类型登场。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U