【问题标题】:Proxy pattern idiom代理模式成语
【发布时间】:2011-04-26 13:44:56
【问题描述】:

我是一名 Web 应用程序开发人员,在使用 SQLAlchemy 时,当我想要来自(例如)users 表的特定 行 时,我发现在我的许多控制器中执行此操作很笨拙:

from model import dbsession # SQLAlchemy SessionMaker instance
from model import User

user = dbsession().query(User).filter_by(some_kw_args).first()

或者说我想向表中添加一个用户(假设另一个控制器):

from model import dbsession # SQLAlchemy SessionMaker instance
from model import User

user = User("someval", "anotherval", "yanv")
dbsession().add(user)

因此,由于这种笨拙(我不会介绍我的其他一些个人习语),我不喜欢仅仅为了向表中添加一条记录或从表中获取一条记录而进行所有这些操作.所以我决定(在对 SQLAlchemy 进行了很多讨厌的黑客攻击并决定我做了太多“神奇”的事情之后)这适合代理模式。

我(起初)在model 模块中做了类似的事情:

def proxy_user(delete=False, *args, **kwargs):
    session = DBSession()

    # Keyword args? Let's instantiate it...
    if (len(kwargs) > 0) and delete:
        obj = session.query(User).filter_by(**kwargs).first()
        session.delete(obj)

        return True
    elif len(kwargs) > 0:
        kwargs.update({'removed' : False})
        return session.query(User).filter_by(**kwargs).first()
    else:
        # Otherwise, let's create an empty one and add it to the session...
        obj = User()
        session.add(obj)
        return obj

我为我的所有模型都做了这个(我知道代码重复很糟糕),而且效果很好。我可以将关键字参数传递给代理函数,它会为我处理所有会话查询(甚至为删除的标志提供默认过滤关键字)。我可以初始化一个空模型对象,然后通过更新对象属性向其中添加数据,并且所有这些更改都被跟踪(并提交/刷新),因为该对象已添加到 SQLAlchemy 会话中。

所以,为了减少重复,我将大部分逻辑放在装饰器函数中,现在正在这样做:

def proxy_model(proxy):
    """Decorator for the proxy_model pattern."""

    def wrapper(delete=False, *args, **kwargs):

        model   = proxy()

        session = DBSession()

        # Keyword args? Let's instantiate it...
        if (len(kwargs) > 0) and delete:
            obj = session.query(model).filter_by(**kwargs).first()
            session.delete(obj)

            return True
        elif len(kwargs) > 0:
            kwargs.update({'removed' : False})
            return session.query(model).filter_by(**kwargs).first()
        else:
            # Otherwise, let's create an empty one and add it to the session...
            obj = model()
            session.add(obj)
            return obj

    return wrapper

# The proxy_model decorator is then used like so:
@proxy_model
def proxy_user(): return User

所以现在,在我的控制器中我可以这样做:

from model import proxy_user

# Fetch a user
user = proxy_user(email="someemail@ex.net") # Returns a user model filtered by that email

# Creating a new user, ZopeTransaction will handle the commit so I don't do it manually
new_user          = proxy_user()
new_user.email    = 'anotheremail@ex.net'
new_user.password = 'just an example'

如果我需要做其他更复杂的查询,如果我经常使用它,我通常会编写处理它的函数。如果这是一次性的事情,我将只导入 dbsession 实例,然后执行“标准”SQLAlchemy orm 查询......

这更干净,效果也很好,但我仍然觉得它并没有完全“锁定”。其他任何人(或更有经验的 python 程序员)能否提供一个更好的习语,以实现与我所寻求的类似数量的清晰性,同时作为一个更清晰的抽象?

【问题讨论】:

    标签: python sqlalchemy abstraction idioms


    【解决方案1】:

    您提到“不喜欢做'所有这些'”,其中“所有这些”看起来很像只有 1 到 2 行代码,所以我觉得这并不是真正必要的。基本上,我真的不认为您开始的任何陈述都那么冗长或令人困惑。

    但是,如果我必须想出一种方式来表达这一点,我不会在这里使用装饰器,因为你并没有真正装饰任何东西。如果没有应用 imo 的装饰器,函数“proxy_user”实际上不会做任何事情。由于您需要以某种方式提供模型的名称,我认为您最好只使用一个函数并将模型类传递给它。我还认为将删除功能滚动到您的代理中是不合适的,并且根据您配置会话的方式,对 DBSession() 的重复调用可能会创建新的不相关会话,如果您需要使用这将导致问题使用同一事务中有多个对象。

    无论如何,下面是我如何将您的装饰器重构为一对函数的快速说明:

    def find_or_add(model, session, **kwargs):
        if len(kwargs) > 0:
             obj = session.query(model).filter_by(**kwargs).first()
             if not obj:
                 obj = model(**kwargs)
                 session.add(obj)
        else:
             # Otherwise, let's create an empty one and add it to the session...
             obj = model()
             session.add(obj)
        return obj
    
    def find_and_delete(model, session, **kwargs):
        deleted = False
        obj = session.query(model).filter_by(**kwargs).first()
        if obj:
            session.delete(obj)
            deleted = True
        return deleted
    

    同样,我不认为这是必要的,但我认为我可以同意:

    user = find_or_add(User, mysession, email="bob@localhost.com")
    

    可能比查找/创建用户并将其添加到会话所需的直接 SQLAlchemy 代码更好看。

    我更喜欢上面的函数而不是你当前的装饰器方法,因为:

    • 这些名称清楚地表明了您的意图,我觉得 proxy_user 并没有真正明确表示您想要一个用户对象(如果它存在),否则您想创建它。
    • 会话被显式管理
    • 他们不需要我将每个模型都包装在装饰器中
    • find_or_add 函数始终返回模型实例,而不是有时返回 True、查询结果集或模型实例。
    • find_and_delete 函数总是返回一个布尔值,表明它是否能够成功地找到并删除 kwargs 中指定的记录。

    当然,您可以考虑使用类装饰器将这些函数作为方法添加到模型类中,或者可能从包含此功能的基类派生模型,以便您可以执行以下操作:

    # let's add a classmethod to User or its base class:
    class User(...):
        ...
        @classmethod
        def find_or_add(cls, session, **kwargs):
            if len(kwargs) > 0:
                obj = session.query(cls).filter_by(**kwargs).first()
                if not obj:
                    obj = cls(**kwargs)
                    session.add(obj)
            else:
                # Otherwise, let's create an empty one and add it to the session...
                obj = cls()
                session.add(obj)
            return obj
        ...
    user = User.find_or_add(session, email="someone@tld.com")
    

    【讨论】:

    • 你的第一个方法是我一直倾向于自己的方法——我认为 proxy_model 装饰器真的没有必要,我可以有一个 proxy_model 函数来接受模型类和 **kwargs 作为参数......
    • 酷 :-) 我认为使用装饰器的方法是在每个模型上使用类装饰器,但我仍然认为我更喜欢单独的函数。
    • 我采用了单独的函数方法,并将模型类作为参数传入。它工作得非常好,抽象感觉“正确”。感谢您的确认输入!此外,我已经尝试了很多事情来以类似于上述的方式使用模型对象,但是 A)使用一些非常奇怪和低效的解决方法非常困难,B)这只是可怕的编程。这种方法非常适合用例。谢谢m8!
    • 很高兴我能帮上忙。快乐的黑客攻击!
    猜你喜欢
    • 1970-01-01
    • 2011-10-12
    • 2011-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    相关资源
    最近更新 更多