有博友留言问“抽象外观类是否能设计为单例类?”,为了能够更全面地回答这个问题,并且为大家在进行面向对象系统设计和实现时提供更多思路,加深对外观模式和单例模式的理解,特写此文。
关于外观模式的基本知识我在此就不介绍了,大家可以参考之前有关外观模式的几篇文章。本文所使用的示例代码如下(此处采用一种特殊的包含@Stereotype的注释在源代码中标注模式角色,目的是在逆向工程生成的UML类图中能够自动标注模式角色,该工具已开发完毕):
//@Stereotype Facade:Subsystem class SubsystemA { public void method() { System.out.println("SubsystemA"); } } //@Stereotype Facade:Subsystem class SubsystemB { public void method() { System.out.println("SubsystemB"); } } //@Stereotype Facade:Subsystem class SubsystemC { public void method() { System.out.println("SubsystemC"); } } //@Stereotype Facade:AbstractFacade abstract class AFacade { private static AFacade instance = null; protected AFacade() {} public abstract void action(); public static AFacade getInstance() { if (instance == null) { instance = (AFacade)XMLUtil.getBean(); } return instance; } } //@Stereotype Facade:ConcreteFacade class CFacade1 extends AFacade { public void action() { SubsystemA a = new SubsystemA(); SubsystemB b = new SubsystemB(); a.method(); b.method(); } } //@Stereotype Facade:ConcreteFacade class CFacade2 extends AFacade { public void action() { SubsystemA a = new SubsystemA(); SubsystemC c = new SubsystemC(); a.method(); c.method(); } } //Client Class class Client { public static void main(String args[]){ AFacade af,af1; af = AFacade.getInstance(); af1 = AFacade.getInstance(); System.out.println(af == af1); af.action(); } }
在上述代码中,我们将抽象外观类设计为单例类(此处只能使用懒汉式单例,大家可以参考前面有关单例模式的文章对懒汉式单例进行进一步改进,解决多线程并发访问的问题),在静态工厂方法getInstance()中并没有直接实例化抽象外观类AFacade(抽象类不能使用new关键字直接实例化),而是通过代码“instance = (AFacade)XMLUtil.getBean();”(这句代码是关键)来获取一个其子类(具体外观类)的对象。在此处,XMLUtil是一个工具类,代码如下:
import javax.xml.parsers.*; import org.w3c.dom.*; import org.xml.sax.SAXException; import java.io.*; public class XMLUtil { //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象 public static Object getBean() { try { //创建文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File("config.xml")); //获取包含类名的文本节点 NodeList nl = doc.getElementsByTagName("className"); Node classNode=nl.item(0).getFirstChild(); String cName=classNode.getNodeValue(); //通过类名生成实例对象并将其返回 Class c=Class.forName(cName); Object obj=c.newInstance(); return obj; } catch(Exception e) { e.printStackTrace(); return null; } } }
XMLUtil从XML配置文件(config.xml)中读取具体外观类类名字符串并反射生成一个外观对象。我们将具体外观类类名存储在XML或properties文件中,此处使用的是XML格式的配置文件,代码如下(config.xml):
<?xml version="1.0"?> <config> <className>CFacade1</className> </config>
编译并运行程序,输出结果如下:
true SubsystemA SubsystemB |
从输出结果可以得知,在客户类Client中,af和af1是两个相同的对象,客户类与具体外观类解耦,没有在客户端直接指定具体外观类。如果需要更换具体外观类,只需修改配置文件config.xml,例如:
<?xml version="1.0"?> <config> <className>CFacade2</className> </config>
编译并运行程序,输出结果如下:
true SubsystemB SubsystemC |
通过对抽象外观类进行单例化改造,既可以确保抽象外观类是一个单例类,只能创建其子类的唯一对象,还可以保证系统的灵活性和可维护性,更换或者增加新的具体外观类无须修改源代码,符合开闭原则。
在本文中,通过反射生成对象的方式来扩展单例类,解决了标准单例模式无法直接通过子类来扩展的问题,在需要设计单例类层次结构时可以考虑使用此设计方案,既可确保对象的唯一性又使得系统具有较好的可扩展性。
【作者:刘伟 http://blog.csdn.net/lovelion】