赞
踩
设计模式贯穿软件开发整个过程,虽然看了很多相关的文章和书籍,但是理解和使用感觉都差点儿意思。
所以借再看设计模式——《漫谈设计模式,从面向对象开始》,梳理其中精髓的设计思想。
变化是永恒的
无论你处于多大的团队,团队采用什么样的开发模式,你总是能听到这样的一句话:
The Only Thing In The World That Doesn't Change Is Change Itself. 世界上唯独不变的是变化本身。
出于从容应对软件开发中的变化,降低软件开发中的风险,成为这一方面的佼佼者,我们学习和使用设计模式就显得很关键了。
在软件开发中,我们经常会遇到诸如,需求变化,技术变化,团队结构变化,公司一些出于利益考虑对软件开发的干涉。
开发还是维护
Andy Hunt 和 Dave Thomas 在 “The Pragmatic Programmer” 一书中认为,软件开发人员始终处于软件维护过程中,原话如下:
Programmers are constantly in maintence mode. Our understanding changes day by day. New requirements arrive as we're designing or coding. Perhaps the environment changes. Whatever the reason, maintenance is not a discrete activity, but a routing part of the entire development process.程序员一直处于维护状态,我们的理解每天都在发生变化,当我们设计和编码时,新的需求总是接踵而至,或许是由于环境的原因吧。不管是什么原因,维护不是一个离散的行为,而是整个日常软件开发的一部分。
使用模式乍一看是增加了代码的复杂度,增加了维护了成本,但我更觉得这降低了维护软件所带来的风险,维护代码的前提是必须去了解代码的设计,并且好的设计会降低修改对整个软件的扰动。
每每看到这段话,我总能想到一个对程序员的玩笑:“要么在修改 bug,要么在制造 bug 的路上”。其实这句话很好的概括了维护软件很重要的两个环节:修改程序错误 (Bug),增强软件功能 (Enhancement)。
DRY(Don't Repeat Yourself)
DRY(Don't Repeat Yourself, 不要复制自己): 也叫 DIE (Duplication Is Evil 复制是魔鬼)。在 "The Pragmatic Programmer" 一书中阐述如下。
Every piece of knowledge must have a single, unambiguous ,authoritative representation within a system.每一份知识在一个系统中必须存在唯一的、明确的、权威的表述。
而我们使用设计模式来避免代码的重复。
简单场景
一个简单程序模拟回家过年的过程:买票、交通工具、团聚。
而我们先假设订票的和团聚的方式是相同的,乘坐交通工具回家的方式不同。UML 静态类图如下:
使用继承
将 GoHomePeople 类作为抽象父类,它包含一个抽象方法 travel(),供子类实现自定义的回家方式。celebrateSpringFestival() 方法保证了 subscribeTicket(), travel() 和 celebrate() 方法按照顺序执行。
public abstract class GoHomePeople { public void celebrateSpringFestival() { subscribeTicket(); travel(); celebrate(); } protected final void subscribeTicket() { //TODO the same way to subscribe Ticket } protected abstract void travel(); protected final void celebrate() { //TODO the same way to celebrate Festival }}
public class PassengerByAir extends GoHomePeople { @Override protected void travel() { //TODO travel by air }}
这样就实现了客户对象使用 PassengerByAir 对象的 celebrateSpringFestival() 方法就可以完成坐飞机回家过年的过程了。
public static void main(String[] args) { GoHomePeople passengerByAir = new PassengerByAir(); passengerByAir.celebrateSpringFestival();}
通过继承,子类中不需要实现那些重复的订票和庆祝团圆的代码了,避免了代码的重复;子类实现了不同的方式的回家方法,把它栓人(hook) 到父类中去。
模版方法模式
GOF 给出的模板方法模式的定义如下:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redifine certain steps of an algorithm without changing the algorithm's structure.定义在一个操作中的一个算法框架,把一些步骤推迟到子类去实现。模版方法模式让子类不需要改变算法结构而重新定义特定的算法步骤。
我们所使用的正是模版方法模式:父类中的方法 celebrateSpringFestival() 是我们的一个模版方法(框架方法),把回家过年分为三步,其中 travel() 是抽象部分,用于子类实现不同客户化逻辑。
模版方法的优势
能够解决代码冗余问题。
把某些算法步骤延迟到子类,子类可以根据不同情况改变/实现这些方法,而子类的新方法不会引起既有父类的功能变化。
易于扩展。比如 PassengerByBoat 类实现坐船回家。
父类提供了算法的框架,控制方法执行流程,而子类不能改变算法流程,子类方法的调用由父类模板方法决定。执行步骤的顺序有时非常重要,我们在容器还在和初始化资源时,为避免执行错误的顺序,经常使用该模式限定子类方法的调用次序。
父类可以把那些重要的、不允许改变的方法屏蔽掉,不让子类去覆写。在 Java 语言为这些方法声明 final 或者 private .
模版方法的劣势
模版方法,比较简单,应用很广泛,但是过分地使用模版方法(Template Method)模式,往往会引起子类的泛滥。书中给出一个举例,查询数据库的记录的需求,步骤拆解如下:
我们需要得到数据库连接 Connection 对象。
创建 Statement 实例并执行相关的查询语句。
处理查询出来的结果并在整个执行过程中处理异常。
整个过程中第一步、第二步以及异常处理的逻辑对于每次查询来说,都是相同的,发生变化的部分主要是在对查询结果的处理上。根据分析,模版方法模式很适合处理这个问题,我们只需要抽象出这个处理查询结果的方法提供不同的子类去延迟实现即可。
但是由于各种各样的查询太多,导致我们需要创建很多的子类来处理这些查询结果,引起子类泛滥。为了解决此问题,通常我们结合使用回调来处理这种问题。
维基百科关于回调函数的定义:
在计算机程序设计中,**回调函数**,或简称 **回调**(Callback 即 call then back 被主函数调用运算后会返回主函数),是指通过参数将函数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。
我们设计SimpleJdbcQueryTemplate 为模版方法类,ResultSetHandler 为回调接口。
查询数据库需求引入回调的类图。
回调表示一段可执行逻辑的引用(或者指针),我们把该引用(或者指针)传递到另外一段逻辑(或者方法)里供这段逻辑适时调用。
伪代码实现
public class SimpleJdbcQueryTemplate { public <T> T query(String queryString, ResultSetHandler<T> rsHandler) { //TODO define variables... try { //获得一个数据库连接 connection = getConnection(); //创建一个 PreparedStatement实例,准备查询; PreparedStatement statement = connection.prepareStatement(queryString); //statement 对象去数据库执行该查询并返回结果; ResultSet resultSet = statement.executeQuery(); //处理查询结果并返回 return rsHandler.handle(resultSet); } catch (SQLException exception) { //TODO handle exception } finally { //TODO close statement and connection } } //other methods}
测试伪代码
boolean called = new SimpleJdbcQueryTemplate().query("SELECT * FROM DB", rs -> { //TODO resolve the query result return Boolean.TRUE;});
我们使用 lamba 表达式语法来回调处理查询结果,即使有不同的查询,也不需要增加一个专门的文件。
介绍了 DRY 原则。重复的代码会带来维护的噩梦。
使用模版方法模式。该模式非常简单,可以有效解决某些场景中的代码冗余问题,但也可能引入类的泛滥问题。
使用回调可以避免类的泛滥。
本篇文章为阅读《漫谈设计模式:从面向对象开始》读书笔记。
本系列会随着读书的进度持续更新下去。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。