10.Java设计模式 工厂模式,单例模式
Java 之工厂方法和抽象工厂模式
1. 概念
工厂方法:一抽象产品类派生出多个具体产品类;一抽象工厂类派生出多个具体工厂类;每个具体工厂类只能创建一个具体产品类的实例。
即定义一个创建对象的接口(即抽象工厂类),让其子类(具体工厂类)决定实例化哪一个类(具体产品类)。“一对一”的关系。
抽象工厂:多个抽象产品类,派生出多个具体产品类;一个抽象工厂类,派生出多个具体工厂类;每个具体工厂类可创建多个具体产品类的实例。
即提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体的类。“一对多”的关系。
2. UML
工厂方法:
抽象工厂:
3. 代码
工厂方法:
public interface Product { } public interface Creator { public Product factory(); } public class ConcreteProduct1 implements Product { public ConcreteProduct1() { System.out.println("ConcreteProduct1被创建"); } } public class ConcreteProduct2 implements Product { public ConcreteProduct2() { System.out.println("ConcreteProduct2被创建"); } } public class ConcreteCreator1 implements Creator { public Product factory() { return new ConcreteProduct1(); } } public class ConcreteCreator2 implements Creator { public Product factory() { return new ConcreteProduct2(); } } public class Client { private static Creator creator1, creator2; private static Product prod1, prod2; public static void main(String[] args) { creator1 = new ConcreteCreator1(); prod1 = creator1.factory(); System.out.println("----------------------------"); creator2 = new ConcreteCreator2(); prod2 = creator2.factory(); } }
抽象工厂:
//定义不同的产品之间的一定具备的标准,用interface实现 //其中的method()方法可看作提取出不同产品的共性,如手机都有类似的功能 interface IProductA{ public void method(); } interface IProductB{ public void method(); } //实现了产品标准实现的一系列具体产品 //由于已经设计好A1由厂商1生产,故以下输出代码有“厂商x” class ProductA1 implements IProductA{ public void method() { System.out.println("厂商A1 生产ProductA1 ..."); } } class ProductA2 implements IProductA{ public void method() { System.out.println("厂商A2 生产ProductA2 ..."); } } class ProductB1 implements IProductB{ public void method() { System.out.println("厂商B1 生产ProductB1 ..."); } } class ProductB2 implements IProductB{ public void method() { System.out.println("厂商B2 生产ProductB2 ..."); } } //每一种牌子的产品生产工厂,即不同的厂商负责自己牌子产品的生产 abstract class Factory1{ abstract IProductA getProductA1(); abstract IProductB getProductB1(); } abstract class Factory2{ abstract IProductA getProductA2(); abstract IProductB getProductB2(); } //具体的工厂用来生产相关的产品 class ConcreteFactory1 extends Factory1{ public IProductA getProductA1() { return new ProductA1(); } public IProductB getProductB1() { return new ProductB1(); } } class ConcreteFactoryB extends Factory2{ public IProductA getProductA2() { return new ProductA2(); } public IProductB getProductB2() { return new ProductB2(); } } //测试类 public class Client { public static void main(String[] args) { //厂商1负责生产产品A1、B1 Factory1 factory1 = new ConcreteFactory1(); IProductA productA1 = factory1.getProductA1(); IProductB productB1 = factory1.getProductB1(); productA1.method(); productB1.method(); //厂商2负责生产产品A2、B2 Factory2 factory2 = new ConcreteFactoryB(); IProductA productA2 = factory2.getProductA2(); IProductB productB2 = factory2.getProductB2(); productA2.method(); productB2.method(); } }
4. 应用场景
工厂方法:
在以下情况下,适用于工厂方法模式:
(1) 当一个类不知道它所必须创建的对象的类的时候。
(2) 当一个类希望由它的子类来指定它所创建的对象的时候。
(3) 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
抽象工厂:
(1) 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
(2) 这个系统有多于一个的产品族,而系统只消费其中某一产品族。
(3) 同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
(4) 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
Java 之单例模式
一.问题引入
偶然想想到的如果把Java的构造方法弄成private,那里面的成员属性是不是只有通过static来访问呢;如果构造方法是private的话,那么有什么好处呢;如果构造方法是private的话,会不更好的封装该内呢?我主要是应用在使用普通类模拟枚举类型里,后来发现这就是传说中的单例模式。构造函数弄成private 就是单例模式,即不想让别人用new 方法来创建多个对象,可以在类里面先生成一个对象,然后写一个public static方法把这个对象return出去。(eg:public 类名 getInstancd(){return 你刚刚生成的那个类对象;}),用static是因为你的构造函数是私有的,不能产生对象,所以只能用类名调用,所有只能是静态函数。成员变量也可以写getter/setter供外界访问的。
第一个代码不是单例模式,也就是说不一定只要构造方法是private的就是单例模式。
class A(){ private A(){} public name; pulbic static A creatInstance(){ return new A(); } } A a = A.createInstance(); a.name; //name 属性
public class single{ private static final single s=new single(); private single(){ } public static single getInstance(){ return s; } }
二.单例模式概念及特点
java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
单例模式有一下特点:
1、单例类只能有一个实例。
2、单例类必须自己自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
正是由于这个特 点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
public class Singleton { private static Singleton uniqueInstance = null; private Singleton() { // Exists only to defeat instantiation. } public static Singleton getInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } // Other methods... }
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实例。
public class TestStream { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } //该类只能有一个实例 private TestStream(){} //私有无参构造方法 //该类必须自行创建 //有2种方式 /*private static final TestStream ts=new TestStream();*/ private static TestStream ts1=null; //这个类必须自动向整个系统提供这个实例对象 public static TestStream getTest(){ if(ts1==null){ ts1=new TestStream(); } return ts1; } public void getInfo(){ System.out.println("output message "+name); } }
public class TestMain { public static void main(String [] args){ TestStream s=TestStream.getTest(); s.setName("张孝祥"); System.out.println(s.getName()); TestStream s1=TestStream.getTest(); s1.setName("张孝祥"); System.out.println(s1.getName()); s.getInfo(); s1.getInfo(); if(s==s1){ System.out.println("创建的是同一个实例"); }else if(s!=s1){ System.out.println("创建的不是同一个实例"); }else{ System.out.println("application error"); } } }
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。
其次,下面是单例的三种实现。
1.饿汉式单例类
下面这个可以不加final,因为静态方法只在编译期间执行一次初始化,也就是只会有一个对象。
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 { //私有的默认构造子 private Singleton1() {} //已经自行实例化 private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
2.懒汉式单例类
那个if判断确保对象只创建一次。
//懒汉式单例类.在第一次调用的时候实例化 public class Singleton2 { //私有的默认构造子 private Singleton2() {} //注意,这里没有final private static Singleton2 single=null; //静态工厂方法 public synchronized static Singleton2 getInstance() { if (single == null) { single = new Singleton2(); } return single; } }
3.登记式单例类
import java.util.HashMap;
import java.util.Map; //登记式单例类. //类似Spring里面的方法,将类名注册,下次从里面直接获取。 public class Singleton3 { private static Map<String,Singleton3> map = new HashMap<String,Singleton3>(); static{ Singleton3 single = new Singleton3(); map.put(single.getClass().getName(), single); } //保护的默认构造子 protected Singleton3(){} //静态工厂方法,返还此类惟一的实例 public static Singleton3 getInstance(String name) { if(name == null) { name = Singleton3.class.getName(); System.out.println("name == null"+"--->name="+name); } if(map.get(name) == null) { try { map.put(name, (Singleton3) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } //一个示意性的商业方法 public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) { Singleton3 single3 = Singleton3.getInstance(null); System.out.println(single3.about()); } }
四.单例对象作配置信息管理时可能会带来的几个同步问题
1.在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化和单例对象的属性更新。
本文描述的方法有如下假设:
a. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。
b. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。
1.1单例对象的初始化
首先,讨论一下单例对象的初始化同步。单例模式的通常处理方式是,在对象中有一个静态成员变量,其类型就是单例类型本身;如果该变量为null,则创建该单例类型的对象,并将该变量指向这个对象;如果该变量不为null,则直接使用该变量。
这种处理方式在单线程的模式下可以很好的运行;但是在多线程模式下,可能产生问题。如果第一个线程发现成员变量为null,准备创建对象;这是第二 个线程同时也发现成员变量为null,也会创建新对象。这就会造成在一个JVM中有多个单例类型的实例。如果这个单例类型的成员变量在运行过程中变化,会 造成多个单例类型实例的不一致,产生一些很奇怪的现象。例如,某服务进程通过检查单例对象的某个属性来停止多个线程服务,如果存在多个单例对象的实例,就 会造成部分线程服务停止,部分线程服务不能停止的情况。
1.2单例对象的属性更新
通常,为了实现配置信息的实时更新,会有一个线程不停检测配置文件或配置数据库的内容,一旦发现变化,就更新到单例对象的属性中。在更新这些信 息的时候,很可能还会有其他线程正在读取这些信息,造成意想不到的后果。还是以通过单例对象属性停止线程服务为例,如果更新属性时读写不同步,可能访问该 属性时这个属性正好为空(null),程序就会抛出异常。
下面是解决方法
//单例对象的初始化同步 public class GlobalConfig { private static GlobalConfig instance = null; private Vector properties = null; private GlobalConfig() { //Load configuration information from DB or file //Set values for properties } private static synchronized void syncInit() { if (instance == null) { instance = new GlobalConfig(); } } public static GlobalConfig getInstance() { if (instance == null) { syncInit(); } return instance; } public Vector getProperties() { return properties; } }
这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。
单例对象的属性更新同步。
参照读者/写者的处理方式,设置一个读计数器,每次读取配置信息前,将计数器加1,读完后将计数器减1.只有在读计数器为0时,才能更新数据,同时要阻塞所有读属性的调用。
代码如下:
public class GlobalConfig { private static GlobalConfig instance; private Vector properties = null; private boolean isUpdating = false; private int readCount = 0; private GlobalConfig() { //Load configuration information from DB or file //Set values for properties } private static synchronized void syncInit() { if (instance == null) { instance = new GlobalConfig(); } } public static GlobalConfig getInstance() { if (instance==null) { syncInit(); } return instance; } public synchronized void update(String p_data) { syncUpdateIn(); //Update properties } private synchronized void syncUpdateIn() { while (readCount > 0) { try { wait(); } catch (Exception e) { } } } private synchronized void syncReadIn() { readCount++; } private synchronized void syncReadOut() { readCount--; notifyAll(); } public Vector getProperties() { syncReadIn(); //Process data syncReadOut(); return properties; } }
采用"影子实例"的办法具体说,就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库或文件中读取最新的配置信息;然后将这些配置信息直接赋值给旧单例对象的属性。
public class GlobalConfig { private static GlobalConfig instance = null; private Vector properties = null; private GlobalConfig() { //Load configuration information from DB or file //Set values for properties } private static synchronized void syncInit() { if (instance = null) { instance = new GlobalConfig(); } } public static GlobalConfig getInstance() { if (instance = null) { syncInit(); } return instance; } public Vector getProperties() { return properties; } public void updateProperties() { //Load updated configuration information by new a GlobalConfig object GlobalConfig shadow = new GlobalConfig(); properties = shadow.getProperties(); } }
注意:在更新方法中,通过生成新的GlobalConfig的实例,从文件或数据库中得到最新配置信息,并存放到properties属性中。上面两个方法比较起来,第二个方法更好,首先,编程更简单;其次,没有那么多的同步操作,对性能的影响也不大。