查看原文
其他

冯火专栏 | HIT软件工程师,不妨学一点软件设计模式“套路”

冯火 HIT专家网官微 2022-11-03

导读

如果只关注如何写代码,而不关注背后的思想,就会越学越懵。

在面向对象编程过程中,经过软件工程的长期实践,有一些经过总结并被证明是成熟、可靠、通用的解决方案,也被称为“设计模式”。它对确保软件质量、可维护性等有着至关重要的作用。如果您是一名HIT软件工程师,想必对单一职责原则、开闭原则等都有一定了解,也听说过什么是工厂模式、代理模式等。
不过,对于刚刚进入HIT软件开发领域的初学者来说,学习软件设计模式会存在一定的困难,原因有二:一方面是初学者基础不牢,导致看不懂代码(有个别设计模式,的确很难看懂代码);另一方面,来自于学习方法不正确,只关注如何写代码,而不关注背后的思想,导致越学越懵。
笔者也曾走过一段弯路,为了搞懂23种经典设计模式,先把那些设计模式代码逐个研究一番,发现越学越懵,比如代理模式和装饰器模式有点像、桥接模式与抽象工厂模式有点像等;在CSDN等专业网站上查阅学习资料,发现那些文章往往过于专业,很难搞清背后的原理,容易陷入代码的海洋中。
什么是学习设计模式的“正确姿势”?笔者认为,首先要摒弃“一上来就马上研究代码”这种习惯性动作,而应先弄清楚这种设计模式主要用来解决什么问题,然后再去看代码是如何实现的。在本文中,笔者将通过两个学习过的常用设计模式,分享一下设计模式的学习心得,同时也建议HIT领域的软件工程师也适当学习一些软件设计模式,并借鉴设计模式的思想,运用在实际工作中。
一、工厂模式(Factory Design Pattern)
学习工厂模式,首先要搞清楚什么是“工厂问题”?想想我们日常生活中的手机工厂、衣服工厂、鞋子工厂等,这些工厂是用来干什么的?归纳总结一下,可以得出一个结论:工厂是用来生产一组相近商品的地方。
回到面向对象编程,“工厂问题”可以理解为“生产一组相近功能的对象”。比如说,“配置文件工厂”就是解决多个相近配置对象的问题。
假设系统中存在多种格式的配置文件,有XML、JSON、YAML等,如图1所示。

图1 多种格式的配置文件

如果我们不使用工厂模式,以上代码便于维护、易使用吗? 显然不是。第一个问题,增加使用者的成本,需要记住多个类;第二个问题,这些对象没有集中管理起来,对于维护者也产生很大麻烦。所以,就有了工厂模式,把它们放进一个“配置工厂”(如图2所示),便于以后的维护与使用。

图2 配置文件的工厂模式

工厂模式的核心思想是“创建与使用相分离”。对于使用者而言,每次从“工厂”中获取相应对象即可,非常方便。
工厂模式又分为简单工厂、工厂方法、抽象工厂。抽象工厂无法直接通过new关键字创建对象,如果需要“数据库连接抽象工厂”,由于无法实现确定连接的数据库是何种类型(如Oracle、MySQL、SQL Server等),所以需要提供一个实现类用以实现相应的接口,再去创建对象。
在实际应用中,可以使用spring项目管理框架,协助进行工厂模式的设计。举例说明:创建“简单工厂”,先通过配置文件进行配置,然后通过工厂拿到相应对象,代码如图3所示。

图3 spring中的简单工厂

通过spring启动工厂,读取相应配置,就可以实现“简单工厂”的效果。需要注意的是,图3中的代码默认拿到是单例对象,如需改成多例对象,应在bean标签中增加sope=prototype的配置。
如何在spring使用抽象工厂(有些时候也称为复杂工厂)?首先,提供一个FactoryBean接口实现类,再将这个类注册到spring.xml,通过工厂来获取这个对象,代码如图4-图6所示。

图4 FactoryBean接口实现类

图5 spring.xml

图6 在工厂中获取Connection对象

spring是对传统工厂概念的进一步抽象,对所有的对象进行管理,所以又将spring称为容器。提到spring,不得不提一个概念:控制反转IOC(Inversion of Controll)。这是站在使用者角度提出的概念,原来是由使用new关键字创建对象,现在把这个new权限交给spring框架,达到一个反转的效果,这个过程就称之为控制反转。在IOC基础上又提出依赖注入DI(Dependency Injection)概念,代码如图7-图8所示。

图7 UserServiceImpl类

图8 spring.xml

从上图可以看出,由于spring.xml中配置了<property name=”userDAO” ref=”userDAO” />,将来使用UserServiceImpl类,里面有一个成员变量UserDAO,spring自动完成赋值,这个过程就叫依赖注入。
二、代理模式(Proxy Design Pattern)
提到代理模式,离不开一个重要的概念:面向切面的编程AOP(Aspect Oriental Programing)。只要理解了AOP,就搞懂了代理模式需要解决的问题。假设存在两个类:UserServiceImpl和OrderServiceImpl,如图9-图10所示。

图9 UserServiceImpl类

图10 OrderServiceImpl类

UserServiceImpl类和OrderServiceImpl类都有save方法。假设都需要实现事务功能,对于find方法进行性能统计,最直接的办法是在两个类中都实现事务功能、性能统计,但这种方法肯定不是最佳的:第一个问题是重复造轮子,事务功能、性能统计本来只有一遍代码,结果写了两次;第二个问题是不符合单一职责原则,OrderServiceImpl类里本来只需要包含“订单”逻辑,结果被硬生生加入一个与“订单”无关的“性能统计”逻辑。
面对这种情况应当怎么办?回到我们的日常生活中,发现也有这种“代理”问题,代理交宽带费、代理交电费、代理买保险等,既然不同的用户都有同样的需求,我们可以把这些事情交给第三方“代理”来做,从而节省自己的精力投入到更需要专注的事情上。同样道理,可以将事务功能、性能统计这种非业务属性的共性功能交给第三方“代理类”来处理,我们专注处理自己的业务逻辑。我们可以把每个类想象成一个多层蛋糕,每一个功能就像一层“蛋糕”,把事务功能、性能统计等层级的“蛋糕”通通切走,拿给“第三方”去处理,这就是所谓面向切面编程。
面向切面编程,就是指使用代理模式来完成非业务功能的通用逻辑处理。有了前面这些认知,我们再来看代理模式的两种方式:静态代理和动态代理。
什么是静态代理?就是只能为其中某个类进行代理,所以每一个类都需要写一个“代理类”,代码如图11所示。

图11 静态代理

有了代理模式以后,就可以不直接使用UserService类,而是使用UserServiceStaticProxy类,在其中调用UserService类的原有功能和附加的“事务处理”功能。静态代理在实际中使用不多,了解即可,使用最多的还是动态代理,也即为所有的类做“代理”,代码如图12所示。

图12 动态代理

在实际应用过程中,代理模式通常也不需要我们从头到尾去实现一遍,利用spring提供面向切面编程的环境来开发即可。spring AOP中引入了两个概念,通知(Advice)和切入点(PointCut)。
什么是Advice?UserService类中包含save和find方法,并不包含事务功能、性能统计这两个功能,需要在代理类中实现。这些需要在代理类中实现的额外功能就叫Advice。
什么是PointCut?因为spring框架事先不知道需要为某个类或类中的某个方法实现附加功能,需要我们指明通过哪个类、哪个方法实现附加功能,这就叫PointCut。Advice、PointCut共同组成一个切面(Aspect)。
通知又分为:前置通知(MethodBeforeAdvice)、环绕通知(MethodInterceptor)、后置通知(AfterReturningAdvice)、异常通知(ThrowsAdvice)等。前置通知的代码如图13-图14所示。

图13 前置通知类

图14 配置spring.xml

小结一下:工厂模式主要用于解决对象创建与使用相分离的问题,在spring中主要起到一种管理作用,我们在开发过程中,通过解决一系列“工厂问题”可提高代码的可读性、维护性。代理模式主要解决多个类中存在非功能性的共有逻辑,比如事务控制、日志记录、权限控制等,在spring中利用AOP可以降低软件开发难度,同时提高代码可读性、维护性。
最后,笔者有一个小问题想提给读到此处的读者:不知这篇文章对您来说是否有所帮助?是太难了、太容易了,还是没必要,互联网上存在大量类似的学习资料,大家都懂?期待您通过留言方式反馈您的想法。
作者简介
冯火资深医信工程师、系统架构设计师。

近期热文
HIT专家网∣致力推进中国卫生信息化长按二维码可申请加入HIT专家网专业交流群投稿:gong_chen@HIT180.com

商务合作:(010)82373062


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存