命令模式是行为模式的一种
“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将**“行为请求者”与“行为实现者”**解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式(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()
--------------------------
命令一
命令二
命令三
文章评论