模式一直是J2EE领域的热门话题。两年前,对刚开始自学java并且到处碰壁的我,一本在图书馆借的《java与模式》看的我云里雾里,尽管心里面觉的他讲的很有道理,可对于没有实际项目经验的我,真正的理解和应用还很远很远。转眼工作快一年多了了,也做了几个不大不小的项目,尽管还只是个代码工人水准的,但是对于模式的兴趣越发浓厚,于是从书堆里找出来买了好久的GOF《设计模式》。这本书是圣经级别了,可我对C++的了解有限(虽然在学校里还是狠狠啃过好几本大部头,现在忘光了),而且这本圣经对初学者似乎不太友好,对模式也以罗列各条目为主,例子失当。于是买了《设计模式精解》,看china-pub的书评都说对初学者很有用,而且翻译的人也是我很佩服的gigix。
在春节期间读完了这本书,本书详细介绍了13种常见的设计模式,以一个实际问题引出了对面向对象新观点和设计模式的讨论,在介绍完所有的模式后,更难能可贵的是作者详细介绍了自己对模式的使用经验(使用共同点/变化点分析,使用分析矩阵等),整本书读下来令人心旷神怡。
我们为什么要学习设计模式呢?总之是为了获得可以复用和容易扩展的解决方案,建立通用术语以方便团队内的沟通交流,另外,模式能让你以更高的层次或者说视角去观察问题,这样的视角将你从过早处理细节的泥潭中解放出来。模式本身就是对如何创建优良面向对象设计策略的实现:
1.针对接口编程
2.优先使用对象组合,而不是类继承
3.发现并封装变化点
你可以在每一个模式的背后或者每一个优秀设计的背后看到这些原则的影子。比如abstract factory、adapter、strategy体现了针对接口编程,composite、bridge体现了优先使用组合而不是继承等。
作者解释了面向对象的新观点:
原来的观点 新的观点
对象 伴随有方法的数据 拥有责任的实体,或者说拥有特定行为的实体
封装 数据隐藏 各种形式的封装,1.数据的封装,2.方法的封装
3.父类对子类的隐藏 4.其他对象的封装
继承 特化和复用 对象分类的一种方法
这些观点其实并不新,Martin Fowler提出了软件开发过程中的三种视觉:概念、规格和实现,过去我们对面向对象的观点来自于实现的角度(代码的角度)去观察,而新的观点只是从概念的角度重新观察面向对象设计。
深入到具体模式的讨论,记录一些需要注意的问题:
1.Adapter与Facade模式的区别
它们都是包装器,但是两者也有细微的区别:
.两个模式中,我们都有已经存在的类(或者说系统)
.Facade模式中,我们无需针对接口编程;而Adapter模式我们必须针对接口编程
.Adapter模式通常是为了保持多态,而Facade模式对此不感兴趣
.动机不同,Facade模式是为了简化接口,而Adapter模式是针对一个现存的接口编程
结论:Facade模式简化接口,而Adapter模式将接口转换成另一个现有的接口
2.Bridge模式的理解
Bridge模式的意图是将抽象部分与它的实现部分分离,使它们可以独立的变化。这里的关键点是需要理解“实现部分”,如果把“实现部分”看成“对象外部、被对象使用的某种东西”,此模式就很好理解了。我们将变化转移到一个使用或者拥有变化的对象(此对象是指抽象类的对象和用来实现抽象类的派生类的对象)。当出现继承的类爆炸情况时,也许你该考虑此模式的应用场景了。此模式的UML图
3.Observer模式,实现自己的观察者模式也是很简单,不过java已经内置了对此模式的支持。java.util.Observer和java.util.Observable是此模式的一个实现版本,实际应用中你所需要做的只是实现Observer接口,和继承Observable类
4.Decorator模式是为现有的功能动态添加附加功能的一种方法,UML图如下
java的IO库是典型的应用实现,java.io.InputStream和java.io.OutputStream就是图中的Component接口,FilterInputStream继承InputStream(也就是图中的Decorator,装饰器),其他的如ByteArrayInputStream、FileInputStream等直接继承自InputStream的类就是被装饰对象,而继承FilterInputStream的就是各式各样的装饰者。
5.Strategy模式是一种定义算法家族的方法,所有的算法都做相同的工作,它们只是拥有不同的实现。当你的代码中出现了很多switch或者if else的语句,你应该考虑此模式。Strategy模式带来的缺点是类的数量的增加,在java中可以通过将实现类作为嵌套类放在Strategy抽象类中来解决。
6.singleton模式的实现
单线程应用:
第一种:静态初始化
public class Singleton {
private Singleton() {
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
第二种:lazy loading
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance==null)
instance=new Singleton();
return instance; }
}
多线程环境下:在C++中安全的Double-Checked Locking模式,在java中是不安全的,详细原因与java的内存管理模型有关,请见dreamstone的文章
《java中的模式——单态》安全的实现方法是使用同步:
public class Singleton {
static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null)
instance == new Singleton();
return instance;
}
}
《Effective Java》中提到的另一种写法
public class Singleton {
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
在介绍完13个模式之后,作者提出了 一种称为分析矩阵的方法,详细讨论不是这篇短文能说的完,有兴趣请找来此书的电子版看看。简单来讲,先从问题领域中分析出所有的变化点和共同点,观察每一种必须实现的功能并作为矩阵的行,而矩阵中的列表示特定情况中的特定实现;然后观察行,并根据场景探讨使用合适的模式;最后观察列,从整体上考虑整个问题的模式的使用。在出现的概念的场景中添加新的概念来进行设计。
最后,作者总结了面向对象的原则:
.“对象”是负有定义良好的责任的东西
.对象对自己负责
.封装意味着
——数据隐藏
——类隐藏(藏在抽象类或者接口后面)
——实现隐藏(变化封装为对象进行引用)
.使用共同点/变化点分析抽象出行为和数据中的变化点
.针对接口编程
.把继承考虑为一种封装变化的方法,而不是为现有的对象制造特殊情况
.将变化点封装在一个类中,并使之与其他变化点相分离
.力求松耦合
.力求高内聚
.绝对细心地应用“一次并且只有一次”规则(只在一个地方实现一条规则)
书本只是提供了模式的介绍和参照,真正的应用还是要靠自己日常工作中的观察和体验,我将继续在工作中理解并贯彻这些原则。