设计模式:命令模式 & 项目案例

命令模式是行为模式的一种

“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将**“行为请求者”与“行为实现者”**解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式(Command Pattern)
在这里插入图片描述
创建型模式:聚焦如何创建对象

结构型模式:聚焦在类之间如何结合

行为型模式:聚焦在方法上如何结合完成功能

命令模式应该有一下几个角色:

Command:

定义命令的接口,声明执行的方法,可以理解为一个基类。

ConcreteCommand:

命令接口实现对象,通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。

Receiver:

接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

Invoker:

要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象,相当于使用命令对象的入口。

Client:

创建具体的命令对象,组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

项目案例

实现搜索口令,例如:淘宝搜索口令。不同搜索词可能会触发一系列动作。
具体需求:广告支持通过白名单方式进行测试,需考虑不同展示平台和渠道来支持测试,常见的是在app中、小程序、PC、M站这几种,需要添加各自设备号。(因为小程序删除了就更以前不一致了,同时PC、M站是通过cookie生成设备号,会过期,同时不同浏览器也不一样,导致运营配置设备号十分困难)
PC、M设备号中间件实现如下:

class ProcessDeviceMiddleware(object):
    """
    场景:给web页面PC、M站随机生成一个设备ID,用于用户设备的绑定,目前打点需要使用该device_id。

    优先级:请求上传web_distinct_id > cookie distinct_id

    工作原理:
    1.先从Cookie中获取设备号。
    2.生成一个新的device_id,绑定到GET请求的WEB_DISTINCT_ID_NAME变量上,使请求的view中能获取到对应的设备ID
    3.执行完请求后,process_response 会写入cookie
    """

    def process_request(self, request):
        # APP、小程序就不用处理了, 上传的是 "device_id",能通过 get_device_id_from_request 获取到
        if not get_app_from_request(request) and not is_weixin_mini_request(request):
            cookie_distinct_id = request.COOKIES.get(WEB_DISTINCT_ID_NAME, '')
            if not cookie_distinct_id:
                cookie_distinct_id = uuid.uuid4().hex
            setattr(request, WEB_DISTINCT_ID_NAME, cookie_distinct_id)

    def process_response(self, request, response):
        # 有cookie,则重新设置过期时间
        distinct_id = getattr(request, WEB_DISTINCT_ID_NAME, None)
        if distinct_id:
            max_age = 60 * 60 * 24 * 365
            expires_time = time.time() + max_age
            expires = cookie_date(expires_time)

            response.set_cookie(WEB_DISTINCT_ID_NAME, distinct_id, max_age=max_age,
                expires=expires, domain=_get_cookie_domain(request),
                path=settings.SESSION_COOKIE_PATH,
                secure=settings.SESSION_COOKIE_SECURE or None,
                httponly=settings.SESSION_COOKIE_HTTPONLY or None,
            )
        return response

起初方案:

在这里插入图片描述
最开始策略,通过写一个h5页面作为跳板,但是跳转十分麻烦,APP端无法提前告知服务器手机设备号导致无法配置。同时跳转APP不支持参数,这样导致每个端处理逻辑出现差异。

解决方案

通过搜索口令(比如各个端搜索‘白名单测试’,搜索之后会自己自动把当前设备加入到白名单),就不用再找设备号、配置设备号之类的。口令只是一个行为告诉server,要触发怎样动作,需区别于线上真实用户。同时对于不同口令,可以使用命令模式扩展。以下是具体实现:

实现code:

from __future__ import absolute_import

from abc import abstractmethod

class SearchCommandHandlersBase(type):
    REGISTERED_COMMAND_CLS = {}
    COMMAND = None

    def __new__(mcs, name, bases, attrs):
        cls = super(SearchCommandHandlersBase, mcs).__new__(mcs, name, bases, attrs)
        if cls.COMMAND:
            mcs.REGISTERED_COMMAND_CLS.setdefault(cls.COMMAND, []).append(cls)
        return cls


class SearchCommandHandler(metaclass=SearchCommandHandlersBase):

    # 抽象处理者
    @abstractmethod
    def execute(self, request):
        pass


class AdWhiteDeviceTestHandler(SearchCommandHandler):
    """
    广告白名单测试
    """
    COMMAND = '白名单测试'

    @classmethod
    def execute(cls, request):
        request_meta = RequestMeta(request)
        if request_meta.app or is_weixin_mini_request(request):
            device_id = request_meta.device_id
        else:
            device_id = request.COOKIES.get(WEB_DISTINCT_ID_NAME)
        # todo: AdWhiteManager 保存device_id 逻辑



def handle_search_command(text, request):
    """
    处理搜索口令
    :param text:
    :return:
    """
    for handler in SearchCommandHandlersBase.REGISTERED_COMMAND_CLS.get(text) or []:
        handler.execute(request)

在搜索接口添加:handle_search_command(query_text, requet)就好了

基础案例:

# -*- coding:utf-8 -*-
 
 
class Command:
    """声明命令模式接口"""
    def __init__(self, obj):
        self.obj = obj
 
    def execute(self):
        pass
 
 
class ConcreteCommand(Command):
    """实现命令模式接口"""
    def execute(self):
        self.obj.run()
 
 
class Invoker:
    """接受命令并执行命令的接口"""
    def __init__(self):
        self._commands = []
 
    def add_command(self, cmd):
        self._commands.append(cmd)
 
    def remove_command(self, cmd):
        self._commands.remove(cmd)
 
    def run_command(self):
        for cmd in self._commands:
            cmd.execute()
 
 
class Receiver:
    """具体动作"""
    def __init__(self, word):
        self.word = word
 
    def run(self):
        print(self.word)
 
 
def client():
    """装配者"""
    test = Invoker()
    cmd1 = ConcreteCommand(Receiver('命令一'))
    test.add_command(cmd1)
    cmd2 = ConcreteCommand(Receiver('命令二'))
    test.add_command(cmd2)
    cmd3 = ConcreteCommand(Receiver('命令三'))
    test.add_command(cmd3)
    test.run_command()
 
 
if __name__ == '__main__':
    client()
--------------------------
命令一
命令二
命令三
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值