一起来学设计模式-----工厂模式的实践

工厂设计模式是入门模式,也是使用较多的模式,这一篇就总结下我在测试项目时,看到使用的地方以及编写测试桩时基于此模式的实际运用。

实例一:测试的c++项目——简单工厂+宏函数反射的使用

由于公司对业务和代码要求保密,在这是不能贴业务代码更不能直接给业务UML类图,所以在这我做了一个类似案例的举例。常测试的一个系统,是一个c++编写的后台系统。即是一个服务器端,又是一个客户端;系统在接收上层的业务请求后,根据实际请求组包发送请求给其他接口服务器。其他接口服务器里,以服务器为单位,都是提供2个接口,所以在封装报文的时候,就要根据具体接口服务器的接口内容进行封装,每个接口都是不一样的。

那问题来了,怎么封装报文呢?

在上层业务的请求里,字段:InterfaceServer(接口服务器),InterfaceType(接口类型)。比如上层业务请求:{“InterfaceServer”:1;“InterfaceType”:A},表示要调用接口服务器1的A接口,中间的服务器解析请求后,判断是接口服务器1的A接口,就构造该类数据报文;如果解析是接口服务器2的B接口,则构造该数据报文;无论是哪个接口服务器,都有2个相同的方法,就是调用A,和调用B接口,由此可以抽象一个Iserver接口类,里面有2个虚函数。每个继承接口基类的类Iserver_A,都必须实现这2个接口,编写接口特定的报文构造方法,并调用接口服务器。那工厂是什么样的呢?怎么获取接口服务器的实例呢?系统用了c++的宏函数完成了接口服务器的自动注册。在每个接口服务器Iserver_A的实现类中,调用这个注册的宏函数,把接口服务器,接口服务器字符串化后的两个参数都放到一个map中,然后在工厂方法里根据接口服务器就能获取实例。

这样的实现,程序在编译之后,就能完成所有子类的接口服务器的自动注册,因为每个子类接口服务器都调用了宏函数,把接口服务器的servertype和字符串化后的实例一起保存在了map<int, IServerInterface*> m_bankIterfaces中,程序在运行时,每次根据前端的业务请求的InterfaceServer,就能直接获取该类接口服务器的实例,再根据InterfaceType,调用具体业务内的接口,构造报文,再调用接口服务器即可。在这个情况下,如果需要增加一个接口服务器,只需要增加该类接口服务器的cpp文件,调用宏函数完成自动注册,实现自己的业务,就能在工厂类被调用,无须修改工厂类。实使用工厂模式+宏反射完成这类设计。

实例二:测试桩python项目----简单工厂+DB配置

今年做的第一件事就是测试桩的编写,这个测试桩就是模拟实例一的接口服务器(实例一接口服务器的测试桩),因为接口服务器是别的公司提供的,往往搭建环境上就会耗时很久,所以就要编写一个测试桩,在之前就熟悉了组包过程的设计,测试桩在解析包的时候肯定也能使用相同的设计模式,但是python是没有宏映射机制的,那这怎么做呢?

对于测试桩,接收的全都是报文,测试桩本身是不知道报文是属于哪个接口服务器的哪个接口的,但是人知道,人可以根据报文内容来识别。于是最开始可以在db配置一张表,里面有每个报文的关键字段(能保证独一无二),测试桩收到请求后,都会在请求中寻找一遍所有的关键字,找到关键字后,返回对应的servertype(int型数据),然后在工厂模式内,能根据servertype得到对应接口服务器的实例,接着再把实际请求转发到对应接口的实际业务层处理A接口和B接口即可。那怎么得到这个实例呢?c++没有宏。于是想了好几个方法:

方法一:使用python的全局变量

定义一个python的全局变量,全局变量的定义最好是单独放在一个文件内,切忌不要把全局变量的定义和可执行文件(main)放在一起,这样一定会有问题,这个问题我也遇到了,感兴趣的可以参考下  XXXXX。然后每个使用到这个全局变量的地方,在文件尾部定义好server类名和servertype的对应值即可。业务模块包含以下:

其中,IServer是基于业务的一层抽象,IServer_A和IServer_B是具体的业务。代码如下:

IServer.py

from abc import ABCMeta, abstractmethod
class IServer:
    @abstractmethod
    def DoWithA(self):
        pass

    @abstractmethod
    def DoWithB(self):
        pass

IServer_A.py

from IServer import *
from ServerRegister import GLOBAL_class_dic
serverType =‘1001‘

class IServer_A(IServer):
    def DoWithA(self):
        print ‘Server_A do with interface A‘

    def DoWithB(self):
        print ‘Server_A do with interface B‘

GLOBAL_class_dic[serverType] = IServer_A

IServer_B.py

from IServer import *from ServerRegister import GLOBAL_class_dicserverType =‘1002‘

class IServer_B(IServer):    def DoWithA(self):        print ‘Server_B do with interface A‘

def DoWithB(self):        print ‘Server_B do with interface B‘

GLOBAL_class_dic[serverType] = IServer_B

子类IServer_A在使用全局变量时,只需要从全局变量文件内import全局变量,然后在代码的最后写入即可。在代码最后的写入,IServer_B不再是一个字符串,已经是一个实例化的类对象。再来看看全局变量模块是怎么定于和获取子类的全局变量的值的

ServerRegister.py

import threading
import os
from  misc import Misc

global GLOBAL_class_dic
GLOBAL_class_dic ={}
print ‘GLOBAL_class_dic in define is:‘, GLOBAL_class_dic
print ‘the id of GLOBAL_class_dic in define is:‘, id(GLOBAL_class_dic)

def getBankALIAS():
    path1 = os.path.join(os.getcwd())
    for fpath in Misc.sys_list_files(path1):
        Misc.sys_import_module(fpath)

GLOBAL_timer_getBankALIAS = threading.Timer(1, getBankALIAS())

全局变量文件,里面就是全局变量的定义和全局变量值在不同子类业务文件内的值的获取。然后简单的使用threading模块的Timer,对子业务文件进行import,子业务文件又对全局变量进行了增加,python的dictionary数据类型又是一种可变数据类型,所以全局变量的dict值能正确的填入。然后在main函数所在的可执行文件中,直接使用全局变量即可。

CreatFactory.py

#coding:UTF-8
print __name__
from ServerRegister import *

def CreateServer(serverType):
    if GLOBAL_class_dic.has_key(serverType):
        return GLOBAL_class_dic[serverType]
    else:
        return ‘no‘

if __name__ == ‘__main__‘:
    # 接收到报文后,根据报文的内容,从db中获取到serverType,假设获取到的serverType=1001
    print ‘main‘
    print ‘the id of GLOBAL_class_dic in MAIN is:‘, id(GLOBAL_class_dic)
    serverType = ‘1001‘
    server = CreateServer(serverType)
    server.DoWithA(server())

实际运行结果如下:

这种思路,主要还是基于以前对c++代码的理解写的,实际使用全局变量,一不注意就会出现很多问题。之前我有遇到的问题是,全局变量定义和使用放在一个文件中,程序在运行的过程中,加载了两次全局变量(两个内存地址),第二次加载的全局变量和子业务使用的是同一个,第一次加载的全局变量和main函数使用的是同一个,这导致在使用全局变量时,全局变量里的值还是空的。详细原因分析,可以参考:xxxxx

方法二:子业务定义相同变量,程序扫描指定路径,获取文件内的变量和方法,找到该变量后,保存起来,然后创建相应的对象即可。这是项目内常使用的方法,不容易出错。代码结构如下:

IServer.py

from abc import ABCMeta, abstractmethod
class IServer:
    @abstractmethod
    def DoWithA(self):
        pass

    @abstractmethod
    def DoWithB(self):
        pass

IServer_A.py

import IServer

ALIAS = {‘1001‘:‘IServer_A‘};

class IServer_A(IServer.IServer):    def __init__(self):        pass

def DoWithA(self):        print ‘Server_A do with interface A‘

def DoWithB(self):        print ‘Server_A do with interface B‘

IServer_B.py

import IServerALIAS = {‘1002‘:‘IServer_B‘};

class IServer_B(IServer.IServer):    def __init__(self):        pass

def DoWithA(self):        print ‘Server_B do with interface A‘

def DoWithB(self):        print ‘Server_B do with interface B‘

子类IServer_A跟方法一不同的地方在于,变量ALIAS 可以定义在文件任何位置,IServer_B必须与类名一致,因为代码后期会为IServer_A创建对象。

server_framework.py

import os
import sys
from Server_02.Baselib.misc import Misc;

class StubFrameWork(object):
    def __init__(self):
        self._server_alias={}
        self._server_dt={}

    def initialize(self):
        self._append_alias_of(os.path.join(SRCPATH, ‘Server‘),self._server_alias)
        print self._server_alias
        self._init_server();
        print self._server_dt

    def _init_server(self,):
        for servertype,server_name in self._server_alias.items():
            obj = Misc.sys_create_object(server_name, server_name);
            if obj is None:
                raise Exception(‘none‘ % server_name);
            else:
                self._server_dt[servertype] = obj;

    def _append_alias_of(self, folder,dt):
        for fpath in Misc.sys_list_files(folder):
            if fpath.find(‘.pyc‘) > 0 or fpath.find(‘__init__.py‘) > 0:
                continue;
            module_name = Misc.sys_import_module(fpath);
            module_obj = sys.modules[module_name];
            if ‘ALIAS‘ in dir(module_obj):
                alias_dt = getattr(module_obj, ‘ALIAS‘);
                for key in alias_dt.keys():
                    dt[key] = alias_dt[key]
            else:
                pass

    def CreateServer(self,serverType):
        if self._server_dt.has_key(serverType):
            return self._server_dt[serverType]
        else:
            return ‘no‘

if __name__ == ‘__main__‘:
    mfw_path = os.path.join(os.getcwd(), __file__)
    SRCPATH = os.path.dirname(mfw_path);
    sfw= StubFrameWork()
    sfw.initialize()
    serverType = ‘1001‘
    server = sfw.CreateServer(serverType)
    server.DoWithA()

可执行脚本扫描server下的文件后,遍历每个文件下的变量和方法,获取ALIAS变量的值,并保存在框架对象的成员变量中,然后变量该成员变量,将str实例化成相应对象保存起来即可。

通过以上两个实例的练习,对软件设计的开放--关闭原则又有了更深一层的体会,对于扩展是开放的,对于修改是关闭的。绝对的修改关闭是不可能的,无论模块多么的封闭,都会存在一些无法对之封闭的变化。既然是不可能完全封闭,所以就必须对设计的模块应该对哪种变化封闭做出选择,必须先猜测出可能发送的变化种类,然后构造抽象来隔离这些变化。

学习设计模式,绝对是测试人员进阶的必修之课,学习不是目的,学习了而且会灵活的应用,能举一反三,对测试项目的代码看得更深入,编写的测试工具越严谨化,模块化,工具越用越方便才是最终目的。工厂方法的学习大概就花了我快一个月的时间了。平常工作日,是完全没有时间去学习的(饿厂的项目测试力度真的超级重),只能在周末在家把一大半的休息时间花在学习上,才能勉强的赶赶学习进度。但是贵在坚持和不断的思考,还是会有收获的快感。

				
时间: 2024-11-10 14:49:09

一起来学设计模式-----工厂模式的实践的相关文章

6.大话设计模式-工厂模式

工厂模式和简单工厂有什么区别.废话不多说,对比第一篇例子应该很清楚能看出来. 优点: 工厂模式弥补了简单工厂模式中违背开放-封闭原则,又保持了封装对象创建过程的优点. using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace DesignModel{    public interface Factory   

设计模式---工厂模式---生产水果

设计模式---工厂模式 需要一个基本的抽象类:相当一个基本的工厂 需要若干个具体类:相当若干个产品 需要工具类:相当工厂中机器,用来选择不同的产品生产 需要主类:相当顾客所想的订单 主类 ----> 工厂类 ----> 产品类 ---> 产品具体类 老板根据顾客的订单,在工具堆中选择合适的,然后去工厂中生产适合的产品,返回给顾客 优势:能够在不修改原先的代码情况,增加所需的类与方法. 不足:会增多类文件的数量 接口:提供的内容,相当于协议,即外界使用此接口时,接口的内容是不允许外界对接口

设计模式——工厂模式学习

1.概念 工厂模式定义:实例化对象,用工厂方法代替new操作.达到解耦的目的(避免硬编码耦合).工厂方法模式是类的创建模式,又叫做虚拟构造子(Virtual Constructor)模式或者多态性工厂(Polymorphic Factory)模式. 工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见.因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例

二十三种设计模式——工厂模式

二十三种设计模式--工厂模式 简单工厂模式 简单工厂模式又称静态工厂方法(StaticFactory Method)模式,不属于23种模式之一. 简单工厂模式是工厂模式最简单使用的模式. 类图: 程序: #include <iostream> #include<string> using namespace std; class NationalFlag//父类 { public: NationalFlag(){} ~NationalFlag(){} virtual void di

设计模式-工厂模式[Factory]

先看下一个简单的实现: 1 package org.masque.designpatterns.factorymethod.one; 2 /** 3 * 4 * Description: Sample子类的标示 5 * BeanEm.java Create on 2014年7月11日 下午2:37:58 6 * @author [email protected] 7 * @version 1.0 8 * Copyright (c) 2014 Company,Inc. All Rights Res

5分钟读书笔记之 - 设计模式 - 工厂模式

一个类或者对象中,往往会包含别的对象.在创建这种对象的时候,你可能习惯于使用常规方式,即用 new 关键字和类构造函数. 这会导致相关的俩个类之间产生依赖. 工厂模式,就是消除这俩个类之间的依赖性的一种模式,它使用一种方法来决定究竟实例化那个具体的类. 简单工厂模式 假设你想开几个自行车商店,每个商店都有几种型号的自行车出售,可以用这样一个类来表示: var BicycleShop = function(){} BicycleShop.prototype = { sellBicycle:func

小菜学设计模式——工厂方法模式

背景 简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关类,对于客户端来说,去除了与具体产品的依赖.如果,项目需要扩展,新增一种产品需要简单工厂模式生产,那么工厂内部必须重写修改必要的逻辑判断,这对于面向接口编码是非常不愿意看到的,因为他违背了面向对象设计原则:开放封闭原则. 那么如何我们才能解决这个问题呢?工厂方法模式的产生应该也是简单工厂模式的改进. 1.使用意图 工厂方法模式实现时,客户端需要决定实例化化哪一个工厂方法来生产产品,换句话说,工厂方法模

java设计模式--工厂模式

总结 (1)简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的. (2)工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成. (3)抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类.它针对的是有多个产品的等级结构.而工厂方法模式针对的是一个产品的等级结构. 一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java

设计模式-工厂模式

一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java与模式>中分为三类:1)简单工厂模式(Simple Factory):不利于产生系列产品: 2)工厂方法模式(Factory Method):又称为多形性工厂: 3)抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品:             这三种模式从上到下逐步抽象,并且更具一般性.             GOF在<