设计模式实用案例之工厂模式

本着实用的原则,我们应用工厂模式的时候首先应抓住最常用的方法,掌握了最常用的方法,熟练应用后将自然的掌握更多的派生方法。

比如工厂模式有简单工厂(Simple Factory )模式、多态工厂模式(Polymorphic Factory)、抽象工厂模式(Abstract Factory Pattern)。但这其中最常用的就是简单工厂模式。

下面我们用一个实际案例来看看简单工厂模式。

 我们需要实现一个能够灵活配置数据库访问的功能,现在我们有一些实时数据库(eDNA、openplant、PI等),每一个实时数据库有自己的API,但其行为都是相似的,例如:

1、取某个点(tag)的实时值(传入点名);

2、按照一定的时间间隔取某个点的区间值的List(传入点名、start_time、end_time、interval;取该点从start_time到end_time的所有值,间隔为interval);

3、更新某个点的值(传入点名、更新时间、更新值)。

 好了,作为产品化的考虑,上层应用的功能是标准的,而上层应用依赖于数据库的就是上面几种行为方法。由于不同的项目客户所使用的数据库不同,所以我们经常需要切换数据库。我们的目标是,系统将所有数据库的API都实现一遍,然后我们希望在配置文件中配置具体使用哪个数据库,这样产品客户化时,配置一下数据库,上层应用功能就能灵活的调用相应数据库的API来实现与数据库的交互功能。

我们使用工厂模式来实现。首先看工厂类:

import com.cpeam.sis.service.RTDBService;
import com.cpeam.sis.util.ConfigUtil;

/**
 * 创建 RTDBService的工厂类
 */
public class RTDBServiceFactory {
    /**
     * 构造方法
     */
    private RTDBServiceFactory() {
        
    }
    
    /**
     * 根据类名初始化RTDBServic对象
     * @param classname RTDBServic类名称
     * @return RTDBServic对象
     * @throws ClassNotFoundException 没有找到类异常
     * @throws InstantiationException 实例化异常
     * @throws IllegalAccessException 非法访问异常
     */
    public static RTDBService createRTDBService(String classname) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        return (RTDBService) Class.forName(classname).newInstance();
    }
    
    /**
     * 根据配置文件初始化RTDBServic对象
     * 在classpath存在rtdb.properties文件, 则使用rtdb.properties文件中classname对应的类名来初始化RTDBServic对象, 否则初始化默认的CpeamDBService对象
     * @return RTDBServic子对象
     * @throws IOException IO异常
     * @throws InstantiationException 实例化异常
     * @throws IllegalAccessException 非法访问异常
     * @throws ClassNotFoundException 没有找到类异常
     */
    public static RTDBService createRTDBService()  {
        try {
            return (RTDBService) Class.forName(ConfigUtil.getRtdbClassname()).newInstance();
        } catch (InstantiationException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        
        return null;
    }
}

如上面的代码,工厂提供了2个重载(overload)的创建数据库服务的方法,一个是传入数据库的名字,一个是没有参数、根据配置文件。具体使用了反射(Class.forName().newInstance()方法)来创建服务,这样比较灵活,当我们新增实现类的时候不需要重写工厂类。

在工厂生产的产品的结构中,需要有1个接口和N个实现。先看接口,接口规定了数据库服务的标准方法:

public interface RTDBService {
    /**
     * 根据测点名称获得当前的实时值
     * @param pointName 测点名称
     * @return 测点实时值
     */
    public abstract double getRealtimeValue(String pointName);
    
    /**
     * 根据测点名称获得当前的实时值
     * @param pointName 测点名称
     * @param accuracy 精度
     * @param errorValue 当获取实时值发生错误时返回的值
     * @return 测点实时值
     */
    public abstract double getRealtimeValue(String pointName, int accuracy, double errorValue);
    
    /**
     * 根据测点名称集合获得当前测点集合中每个测点的实时值集合
     * @param pointNames 测点名称集合
     * @param accuracy 精度
     * @return 测当前测点集合中每个测点的实时值集合
     */
    public abstract List<PointValueInfo> getRealtimeValues(List<String> pointNames, int accuracy);
    
    /**
     * 根据测点名称获得一段时间内的测点值对象集合
     * @param pointName 测点名称
     * @param start 开始时间
     * @param end 结束时间
     * @param interval 间隔(s)
     * @param maxPointNumber 测点值对象集合的元素数量最大值
     * @param historyType 历史类型
     * @param accuracy 精度
     * @param errorValue 当获取一段时间内的测点值对象集合出错时每个测点值对象的值
     * @return 一段时间内的测点值对象集合
     */
    public List<PointValueInfo> getPointValueInfoHistory(String pointName, Date start, Date end, int interval, long maxPointNumber, String historyType, int accuracy, double errorValue);
    
    /**
     * 根据测点名称获得一段时间内的测点值对象集合
     * @param pointNames 多个测点名称
     * @param start 开始时间
     * @param end 结束时间
     * @param interval 间隔(s)
     * @param maxPointNumber 测点值对象集合的元素数量最大值
     * @param historyType 历史类型
     * @param accuracy 精度
     * @param errorValue 当获取一段时间内的测点值对象集合出错时每个测点值对象的值
     * @return 一段时间内的测点值对象集合
     * @return
     */
    public List<PointValueInfo> getPointValueInfoHistory(List<String> pointNames, Date start, Date end, int interval, long maxPointNumber, String historyType, int accuracy, double errorValue);
    
    /**
     * 更新测点值
     * @param pointName 测点名称
     * @param time 时间戳
     * @param value 值
     * @return
     */
    public boolean updatePoint(String pointName, long time, double value);
    
    /**
     * 批量修改测点值
     * eDNA:最大支持160个
     * @param pointNames
     * @param time
     * @param values
     * @return
     */
    public boolean updatePoints(String[] pointNames, long time, double[] values);
    
    /**
     * 批量修改测点值
     * eDNA:最大支持160个
     * @param pointName
     * @param times
     * @param values
     * @return
     */
    public boolean updatePoints(String pointName, long[] times, double[] values);
}

基于上面的接口,可以有多个实现,具体的实现需要根据不同的数据库产品提供的api来实现接口,下面看一个例子:

/**
 * eDNA实时数据库接口实现类
 */
public class EDNADBServiceImpl implements RTDBService {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//时间格式化
    
    @Override
    public double getRealtimeValue(String pointname) {
        EDNAClient client = new EDNAClient();
        double value = 0;
        try{
            value = client.getEDNAApi().getRealtimeTagValue(ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(pointname)).DValue;
        }catch(Exception e){
            
        }
        
        return value; 
    }

    @Override
    public double getRealtimeValue(String pointname, int accuracy,
            double errorValue) {
        //TODO
        double value = getRealtimeValue(pointname);
        
        return CommonUtil.accurateValue(value, accuracy);
    }

    @Override
    public List<PointValueInfo> getRealtimeValues(List<String> pointnames,
            int accuracy) {
        EDNAClient client = new EDNAClient();
        List<PointValueInfo> list = new ArrayList<PointValueInfo>();
        try{
            String[] ps = new String[pointnames.size()];
            for(int i = 0; i < ps.length; i++){
                //ps[i] = ConfigUtil.getRtdbServerName()+ pointnames.get(i);
                //String tagName = ConfigUtil.getRtdbServerName()+pointnames.get(i);
                ps[i] = ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(pointnames.get(i));
                //System.out.println("longid:"+client.getEDNAApi().longidFromShortid(ConfigUtil.getRtdbServerName()+"AI00006"));
                //System.out.println("shortid:"+client.getEDNAApi().shortidFromLongid(tagName));
                //System.out.println(ps[i]);
                //System.out.println(pointnames.get(i));
                /*
                double value = getRealtimeValue(pointnames.get(i), accuracy, 0);
                PointValueInfo pv = new PointValueInfo();
                pv.setValue(value);
                pv.setTime(new Date());
                list.add(pv);
                */
            }
            
            
            Map<String, DNAVal> vals = client.getEDNAApi().getRealtimeTagValues(ps);
            //System.out.println("-----------");
            //System.out.println(pointnames.size());
            List<PointValueInfo> list_temp = new ArrayList<PointValueInfo>();
            for(Map.Entry<String,DNAVal> setEntry : vals.entrySet())
            {
                PointValueInfo pv = new PointValueInfo();
                try{
                    //System.out.println("实时" + setEntry.getKey() + " : " + setEntry.getValue().DValue + " - " + setEntry.getValue().Time + " - " + setEntry.getValue().Status);
                    //System.out.println(client.getEDNAApi().longidFromShortid(setEntry.getKey()));
                    pv.setPointName(setEntry.getKey());
                    pv.setValue(CommonUtil.accurateValue(setEntry.getValue().DValue, accuracy));
                    pv.setTime(new Date(setEntry.getValue().Time*1000L));//这里需要将UTC时间转换成timestamp
                    pv.setStatus(setEntry.getValue().Status);
                }catch(Exception e){
                    pv.setValue(-9999);
                    pv.setTime(new Date());
                }
                
                list_temp.add(pv);
            }
            
            
            for(String p:ps){
                boolean isHas = false;
                for(PointValueInfo pv:list_temp){
                    if(p.equals(pv.getPointName())){
                        list.add(pv);
                        isHas = true;
                    }
                }
                if(!isHas){
                    PointValueInfo pv = new PointValueInfo();
                    list.add(pv);
                }
            }
            
            //System.out.println(list.size());
            //System.out.println("-----------");
        }catch(Exception e){
            e.printStackTrace();
        }
        
        return list;
    }

    @Override
    public List<PointValueInfo> getPointValueInfoHistory(String pointName,
            Date start, Date end, int interval, long maxPointNumber,
            String historyType, int accuracy, double errorValue) {
        EDNAClient client = new EDNAClient();
        if(historyType == null || historyType.equals(""))historyType=ApiEntry.HIST_SNAP+"";
        Map<String, DNAVal> vals = client.getEDNAApi().getHistoryTagValues(ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(pointName),
                sdf.format(start), sdf.format(end), interval, Integer.valueOf(historyType));
        
        //System.out.println(vals.entrySet().size());
        List<PointValueInfo> list = new ArrayList<PointValueInfo>();
        try{
            //System.out.println("-----------");
            /*
            for(Map.Entry<String,DNAVal> setEntry : vals.entrySet())
            {
                PointValueInfo pv = new PointValueInfo();
                try{
                    System.out.println("历史" + setEntry.getKey() + " : " + setEntry.getValue().DValue + " - " + setEntry.getValue().Time + 
                            " - " + setEntry.getValue().Status);
                    pv.setPointName(setEntry.getKey());
                    pv.setValue(CommonUtil.accurateValue(setEntry.getValue().DValue, accuracy));
                    pv.setTime(sdf.parse(setEntry.getKey()));
                }catch(Exception e){
                    pv.setValue(0);
                }
                
                list.add(pv);
            }
            */
            
            long[] keys = new long[vals.size()];
            int i = 0;
            for(Map.Entry<String,DNAVal> setEntry : vals.entrySet())
            {
                //System.out.println("历史" + setEntry.getKey() + " : " + setEntry.getValue().DValue + " - " + setEntry.getValue().Time + 
                //            " - " + setEntry.getValue().Status);
                keys[i++] = sdf.parse(setEntry.getKey()).getTime();
            }
            
            QuickSort.quickSort(keys);
            for(long k:keys){
                String dk = sdf.format(new Date(k));//这里需要将UTC时间转换成timestamp
                //System.out.println(dk);
                DNAVal dna = vals.get(dk);
                //System.out.println("历史" + dk + " : " + dna.DValue + " - " + dna.Time + 
                //        " - " + dna.Status);
                
                PointValueInfo pv = new PointValueInfo();
                try{
                    //System.out.println("历史" + setEntry.getKey() + " : " + setEntry.getValue().DValue + " - " + setEntry.getValue().Time + 
                    //        " - " + setEntry.getValue().Status);
                    pv.setPointName(dk);
                    pv.setValue(CommonUtil.accurateValue(dna.DValue, accuracy));
                    pv.setTime(sdf.parse(dk));
                    pv.setStatus(dna.Status);
                }catch(Exception e){
                    pv.setValue(0);
                }
                
                list.add(pv);
            }
            
        }catch(Exception e){
            e.printStackTrace();
        }
        //System.out.println("-----------");
        return list;
    }

    @Override
    public List<PointValueInfo> getPointValueInfoHistory(
            List<String> pointnames, Date start, Date end, int interval,
            long maxPointNumber, String historyType, int accuracy,
            double errorValue) {
        EDNAClient client = new EDNAClient();
        List<PointValueInfo> list = new ArrayList<PointValueInfo>();
        String[] ps = new String[pointnames.size()];
        for(int i = 0; i < ps.length; i++){
            //ps[i] = ConfigUtil.getRtdbServerName() +pointnames.get(i);
            ps[i] = ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(pointnames.get(i));
        }
        
        if(historyType == null || historyType.equals(""))historyType=ApiEntry.HIST_SNAP+"";
        Map<String, DNAVal> vals = client.getEDNAApi().getHistoryMatrixValues(sdf.format(start), sdf.format(end), Integer.valueOf(historyType), ps);
        //System.out.println("-----------");
        //System.out.println(pointnames.size());
        List<PointValueInfo> list_temp = new ArrayList<PointValueInfo>();
        for(Map.Entry<String,DNAVal> setEntry : vals.entrySet())
        {
            PointValueInfo pv = new PointValueInfo();
            try{
                //System.out.println("历史" +setEntry.getKey() + " : " + setEntry.getValue().DValue + " - " + setEntry.getValue().Time + " - " + setEntry.getValue().Status);
                pv.setPointName(setEntry.getKey());
                pv.setValue(CommonUtil.accurateValue(setEntry.getValue().DValue, accuracy));
                pv.setTime(new Date(setEntry.getValue().Time * 1000L));//这里需要将UTC时间转换成timestamp
                pv.setStatus(setEntry.getValue().Status);
            }catch(Exception e){
                pv.setValue(0);
                pv.setTime(new Date());
                e.printStackTrace();
            }
            
            list_temp.add(pv);
        }
        

        for(String p:ps){
            boolean isHas = false;
            for(PointValueInfo pv:list_temp){
                if(p.equals(pv.getPointName())){
                    list.add(pv);
                    isHas = true;
                }
            }
            if(!isHas){
                PointValueInfo pv = new PointValueInfo();
                pv.setValue(0);
                pv.setTime(new Date());
                list.add(pv);
            }
        }
        //System.out.println(list.size());
        //System.out.println("-----------");
        return list;
    }

    @Override
    public boolean updatePoint(String pointName, long time,  double value) {
        boolean flag = false;
        try {
            //pointName = ConfigUtil.getRtdbServerName()+pointName;//加上前缀
            pointName = ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(pointName);
            String timeStr = new DateTime(time).toString("yyyy-MM-dd HH:mm:ss");
            EDNAClient client = new EDNAClient();
            //删除
            //client.getEDNAApi().deletePointRec(pointName, timeStr, timeStr);
            
            //【注意】eDNA删除和插入操作之间要让它休息一会儿,不然会处理不过来导致不能成功
            Thread.sleep(100);
            
            //插入测点数据
            DNAVal[] vals = new DNAVal[1];
            DNAVal val = new DNAVal();
            val.DValue = value;////val.Time = (int)(time/1000);//时间戳(UTC)
            val.Time = client.getEDNAApi().parseDateStringToUTC(timeStr);//时间字符串转UTC时间
            val.Status = 3;//状态
            vals[0] = val;
            
            client.getEDNAApi().updateVals(pointName, vals);
            flag = true;
        }catch(Exception e){
            e.printStackTrace();
            
        }
        return flag;
    }

    @Override
    public boolean updatePoints(String[] pointNames, long time, double[] values) {
        boolean flag = false;
        try {
            EDNAClient client = new EDNAClient();
            String timeStr = new DateTime(time).toString("yyyy-MM-dd HH:mm:ss");
            int utcTime = client.getEDNAApi().parseDateStringToUTC(timeStr);//时间字符串转UTC时间
            
            //封装参数值
            for(int i = 0; i < pointNames.length; i++){
                String p = pointNames[i];
                //pointNames[i] = ConfigUtil.getRtdbServerName()+p;//加上前缀
                pointNames[i] = ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(p);
                //删除
                //client.getEDNAApi().deletePointRec(pointNames[i], utcTime, utcTime);
                //【注意】eDNA和操作之间要让它休息一会儿,不然会处理不过来导致不能成功
                //Thread.sleep(100);
            }
            
            //【注意】eDNA删除和插入操作之间要让它休息一会儿,不然会处理不过来导致不能成功
            Thread.sleep(100);
            
            //插入测点数据
            for(int i = 0; i < pointNames.length; i++){
                String p = pointNames[i];
                DNAVal val = new DNAVal();
                val.DValue = values[i];////val.Time = (int)(time/1000);//时间戳(UTC)
                val.Time = client.getEDNAApi().parseDateStringToUTC(timeStr);//时间字符串转UTC时间
                val.Status = 3;//状态
                
                client.getEDNAApi().updateVals(p, val);
                //【注意】eDNA和操作之间要让它休息一会儿,不然会处理不过来导致不能成功
                Thread.sleep(100);
            }
            
            flag = true;
        }catch(Exception e){
            e.printStackTrace();
            
        }
        return flag;
    }

    @Override
    public boolean updatePoints(String pointName, long[] times, double[] values) {
        boolean flag = false;
        try {
            //pointName = ConfigUtil.getRtdbServerName()+pointName;//加上前缀
            pointName = ConfigUtil.getRtdbServerName()+EDNAClient.yingshe.get(pointName);
            EDNAClient client = new EDNAClient();
            
            DNAVal[] vals = new DNAVal[times.length];
            for(int i = 0; i < times.length; i++){
                long time = times[i];
                String timeStr = new DateTime(time).toString("yyyy-MM-dd HH:mm:ss");
                int utcTime = client.getEDNAApi().parseDateStringToUTC(timeStr);//时间字符串转UTC时间
                
                DNAVal val = new DNAVal();
                val.DValue = values[i];////val.Time = (int)(time/1000);//时间戳(UTC)
                val.Time = client.getEDNAApi().parseDateStringToUTC(timeStr);//时间字符串转UTC时间
                val.Status = 3;//状态
                
                //删除
                //client.getEDNAApi().deletePointRec(pointName, utcTime, utcTime);
                //【注意】eDNA和操作之间要让它休息一会儿,不然会处理不过来导致不能成功
                //Thread.sleep(100);
                
                vals[i] = val;
            }
            
            //【注意】eDNA删除和插入操作之间要让它休息一会儿,不然会处理不过来导致不能成功
            Thread.sleep(100);
            
            //插入测点数据
            client.getEDNAApi().updateVals(pointName, vals);
            flag = true;
        }catch(Exception e){
            e.printStackTrace();
            
        }
        return flag;
    }
    
    

}

这样工厂-数据库服务接口-数据库服务实现的结构就建好了,以后如果需要接入新的数据库,只需要编写新的实现类,然后在配置文件中配置,上层应用就可以使用这个数据库了,不需要更新上层的代码。

 

另外,补充一下工厂模式和适配器模式的区别,看上去工厂模式和适配器模式都能运用在一个接口、多个实现灵活调用的场景中。但是工厂模式是创建类的方法;适配器模式是重新封装类的方法,是基于原有系统中已有的类来做的,所以两者在应用场景上有很大的不同。

posted @ 2016-12-22 10:54  fleeyone  阅读(1489)  评论(0)    收藏  举报