迷思 2018-04-16
这段时间学习了一下软件开发中的设计模式,这篇读书笔记就谈谈我对设计模式的理解。
设计模式不是一套api,而是一种可复用的、一般性的解决方式,类似于以前谈过的MSF,其官方定义如下:设计模式(design pattern)是软件开发人员在软件开发过程中面临的一般问题的解决方案。设计模式的提出本身是基于面向对象的语言的,没有了面向对象的继承与多态,所有设计模式都玩不转了。设计模式分为三大类,创建型、结构型、行为型,这次就主要谈谈创建型设计模式。
首先是最常见的工厂模式,官方的说法是:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。这句话真的很难懂,所以不如举个例子,到汽车厂提车,无论宝马、奥迪还是奥拓只要提就行了,不必管具体的制造细节。这样的说法感觉清晰了些,可以看出工厂模式提供了一定程度上的封装,但具体它要实现怎样的目的呢?放一段Java源码:
public interface Shape { void draw(); } public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } } public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } } public class ShapeFactory { public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("CIRCLE")){ return new Circle(); } else if(shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; } }
这样一切都清晰了,用我自己的话说,工厂模式实现的是对一类产品族的封装。其实大家多少都用过工厂模式,当初学习C++虚基类以及Java接口的时候,肯定尝试过通过一个抽象类实现各种相应的实体类,这个过程其实即使工厂模式的核心,即基于接口与实现技术,对于C++也可以说是虚基类与继承技术。要说工厂模式比我们原来写的东西多了什么,就是最后的ShapeFactory类以及其getShape方法,我相信当初大部分人都不会使用这种写法,但仔细一下这种方式实现了对上层非常干净的封装,对于用户来说再也不需要想着new一个什么样的对象,需要的只是产生一个ShapeFactory对象,然后从中getShape得到想要的对象,即只要走进汽车工厂就可以取到各种各样的车。
有句题外话,就是我在看工厂模式的过程中突然模糊了Java中虚基类与接口的区别,查了查才想起来虚基类的部分函数是已经实现了的,子类只需实现那些没有实现的类,而接口的函数全部都是虚函数,实现类必须实现其中全部的函数。
下面看看单例模式,这种模式要求该类自己负责创建自己,且需确保只有单个对象被创建。为了确保该类不会被实例化,有种骚操作就是把构造函数设为 private ,这样如果该类被 new 则编译器报错,这种操作真的是长见识。具体的单例模式代码还分为懒汉模式和饿汉模式。
懒汉模式:
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
其中 synchronized 关键字是为了保证线程安全,即多线程访问时给对象上锁。从下可以看到饿汉模式不存在这种问题。
饿汉模式:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; }
这两者的区别就在于,懒汉模式的初始化在被调用时进行,而饿汉模式的初始化在类加载时就进行了,这带来的结果就是懒汉模式更节省内存,但是其本身存在线程不安全的问题,必须使用 sychronized 关键字,所以性能会比较低,一般在单例用的次数少且消耗资源大时使用。
单例模式这样就讲清楚了,但是带来的新的问题是类到底何时被初始化?我查了相关资料发现,JVM并没有规定类何时被加载(加载到JVM方法区和堆区),但严格规定了何时被初始化,其中最常见的两种情况就是 new 一个类或者调用其静态方法(还包括读取、设置静态字段等情况),一旦出现上述情况类就必须被初始化,而饿汉模式中的static成员instance在类加载时被赋值,同时 new 了自身,所以该类在加载时就初始化,懒汉模式就可以等到被调用再初始化。
可见,这么短短两段代码中蕴藏着那么多知识,充分体现了第一次提出规范单例模式的程序员的智慧。
最后再谈谈原型模式。原型模式的目的是创建重复的对象,比如说,如果类的初始化需要很多资源,不如只保存一份相应的对象。从这个角度看,原型模式很像是单例模式的扩展,可以看成是一个产品族的单例集合。看看如下java代码:
public abstract class Shape implements Cloneable { private String id; protected String type; abstract void draw(); public String getType(){ return type; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Object clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } } public class ShapeCache { private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>(); public static Shape getShape(String shapeId) { Shape cachedShape = shapeMap.get(shapeId); return (Shape) cachedShape.clone(); } public static void loadCache() { Circle circle = new Circle(); circle.setId("1"); shapeMap.put(circle.getId(),circle); Square square = new Square(); square.setId("2"); shapeMap.put(square.getId(),square); Rectangle rectangle = new Rectangle(); rectangle.setId("3"); shapeMap.put(rectangle.getId(),rectangle); } }
java中自带的Cloneable类可以很好地实现原型模式的目的。虽然上述代码有些工厂模式的感觉,但其实原型模式的目的与工厂模式完全不同。但是,原型模式最大的用处之一就是和工厂模式结合,如果把上述代码中的形状改为工厂,具体的形状改为不同的工厂,那么最后的 Cache 提供的就是不同工厂的克隆,这很好地实现了工厂的单例性。
本文描述了工厂模式、单例模式、原型模式三种常见的创建型设计模式,下次再来说说结构型设计模式。