欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一个ASP创建动态对象的工厂类(类似PHP的stdClass)

程序员文章站 2022-08-10 18:02:57
最近整理asp/vbscript代码,发现过去的一个asp实现的mvc框架,可惜是个半成品,效率也成问题,不过发现里面有些我写的代码,感觉还稍稍可以拿出来见人,于是今天作此...

最近整理asp/vbscript代码,发现过去的一个asp实现的mvc框架,可惜是个半成品,效率也成问题,不过发现里面有些我写的代码,感觉还稍稍可以拿出来见人,于是今天作此文以记之。

说是asp,其实和vbscript也脱不了干系,vbscript语言传承于visual basic,vb的语法灵活度已经不尽如人意了,vbs作为其子集可想而知。神马反射、自省等先进的技术,微软在.net中才引入。作为被抛弃的技术,也不奢望微软能够提供支持,于是顽固守旧的程序员只有绞尽脑汁的去模仿实现一些类似的功能。

好吧,我承认很长一段时间我就是顽固守旧派中的一员,今天介绍的就是其中的一项功能,动态创建一个属性对象,属性对象姑且这么称呼,也就是说动态创建的对象只包含属性(properties)。


下面贴出实现代码供大家参考:

复制代码 代码如下:

'
' asp/vbscript dynamic object generator
' author: wangye
' for more information please visit
'    
' this code is distributed under the bsd license
'
const property_access_readonly = 1
const property_access_writeonly = -1
const property_access_all = 0

class dynamicobject
    private m_objproperties
    private m_strname

    private sub class_initialize()
        set m_objproperties = createobject("scripting.dictionary")
        m_strname = "anonymousobject"
    end sub

    private sub class_terminate()
        if not isobject(m_objproperties) then
            m_objproperties.removeall
        end if
        set m_objproperties = nothing
    end sub

    public sub setclassname(strname)
        m_strname = strname
    end sub

    public sub add(key, value, access)
        m_objproperties.add key, array(value, access)
    end sub

    public sub setvalue(key, value, access)
        if m_objproperties.exists(key) then
            m_objproperties.item(key)(0) = value
            m_objproperties.item(key)(1) = access
        else
            add key,value,access
        end if
    end sub

    private function getreadonlycode(strkey)
        dim strprivatename, strpublicgetname
        strprivatename = "m_var" & strkey
        strpublicgetname = "get" & strkey
        getreadonlycode = _
            "public function " & strpublicgetname & "() :" & _
            strpublicgetname & "=" & strprivatename & " : " & _
            "end function : public property get " & strkey & _
            " : " & strkey & "=" & strprivatename & " : end property : "
    end function

    private function getwriteonlycode(strkey)
        dim pstr
        dim strprivatename, strpublicsetname, strparamname
        strprivatename = "m_var" & strkey
        strpublicsetname = "set" & strkey
        strparamname = "param" & strkey
        getwriteonlycode = _
            "public sub " & strpublicsetname & "(" & strparamname & ") :" & _
            strprivatename & "=" & strparamname & " : " & _
            "end sub : public property let " & strkey & "(" & strparamname & ")" & _
            " : " & strprivatename & "=" & strparamname & " : end property : "
    end function

    private function parse()
        dim i, keys, items
        keys = m_objproperties.keys
        items = m_objproperties.items

        dim init, pstr
        init = ""
        pstr = ""
        parse = "class " & m_strname & " :" & _
                "private sub class_initialize() : "

        dim strprivatename
        for i = 0 to m_objproperties.count - 1
            strprivatename = "m_var" & keys(i)
            init = init & strprivatename & "=""" & _
                replace(cstr(items(i)(0)), """", """""") & """:"
            pstr = pstr & "private " & strprivatename & " : "
            if cint(items(i)(1)) > 0 then ' readonly
                pstr = pstr & getreadonlycode(keys(i))
            elseif cint(items(i)(1)) < 0 then ' writeonly
                pstr = pstr & getwriteonlycode(keys(i))
            else ' accessall
                pstr = pstr & getreadonlycode(keys(i)) & _
                        getwriteonlycode(keys(i))
            end if
        next
        parse = parse & init & "end sub : " &  pstr & "end class"
    end function

    public function getobject()
        call execute(parse)
        set getobject = eval("new " & m_strname)
    end function

    public sub invokeobject(byref obj)
        call execute(parse)
        set obj = eval("new " & m_strname)
    end sub
end class

对于属性对象分别提供了property直接访问模式和set或者get函数访问模式,当然我还提供了三种权限控制,在add方法中使用,分别是property_access_readonly(属性只读)、property_access_writeonly(属性只写)和property_access_all(属性读写),你可以像下面这样使用(一个例子):

复制代码 代码如下:

dim dynobj
set dynobj = new dynamicobject
    dynobj.add "name", "wangye", property_access_readonly
    dynobj.add "homepage", "http://jb51.net", property_access_readonly
    dynobj.add "job", "programmer", property_access_all
    '
    ' 如果没有setclassname,
    ' 新创建的对象将会自动命名为anonymousobject
    ' 但是如果创建多个对象,就必须指定名称
    ' 否则就可能引起对象名重复的异常
    dynobj.setclassname "user"

    dim user
    set user = dynobj.getobject()
    ' 或者 dynobj.invokeobject user
        response.write user.name
        ' response.write user.getname()
 response.write user.homepage
        ' response.write user.gethomepage()
 response.write user.job
        ' response.write user.getjob()

        ' 改变属性值
        user.job = "engineer"
        ' user.setjob "engineer"

        response.write user.getjob()
    set user = nothing

set dynobj = nothing


其原理很简单,就是通过给定的key-value动态生成vbs class脚本代码,然后调用execute执行以便于将这段代码加入到代码上下文流中,最后再通过eval新建这个对象。

好了,就介绍到这里,今后我可能还会陆续公开一些classic asp的相关技巧代码。

2012年11月7日更新

修复从旧项目移植过来导致的bug。

修复了一些bug增加了一些特性,我先把最新的代码贴出来供大家参考:

复制代码 代码如下:
'
' asp/vbscript dynamic object generator
' author: wangye
' for more information please visit
'    
' this code is distributed under the bsd license
'
' update:
'   2012/11/7
'       1. add variable key validator.
'       2. add hasattr_ property for determine
'          if the property exists.
'       3. add getattr_ property for get property
'          value safety.
'       4. class name can be accessed by classname_ property.
'       5. fixed some issues.
'
const property_access_readonly = 1
const property_access_writeonly = -1
const property_access_all = 0

class dynamicobject
    private m_objproperties
    private m_strname
    private m_objregexp

    private sub class_initialize()
        set m_objproperties = createobject("scripting.dictionary")
        set m_objregexp = new regexp
            m_objregexp.ignorecase = true
            m_objregexp.global = false
            m_objregexp.pattern = "^[a-z][a-z0-9]*$"
        m_strname = "anonymousobject"
        m_objproperties.add "classname_", _
            array(m_strname, property_access_readonly)
    end sub

    private sub class_terminate()
        set m_objregexp = nothing
        if isobject(m_objproperties) then
            m_objproperties.removeall
        end if
        set m_objproperties = nothing
    end sub

    public sub setclassname(strname)
        if not m_objregexp.test(strname) then
            ' skipped invalid class name
            ' raise
            exit sub
        end if
        m_strname = strname
        m_objproperties("classname_") = _
            array(m_strname, property_access_readonly)
    end sub

    public sub add(key, value, access)
        if not m_objregexp.test(key) then
            ' skipped invalid key
            ' raise
            exit sub
        end if
        if key = "hasattr_" then key = "hasattr__"
        if key = "classname_" then key = "classname__"
        'response.write key
        m_objproperties.add key, array(value, access)
    end sub

    public sub setvalue(key, value, access)
        if m_objproperties.exists(key) then
            m_objproperties.item(key)(0) = value
            m_objproperties.item(key)(1) = access
        else
            add key,value,access
        end if
    end sub

    private function getreadonlycode(strkey)
        dim strprivatename, strpublicgetname
        strprivatename = "m_var" & strkey
        strpublicgetname = "get" & strkey
        getreadonlycode = _
            "public function " & strpublicgetname & "() :" & _
            strpublicgetname & "=" & strprivatename & " : " & _
            "end function : public property get " & strkey & _
            " : " & strkey & "=" & strprivatename & _
            " : end property : "
    end function

    private function getwriteonlycode(strkey)
        dim pstr
        dim strprivatename, strpublicsetname, strparamname
        strprivatename = "m_var" & strkey
        strpublicsetname = "set" & strkey
        strparamname = "param" & strkey
        getwriteonlycode = _
            "public sub " & strpublicsetname & _
            "(" & strparamname & ") :" & _
            strprivatename & "=" & strparamname & " : " & _
            "end sub : public property let " & strkey & _
            "(" & strparamname & ")" & _
            " : " & strprivatename & "=" & strparamname & _
            " : end property : "
    end function

    private function parse()
        dim i, keys, items
        keys = m_objproperties.keys
        items = m_objproperties.items

        dim init, pstr
        init = ""
        pstr = ""
        parse = "class " & m_strname & " :" & _
                "private sub class_initialize() : "

        dim strprivatename, stravailablekeys

        for i = 0 to m_objproperties.count - 1
            strprivatename = "m_var" & keys(i)
            init = init & strprivatename & "=""" & _
                replace(cstr(items(i)(0)), """", """""") & """:"
            pstr = pstr & "private " & strprivatename & " : "
            stravailablekeys = stravailablekeys & keys(i) & ","
            if cint(items(i)(1)) > 0 then ' readonly
                pstr = pstr & getreadonlycode(keys(i))
            elseif cint(items(i)(1)) < 0 then ' writeonly
                pstr = pstr & getwriteonlycode(keys(i))
            else ' accessall
                pstr = pstr & getreadonlycode(keys(i)) & _
                        getwriteonlycode(keys(i))
            end if
        next

        init = init & "m_stravailablekeys = replace(""," & _
                stravailablekeys & """, "" "", """") : "
        dim hasstmt
        hasstmt = "private m_stravailablekeys : " & _
                  "public function hasattr_(byval key) : " & _
                  "hasattr_ = cbool(instr(m_stravailablekeys," & _
                  " "","" & key & "","") > 0) : " & _
                  "end function : " & _
                  "public function getattr_(byval key, byval defaultvalue) : " & _
                  "if hasattr_(key) then : getattr_ = eval(key) : " & _
                  "else : getattr_ = defaultvalue : end if : " & _
                  "end function : "

        parse = parse & init & "end sub : " & _
            hasstmt & pstr & "end class"
    end function

    public function getobject()
        'response.write parse
        call execute(parse)
        set getobject = eval("new " & m_strname)
    end function

    public sub invokeobject(byref obj)
        call execute(parse)
        set obj = eval("new " & m_strname)
    end sub
end class


需要注意的几个新特性:

1. 增加了类名和属性名验证措施,防止畸形的类名或者属性名导致动态生成的代码出现语法错误。不过处理的方式是直接忽略,本来想raise异常的,但考虑到vbs对异常处理不是很好的,所以采取忽略策略:

' 有效的类名或属性名必须以字母开头

复制代码 代码如下:
dim dynobj
set dynobj = new dynamicobject
    dynobj.setclassname "1user" ' 此句将被忽略,因为类名不能以数字开始
    ' 下面这句也会被忽略,因为属性名不能以特殊符号开始
    dynobj.add "%name", "wangye", property_access_readonly
set dynobj = nothing

2. 对于动态对象增加了hasattr_方法,该属性用于检测此对象是否支持相应的属性,可以在访问一个属性前先确定该对象是否支持此属性:
复制代码 代码如下:

dim dynobj
set dynobj = new dynamicobject
    dynobj.add "name", "wangye", property_access_readonly

    response.write dynobj.hasattr_("name") ' true
    response.write dynobj.hasattr_("favor") ' false

set dynobj = nothing

3. 对于动态对象增加了getattr_方法,此方法可以安全的获取指定的属性值,避免因为对象不存在属性值导致出错。方法原型为getattr_(byval propertyname, byval defaultvalue),参数propertyname指定属性的名字,defaultvalue是当指定属性不存在是可以返回的默认值,比如下面代码:

复制代码 代码如下:

dim dynobj
set dynobj = new dynamicobject
    dynobj.add "name", "wangye", property_access_readonly

    response.write dynobj.getattr_("name", "n/a") ' wangye
    response.write dynobj.getattr_("favor", "n/a") ' n/a

set dynobj = nothing


4. 动态对象的类名可以通过classname_属性或者getclassname_()方法获取。

2012年11月12日更新

修复双引号导致构造类错误或导致执行任意代码的bug。