《Java与模式》学习笔记——序列键生成器与单例及多例模式

Posted brooksychen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java与模式》学习笔记——序列键生成器与单例及多例模式相关的知识,希望对你有一定的参考价值。

在一个关系数据库中,所有的数据都是存储在表里,而每一个表都有一个主键(Primary Key)。对大多数的用户输入数据来讲,主键需要由系统以序列号方式产生。比如一个餐馆的贩卖系统需要一个序列号给每天开出去的卖单编号,这个序列号码就应当存放到数据库里面。每当发出序列号码的时候,都应当从数据库读取这个号码,并更新这个号码。
 
为了保证在任何情况下键值都不会出现重复,应当使用预定式键值存储办法。在请求一个键值时,首先将数据库中的键值更新为下一个可用值,然后将旧值提供给客户端。这样万一出现运行中断的话,最多就是这个键值被浪费掉。
 
与此相对的是记录式键值存储办法。也就是说,键值首先被返还给客户端,然后记录到数据库中去。这样做缺点明显,因此不要使用这种登记式的存储办法。
 
预定式的存储办法可以每一次预定多个键值(也即一个键值区间),而不是每一次仅仅预定一个值。由于这些值都是一些序列数值,因此,所谓一次预定多个值,不过就是每次更新键值时将键值增加一个大于1的数目。
 
这个序列键管理器可以设计成一个单例类。
 
下面从一个最简单的情况出发,逐渐将问题的复杂性提高,直到给出具有实用价值的解决方案为止。
 
方案一:没有数据库的情况
 
package  com.javapatterns.keygen.ver1;

public   class  KeyGenerator  {
    
private static KeyGenerator keygen = new KeyGenerator();
    
private int key = 1000;

    
private KeyGenerator() {}

    
public static KeyGenerator getInstance() {
        
return keygen;
    }


    
public synchronized int getNextKey() {
        
return key++;
    }

}
 
package  com.javapatterns.keygen.ver1;

public   class  Client  {
    
private static KeyGenerator keygen;

    
public static void main(String[] args) {
        keygen 
= KeyGenerator.getInstance();
        System.out.println(
"key = " + keygen.getNextKey());
        System.out.println(
"key = " + keygen.getNextKey());
        System.out.println(
"key = " + keygen.getNextKey());
    }

}
 
这一设计基本上实现了向客户端提供键值的功能,但是也有明显的缺点。由于没有数据库的存储,一旦系统重新启动,KeyGenerator都会重新初始化,这就会造成键值的重复。为了避免这一点,就必须将每次的键值存储起来,以便一旦系统和重启时,可以将这个键值取出,并在这个值的基础上重新开始。
 
方案二:有数据库的情况
 
package  com.javapatterns.keygen.ver2;

public   class  KeyGenerator  {
    
private static KeyGenerator keygen = new KeyGenerator();

    
private KeyGenerator() {}

    
public static KeyGenerator getInstance() {
        
return keygen;
    }


    
public synchronized int getNextKey() {
        
return getNextKeyFromDB();
    }


    
private int getNextKeyFromDB() {
        String sql1 
= "UPDATE KeyTable SET keyValue = keyValue + 1 ";
        String sql2 
= "SELECT keyValue FROM KeyTable";
        
//execute the update SQL
        
//run the SELECT query
        
//这里只是示意性地返回一个数值
        return 1000;
    }

}
 
Client类的代码与方案一类似,不再重复。
 
在接到客户端的请求时,这个KeyGenerator每次都向数据库查询键值,将新的键值登记到表里,然后将查询的结果返还给客户端。上面的代码中,只给出了SQL语句,为了将注意力集中在系统设计上而并没有给出执行这两行语句的JDBC代码。
 
方案三:键值的缓存方案
 
每一次都进行键值的查询,有必要吗?毕竟一个键的值只是一些序列号码,与其每接到一次请求就查询一次,然后向客户端提供这一个值,不如在一次查询中一次性地预先登记多个键值,然后连续多次地向客户端提供这些预订的键值。这样一来,不是节省了大部分不必要的数据库查询操作吗?
 
这就是键值的缓存机制。当KeyGenerator每次更新数据库中的键值时,它都将键值增加。与方案二不同之处是,键值的增加值不是1而是更多。下面的例子中,键值的增加值是20.为了存储所有的与键有关的信息,特地引进一个KeyInfo类,这个类除了存储与键有关的信息外,还提供了一个retrieveFromDB()方法,向数据库查询键值。
 
package  com.javapatterns.keygen.ver3;

public   class  KeyGenerator  {
    
private static KeyGenerator keygen = new KeyGenerator();
    
private static final int POOL_SIZE = 20;
    
private KeyInfo key ;

    
private KeyGenerator() {
        key 
= new KeyInfo(POOL_SIZE);
    }


    
public static KeyGenerator getInstance() {
        
return keygen;
    }


    
public int getNextKey() {
        
return key.getNextKey();
    }

}
 
package  com.javapatterns.keygen.ver3;

class  KeyInfo  {
    
private int keyMax;
    
private int keyMin;
    
private int nextKey;
    
private int poolSize;

    
public KeyInfo(int poolSize) {
        
this.poolSize = poolSize;
        retrieveFromDB();
    }


    
public int getKeyMax() {
        
return keyMax;
    }


    
public int getKeyMin() {
        
return keyMin;
    }


    
public synchronized int getNextKey() {
        
if (nextKey > keyMax) {
            retrieveFromDB();
        }

        
return nextKey++;
    }


    
private void retrieveFromDB() {
        String sql1 
= "UPDATE KeyTable SET keyValue = keyValue + "
            
+ poolSize + " WHERE keyName = 'PO_NUMBER'";
        String sql2 
= "SELECT keyValue FROM KeyTable WHERE KeyName = 'PO_NUMBER'";
        
// execute the above queries in a transaction and commit it
        
// assume the value returned is 1000
        int keyFromDB = 1000;
        keyMax 
= keyFromDB;
        keyMin 
= keyFromDB - poolSize + 1;
        nextKey 
= keyMin;
    }

}
 
package  com.javapatterns.keygen.ver3;

public   class  Client  {
    
private static KeyGenerator keygen;

    
public static void main(String[] args) {
        keygen 
= KeyGenerator.getInstance();
        
for (int i = 0 ; i < 25 ; i++{
            System.out.println(
"key(" + (i+1)
                
+ ")= " + keygen.getNextKey());
        }

    }

}
 
现在,这个键值生成器已经具有如下的功能:在整个系统是唯一的,能将生成的键值存储到数据库中,以便在系统重新启动时也能够继续键值的生成,而不会造成键值上的重复。
 
这本来已经足够好了,但是还有一点值得设计师考虑改进的是,一般的系统都不会只有一个键值,而是有多个键值需要生成。怎么让上面的设计适用于任意多个键值的情况呢?
 
首先,由于KeyGenerator是单例类,因此,给出多个KeyGenerator的实例并无可能,除非将之推广为多例类。
 
其次,虽然KeyGenerator是单例类,但KeyGenerator仍然可以在内部使用一个聚集管理多个键值。换言之,可以使用一个本身是单例对象的聚集对象,配合上合适的接口达到目的。
 
方案四:有缓存的多序列键生成器
 
此方案是对方案三的改进,引进了一个聚集来存储不同序列键信息的KeyInfo对象。
 
package  com.javapatterns.keygen.ver4;

import  java.util.HashMap;

public   class  KeyGenerator  {
    
private static KeyGenerator keygen = new KeyGenerator();
    
private static final int POOL_SIZE = 20;
    
private HashMap keyList = new HashMap(10);

    
private KeyGenerator() {}

    
public static KeyGenerator getInstance() {
        
return keygen;
    }


    
public int getNextKey(String keyName) {
        KeyInfo keyinfo;
        
if ( keyList.containsKey(keyName) ) {
            keyinfo 
= (KeyInfo) keyList.get(keyName);
            System.out.println(
"key found");
        }
 else {
            keyinfo 
= new KeyInfo(POOL_SIZE, keyName);
            keyList.put(keyName, keyinfo);
            System.out.println(
"new key created");
        }

        
return keyinfo.getNextKey();
    }

}
 
package  com.javapatterns.keygen.ver4;

class  KeyInfo  {
    
private int keyMax;
    
private int keyMin;
    
private int nextKey;
    
private intJava Object 序列化与单例模式 [ 转载 ]

effective java笔记之单例模式与序列化

kotlin-object关键字与单例模式

享元模式与单例模式的区别

简单工厂与单例

工厂模式与单例模式