1、单例模式(Singleton Pattern)
Ensure a class only has one instance,and provide a global point of access to it.
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
何时使用
- 当系统需要某个类只有一个实例的时候
优点
- 单例模式的类唯一实例由其本身控制,可以很好的控制用户何时访问它。
- 单例模式概念很简单,而且也比较常用。
实现单例模式的思路
-
外界不能造对象 --把无参构造方法私有
-
类本身要造一个 --调用构造方法即可
-
通过公共的方式对外提供
– 通过public修饰
– 又由于无参构造私有,所以要用static修饰符
– 为了保证静态方法只能访问静态成员,所以这个对象也要用static修饰
如果这样… static Student s = new Student();
那么外界可以这样… Student.s = null使用为null的对象可能出现空指针异常错误
– 所以要加private修饰
这样想着,就有了下面的这种写法:
懒汉式,线程不安全
public class Singleton {
private static Singleton instance;
//外界不能造对象 把无参构造方法私有
private Singleton (){}
//通过公共的方式对外提供 通过public修饰
public static Singleton getInstance() {
if (instance == null) {
//类本身要造一个 调用构造方法即可
instance = new Singleton();
}
return instance;
}
}
这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance()的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
饿汉式 static final field
这种方法非常简单,因为单例的实例被声明成 static和 final变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
public class Singleton {
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
懒汉式的三种写法
方法1:懒汉式线程安全
public class Singleton {
//类加载时就初始化
private static final Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance() {
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance()方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。
方式2:双重检验锁
public class Singleton {
private volatile static Singleton instance; //声明成 volatile 保证编译器不进行优化
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
方式3:静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance()之外没有办法访问它,并且因为是内部类,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK版本。
总的来说,方式3结合了懒汉式的懒加载和饿汉式的线程安全
懒汉式、饿汉式在spring IOC中的应用
在spring IOC中,bean在xml中可以配置为singleton
,而且有一个lazy-init属性
lazy-init=true
,设置延迟初始化, 在创建容器之后,在第一次从容器获取对象的时候创建单例的对象
如果没有配置或延迟初始化为默认值, 单例的对象会在创建容器的时候创建对象
2、工厂方法模式(Factory Method Pattern)
别名:虚拟构造(Another Name:Virtual Constructor)。
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclassess.
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
何时使用
- 用户需要一个类的子类的实例,但不希望与该类的子类形成耦合
- 用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用
优点
- 使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦
- 工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可
简单工厂模式
介绍工厂方法模式前,先介绍一下简单工厂模式,简单工厂模式也是一种工厂方法模式。
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么久可以使用简单工厂模式。下面就是简单工厂的例子:
//演示简单工厂
public class SimpleFactory {
public static void main(String args[]) throws Exception{
Factory factory = new Factory();
factory.produce("PRO5").run();
factory.produce("PRO6").run();
}
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品X2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class PRO6 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO6");
}
}
//工厂
class Factory{
MeizuPhone produce(String product) throws Exception{
if(product.equals("PRO5"))
return new PRO5();
else if(product.equals("PRO6"))
return new PRO6();
throw new Exception("No Such Class");
}
}
很容易看出,简单工厂模式是不易维护的,如果需要添加新的产品,则整个系统都需要修改。
如果我们需要添加诸如PRO7、PRO8等产品,直接在工程类中添加即可。
但是如果这时候根部不知道还有什么产品,只有到子类实现时才知道,这时候就需要工厂方法模式。
而在实际应用中,很可能产品是一个多层次的树状结构。
由于简单工厂模式中只有一个工厂类来对应这些产品,所以实现起来是比较麻烦的,
那么工厂方法模式正式解决这个问题的,下面就介绍工厂方法模式。
工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。
这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
针对上面的例子,如果使用工厂方法模式,即将工厂定义为一个接口,然后由具体的工厂来确定需要生成什么样的产品,
为了与简单工厂比较,这里还是贴上代码:
//工厂方法模式
public class FactoryMethod {
public static void main(String args[]){
IFactory bigfactory;
bigfactory = new SmallFactory();
bigfactory.produce().run();
bigfactory = new BigFactory();
bigfactory.produce().run();
}
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品*2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class MX5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台MX5");
}
}
interface IFactory{//抽象的工厂
MeizuPhone produce();
}
//工厂*2
class BigFactory implements IFactory{
@Override
public MeizuPhone produce() {
return new PRO5();
}
}
class SmallFactory implements IFactory{
@Override
public MeizuPhone produce() {
return new MX5();
}
}
如果了解Java的集合框架,那么它就是一个很好的例子:
Java中的Collection接口的实现都能通过iterator()方法返回一个迭代器,
而不同的实现的迭代器都在该实现中以内部类的方式对Iterator接口实现的,然后通过iterator()方法返回。
那么,这个iterator()方法就是一种工厂方法。
可以看到,在这里抽象产品是Iterator接口,具体产品就是Collection接口的实现中对Iterator接口的实现,
构造者是Collection接口,其提供的工厂方法就是Iterator iterator();具体构造者就是Collection的实现。
而工厂方法模式的结构,也就是由前面加粗的4部分组成。
如果对Java容器不熟悉,下面再提供一个例子(模仿Iterator,其实顺便也介绍了Iterator):
如果有多种数据结构要遍历,我们就需要一种用于遍历不同结构的工具,
首先我们就需要为这个工具定义一个接口(抽象产品),用于描述如何来遍历:
//只是需要遍历一堆数据,那么只需要2个方法就可以了
public interface Iterator<T> {
boolean hasNext(); //是否还有下一个元素
T next(); //得到下一个元素
}
然后就是我们要遍历的目标,而这些目标此处我们暂定为列表,这就是构造者:
//便于介绍,不做多的操作
public interface List<T> {
Iterator<T> iterator(); //返回一个遍历器
boolean add(T t); //添加元素到列表
}
对于List可能有多种实现方式,比如数组和链表,此处就简陋的介绍一下,而这些就是具体构造者,
而里面有遍历器的具体实现(具体产品),此处以内部类的形式放到了List的实现(具体构造者)里面,
也完全可以修改代码将遍历器的实现(具体产品)独立出来:
数组的实现:
package com.anxpp.designpattern.factorymethod;
//方便演示而实现的简陋的数组list
public class ArrayList<T> implements List<T>{
private int size; //存放的元素个数,会默认初始化为0
private Object[] defaultList; //使用数组存放元素
private static final int defaultLength = 10;//默认长度
public ArrayList(){ //默认构造函数
defaultList = new Object[defaultLength];
}
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
//添加元素
@Override
public boolean add(T t) {
if(size<=defaultLength){
defaultList[size++] = t;
return true;
}
return false;
}
//遍历器(具体产品)
private class MyIterator implements Iterator<T>{
private int next;
@Override
public boolean hasNext() {
return next<size;
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) defaultList[next++];
}
}
链表实现:
//方便演示而实现的简陋的单向链表list
public class LinkList<T> implements List<T>{
private int size; //存放的元素个数,会默认初始化为0
private Node<T> first; //首节点,默认初始化为null
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
@Override
public boolean add(T t) {
if(size==0){
first = new Node<T>(t,null);
size++;
return true;
}
Node<T> node = first;
while(node.next!=null) {
node = node.next;
node.next = new Node<T>(t,null);
size++;
}
return true;
}
//链表节点
private static class Node<T>{
T data;
Node<T> next;
Node(T data,Node<T> next){
this.data = data;
this.next = next;
}
}
//遍历器
private class MyIterator implements Iterator<T>{
private Node<T> next; //下一个节点
MyIterator(){
next = first;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public T next() {
T data = next.data;
next = next.next;
return data;
}
}
使用上述代码(模式的使用):
package com.anxpp.designpattern.factorymethod;
public class TestUse {
public static void main(String args[]){
//分别定义两种结构
List<Integer> array = new ArrayList<Integer>();
List<Integer> link = new LinkList<Integer>();
//添加数据
for(int i = 1;i < 8; i++){
array.add(i);
link.add(i);
}
//获得迭代器
Iterator<Integer> ai = array.iterator();
Iterator<Integer> li = link.iterator();
//遍历并输出
while(ai.hasNext())
System.out.print(ai.next());
System.out.println();
while(li.hasNext())
System.out.print(li.next());
}
}
控制台会输出:
1234567
1234567
这就是工厂方法模式,其中遍历器也算是一种迭代器设计模式,后面会介绍。这里给出实际应用。
这里只是其中一种应用的举例,当一个接口的一系列实现需要另外的对象对其进行相同操作时,
我们就可以这样用:在这个接口中定义返回另外一个对象的方法(工厂方法),然后再在这个接口的实现中,
返回对其操作的对象。
上面这个例子会在迭代器模式中给出完整的实现代码。
一抽象产品类派生出多个具体产品类;一抽象工厂类派生出多个具体工厂类;每个具体工厂类只能创建一个具体产品类的实例。
即定义一个创建对象的接口(即抽象工厂类),让其子类(具体工厂类)决定实例化哪一个类(具体产品类)。
“一对一”的关系。
与简单工厂间的取舍:工厂方法模式和简单工厂模式在定义上的不同是很明显的。
工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。
工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,
从而推广了简单工厂模式。 反过来讲,简单工厂模式是由工厂方法模式退化而来。
设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。
而这样一来,我们就退化到简单工厂模式了。
可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
如果再分得详细一点,一个工厂可能不只是生产手机(如小米除了手机,连电饭锅都有),但有得工厂智能生成低端的产品,
而大一点的工厂可能通常是生成更高端的产品。所以一个工厂是不够用了,这时,就应该使用抽象工厂来解决这个问题。
3、抽象工厂方法模式(Abstract Factory Pattern)
别名:配套(Another Name:Kit)
Provide an interface for creating families of related or dependent objects without specifying their concrete classess.
提供一个创建一系列或相互依赖对象的接口,而无须指定他们的具体的类。
何时使用:
优点:
上述生成魅族产品的例子中,我们只生产了手机,但是它不止有手机一种产品,可能还有其他的,比如耳机,
为了还可以生成耳机,我们需要对上例进行扩展。
我们先给出上面生成手机的例子的扩展后的抽象工厂模式代码,以比较这几种模式:
//抽象工厂模式
public class AbstractFactory {
public static void main(String args[]){
IFactory bigfactory = new BigFactory();
IFactory smallfactory = new BigFactory();
bigfactory.producePhone().run();
bigfactory.produceHeadset().play();
smallfactory.producePhone().run();
smallfactory.produceHeadset().play();
}
}
//抽象产品*2
interface Headset{
void play();
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品*2*2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class MX5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台MX5");
}
}
class EP21 implements Headset{
@Override
public void play() {
System.out.println("我是一副EP21");
}
}
class EP30 implements Headset{
@Override
public void play() {
System.out.println("我是一台EP30");
}
}
//抽象工厂
interface IFactory{
MeizuPhone producePhone();
Headset produceHeadset();
}
//具体工厂*2
class BigFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new PRO5();
}
@Override
public Headset produceHeadset() {
return new EP30();
}
}
//具体工厂*2
class SmallFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new MX5();
}
@Override
public Headset produceHeadset() {
return new EP21();
}
}
在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。
在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式(不如上例减去耳机这种产品,就回到工厂方法模式了)。
这样举例子其实很空洞,这里只是为了比较三种模式,给出抽象的例子才更容易看出区别。
那么上例中实际应用就是生产迭代器的例子,这里也对齐扩展来介绍抽象工厂模式。Iterator迭代器是Collection专属的,
但是现在我们希望能生产Map的迭代器,我们都知道,Map不是继承自Collection的,遍历的方式是不一样的,
这就相当于2个产品族,接下来我们就要来实现它。
为了演示我们如果实现这个同时能生产Map和Collection的迭代器,我会将例子一步步贴出来:
首先是抽象产品,用来描述迭代器的公共接口:
//抽象产品
public interface IIterator<T> {
boolean hasNext();
Object next();
}
然后是抽象工厂,用来返回不同迭代器:
//抽象工厂
public interface IIteratorFactory<T> {
IIterator<T> iteratorMap(Map<T,Object> m);
IIterator<T> iteratorCollection(Collection<T> c);
}
接下来是具体产品。
Collection的迭代器(具体产品):
//具体产品,Collection迭代器(用到了代理模式)
public class IteratorCollection<T> implements IIterator<T>{
Iterator<T> iterator;
public IteratorCollection(Collection<T> c){
iterator = c.iterator();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
return iterator.next();
}
}
Map的迭代器(具体产品):
//具体产品,Map迭代器(用到了代理模式)
public class IteratorMap<T> implements IIterator<T>{
Iterator<Map.Entry<T, Object>> iterator;
public IteratorMap(Map<T,Object> m){
iterator = m.entrySet().iterator();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Object next() {
return iterator.next().getValue();
}
}
完成具体产品设计后,我们就要实现具体的工厂了:
//具体工厂
public class IteratorFactory<T> implements IIteratorFactory<T>{
@Override
public IteratorMap<T> iteratorMap(Map<T,Object> m) {
return new IteratorMap<T>(m);
}
@Override
public IteratorCollection<T> iteratorCollection(Collection<T> c) {
return new IteratorCollection<T>(c);
}
}
至此,这个小框架就完成了,我们可以使用它来遍历Collection(List,Set,Queue都是集成自它)和Map:
//测试使用
public class TestUse {
public static void main(String args[]){
IIteratorFactory<Integer> factory = new IteratorFactory<>();
Collection<Integer> collection = new ArrayList<Integer>();
Map<Integer, Object> map = new LinkedHashMap<>();
for(int i=0;i<10;i++){
collection.add(i);
map.put(i, i);
}
IIterator<Integer> iteratorCollection = factory.iteratorCollection(collection);
IIterator<Integer> iteratorMap = factory.iteratorMap(map);
while(iteratorCollection.hasNext())
System.out.print(iteratorCollection.next());
System.out.println();
while(iteratorMap.hasNext())
System.out.print(iteratorMap.next());
}
}
输出:
0123456789
0123456789
实际情况下,我们可能不应该这么做,以为Collection面向一种对象的容器,Map是面向两种对象的关联容器,
但是此例使用抽象工厂模式确实实现了不同容器的 统一遍历方式。
如果一个容器持有的大量对象,他们都直接或间接集成自某一个类,使用访问者模式遍历也是一种很好的方式,
具体在后面的访问者模式中会详细介绍。
工厂模式主要就涉及上面介绍的三种:
简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。
它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。
4、生成器模式(Builder Pattern)
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。
何时使用:
- 当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式老构造这样的对象。
- 当某些系统要求对象的构造过程必须独立于创建该对象的类时。
优点:
- 生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
- 生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
- 可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
- 生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
- 当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。
- 模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。
比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。
首先是这个类(产品):
//产品
public class MyDate {
String date;
}
然后就是抽象生成器,描述生成器的行为:
//抽象生成器
public interface IDateBuilder {
IDateBuilder buildDate(int y,int m,int d);
String date();
}
接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:
//具体生成器
public class DateBuilder1 implements IDateBuilder{
private MyDate myDate;
public DateBuilder1(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+"-"+m+"-"+d;
return this;
}
@Override
public String date() {
return myDate.date;
}
}
//具体生成器
public class DateBuilder2 implements IDateBuilder{
private MyDate myDate;
public DateBuilder2(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+" "+m+" "+d;
return this;
}
@Override
public String date() {
return myDate.date;
}
}
接下来是指挥官,向用户提供具体的生成器:
//指挥者
public class Derector {
private IDateBuilder builder;
public Derector(IDateBuilder builder){
this.builder = builder;
}
public String getDate(int y,int m,int d){
builder.buildDate(y, m, d);
return builder.date();
}
}
使用如下:
public class TestUse {
public static void main(String args[]){
MyDate date = new MyDate();
IDateBuilder builder;
builder = new DateBuilder1(date).buildDate(2066, 3, 5);
System.out.println(builder.date());
builder = new DateBuilder2(date).buildDate(2066, 3, 5);
System.out.println(builder.date());
}
}
输出:
2066-3-5
2066 3 5
使用不同生成器,可以使原有产品表现得有点不一样。
往往在实际的应用中,生成器要做的工作不会这么简单,而是相对复杂的(因为其产品一般是比较复杂的),原有构建的维护会转移到生成器的维护上。
5、原型模式(Prototype Pattern)
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
何时使用:
- 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
- 当对象的创建需要独立于它的构造过程和表示时。
- 一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,那么通过该实例复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便
优点:
- 当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
- 可以动态的保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
- 可以在运行时创建新的对象,而无须创建一系列类和集成结构。
- 可以动态的添加、删除原型的复制品。
- 原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。
- 这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
例子中的抽象原型没有使用方法名clone(),其原因下面会介绍。
简单形式的原型模式:
//具体原型
public class SimplePrototype implements Prototype,Cloneable {
int value;
//clone()实现
@Override
public Object cloneSelf() {
SimplePrototype self = new SimplePrototype();
self.value = value;
return self;
}
//使用
public static void main(String args[]){
SimplePrototype simplePrototype = new SimplePrototype();
simplePrototype.value = 500;
SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf();
System.out.println(simplePrototypeClone.value);
}
}
//抽象原型
interface Prototype{
Object cloneSelf();//克隆自身的方法
}
//客户端使用
class Client{
SimplePrototype prototype;
public Client(SimplePrototype prototype){
this.prototype = prototype;
}
public Object getPrototype(){
return prototype.cloneSelf();
}
}
简单的原型模式就是在clone()实现时,new一个新的实例,然后为成员变量赋值后返回。
Java的原生支持
Java中所有类都直接或间接继承自Object类,Object类中已有clone()方法:”protected native Object clone() throws CloneNotSupportedException;“,
可以看到权限是protected的,所以仅有子类可以访问这个方法,但我们可以在子类中重写这个方法,
将访问权限上调到public,然后方法体里面return super.clone()。
我们能看到这个Object方法是可能会抛出异常的,我们必须实现Cloneable接口,才可以使用这个方法,
否则会抛出“java.lang.CloneNotSupportedException”的异常。这个Cloneable接口其实是空的,
下面看演示代码:
//使用 java 自带的支持
public class APITestUse {
public static void main(String args[]) throws CloneNotSupportedException{
MyObject myObject = new MyObject();
myObject.i = 500;
MyObject myObjectClone = (MyObject) myObject.clone();
System.out.println(myObjectClone.i);
}
}
//一个可以复制的对象
class MyObject implements Cloneable{
int i;
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}//结果会输出 500
调用这个方法时,成员变量会自动被复制。所以如果需要使用原型模式,Java原生支持就已经很好用了。
除了以上的原生支持,java中还有一种序列化,只需要对象实现Serializable接口。这样,我们可以将对象写入到流中,可以保存到文件,也可以通过网络发送到其他地方:
//使用Serializable支持克隆
public class SerializablePrototype implements Serializable{
private static final long serialVersionUID = 1L;
private int i;
private transient int notClone;//transient关键字的成员不会被序列化
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int getNotClone() {
return notClone;
}
public void setNotClone(int notClone) {
this.notClone = notClone;
}
public void writeToFile(String path) throws Exception{
FileOutputStream outStream = new FileOutputStream(path);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
objectOutputStream.writeObject(this);
outStream.close();
}
public SerializablePrototype ReadFromFile(String path) throws Exception{
File file = new File(path);
if(!file.exists())
file.createNewFile();
FileInputStream inStream = new FileInputStream(path);
ObjectInputStream objectOutputStream = new ObjectInputStream(inStream);
Object o= objectOutputStream.readObject();
inStream.close();
return (SerializablePrototype) o;
}
public static void main(String args[]) throws Exception{
String path = "D:/SerializablePrototype.instance";
SerializablePrototype prototype = new SerializablePrototype();
prototype.setI(123);
prototype.setNotClone(456);
prototype.writeToFile(path);
SerializablePrototype prototypeClone = new SerializablePrototype();
prototypeClone = prototype.ReadFromFile(path);
System.out.println(prototypeClone.getI() + " " + prototypeClone.getNotClone());
}
}//输出:123 0
我们来分析上例:这个对象有3个成员变量,而其中一个是有transient关键字申明的,一个是序列化id,一个是普通变量,在main方法中,想创建了对象,并设置值,然后写入到文件,再从文件读出来,最后输出读出来的对象的变量,普通变量是可以正常输出的(序列化id也可以,只是此处没有输出来而已),而transient申明的变量为0了,那就证明这个变量没有被保存到文件中,因为这个关键字声明的变量在序列化时会被忽略,而是后来创建时被自动初始化为0了(java中类的成员变量是基本数据类型时,如果没有初值,就会被自动初始化为0)。