绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
设计模式 | Catalog设计模式,抵御业务方需求变动
2020-08-28 15:36:06

大家好,这是一个全新的专题——设计模式

其实可以选择的专题还有好几个,为什么选择设计模式呢?原因也很简单,首先是设计模式简单、易学。干货的文章固然好,但是普适性往往不强。另外一个很重要的点就是设计模式学习的好处非常明显,如果学得好的话,会觉得自己的编码能力有了质的突破。这并不是夸大其词,很多人包括我,在学习的时候都曾经有过这种感觉。


设计模式简介

设计模式这个词我想大家应该都听说过,但是它究竟是什么意思可能很多人并不清楚。其实设计模式就是一种经验,就是一种前人总结出来反复印证过可以解决各种问题或者是做出各种优化的代码设计经验

以上是教科书当中的内容,下面是我个人的理解,在我看来设计模式主要有两种用途,种是优化我们的代码结构,让我们的代码更加健壮,设计更加合理。我们读大牛的代码常常惊叹,同样的功能他怎么这么简单就实现了,这个设计太巧妙了。设计模式就是这些令人惊叹的精彩设计的总结。第二种用途相对功利一些,是为了抵抗业务逻辑变动。这一点如果你还没有毕业的话,可能理解不深刻。在职场当中程序员讨厌什么?其中很重要的一个点就是业务逻辑的变动,昨天才说了这里要这么设计,突然过了两天就改了。或者是过了几天突然增加了一个之前没有想到的需求。而我们使用设计模式,一定程度上可以抵御这样的变更,尽量减少需求变动带来代码的更改。

简单总结一下,学习设计模式一方面可以让我们的代码能力更强写出来更优雅更牛的代码,另一方面可以帮助我们应对职场中需求,提升我们的表现和产出。

相比于这些好处重要的是,它的难度并不大,我们学习的成本不高。所以这是一件一本万利的事情,说是程序员进阶的必备技能也不为过。前面也说了设计模式是代码经验的总结和提炼,所以它也和语言特性有关,不同的语言实现出来的设计模式以及能够实现的设计模式也不一样。主流一般流行Java来实现设计模式,不过由于我们之前没有介绍过Java相关的语法,我们这里选择使用Python的设计模式。代码参考借鉴了github中设计模式热门repo:patterns,链接:https://github.com/faif/python-patterns

好了,废话不多说,我们开始今天的内容。

目录模式

今天要介绍的设计模式叫做Catalog,翻译过来是目录的意思。我没有找到很好的中文资料,可能也许是因为Java当中不支持这种模式,而中文主流的设计模式都是Java为基础的。

目录设计模式的核心逻辑在于我们在一个类当中以方法的形式提供许多种功能,我们将这些功能以目录的形式存储在一个dict当中。我们在创建实例的时候通过不同的参数获取不同的功能,使用方在使用的时候不感知具体的参数。

这种设计模式有几种实现方式,我们一个一个来看。

代码示例

基础版本

class Catalog:
    def __init__(self, param):
        self._static_method_class = {'param_value_1': self._static_method_1, 'param_value_2': self._static_method_2}

        if param in self._static_method_class:
            self.param = param
        else:
            raise ValueError('Invalid value for param: {0}'.format(param))

    @staticmethod
    def _static_method_1(self):
        print('excuted method 1')

    @staticmethod
    def _static_method_2(self):
        print('excuted method 2')

    def main_method(self):
        self._static_method_class[self.param](self)

整个的逻辑很简单,我们在init Catalog这个类的时候创建了一个_static_method_class dict,在这个dict当中我们的key是一个字符串,value是Catalog这个类的两个静态方法。然后我们判断param在不在dict当中,如果不在的话说明传入的param有误,我们抛出一个异常。

在使用的时候调用的是main_method函数,在这个函数当中我们直接会根据self.param的当中的值执行对应的方法。这里我们使用了静态方法,静态方法的好处是当我们创建它的子类的时候,静态方法不会被子类覆盖。当然这个静态方法不是非常有必要,也可以去除静态的逻辑,就使用普通方法也是一样可以运行的。

实例版本

上面的实现没有问题,但是有一个地方有一点怪怪的,就是_static_method_class这个dict我们放在了实例当中。带来的问题是如果这个dict很大,并且我们创建的实例很多的话,会导致冗余。因为既然所有实例的这个dict内容都是一样的,那么干嘛存那么多份呢,我们只需要存一份就可以了呀。

怎么样才能做到只存一份呢?也很简单,我们只需要把这个dict从实例域转移到类域就可以了。也就是说把这个dict变成类当中的field,说白了也就是把它的定义挪到init方法外面。

class CatalogInstance:

    def __init__(self, param):
        self.x1 = 'x1'
        self.x2 = 'x2'
        if param in self._instance_method_choices:
            self.param = param
        else:
            raise ValueError('Invalid value for param: {0}'.format(param))

    def _instance_method_1(self):
        print('Value {}'.format(self.x1))

    def _instance_method_2(self):
        print('Value {}'.format(self.x2))

    _instance_method_choices = {'param_value_1': _instance_method_1, 'param_value_2': _instance_method_2}

    def main_method(self):
        self._instance_method_choices[self.param](self)

这里我们新增了一些细节,就是x1和x2这两个参数。在这个例子当中,这两个参数是写死的,但是实际上我们完全可以将它的初始化也写在init方法当中。整体的逻辑和上面的版本大同小异,应该都可以看懂。

一点需要注意的是,当我们在类的内部调用实例的方法的时候,都是通过self.xxxx来调用的,我们在调用的时候,解释器会自动把当前实例作为个参数传入其中,这也是为什么实例级的方法前面个参数一定是self的原因。而这里,我们是把实例方法存在了dict里,通过dict取出来调用的。这种情况下,解释器并不会传入self,所以我们自己需要加上self这个参数,否则的话就会引发报错。

class和static版本

除了把_static_method_class这个dict放到了init方法的外面变成了类中的字段之外,我们还可以对这个dict存储的value做改动。我们可以把这两个方法变成类级别的方法和静态方法。虽然我个人觉得这样改动的意义不是很大,但是也是一种方法,大家可以参考一下。

把方法变成类级别方法:

class CatalogClass:

    x1 = 'x1'
    x2 = 'x2'

    def __init__(self, param):
        if param in self._static_method_choices:
            self.param = param
        else:
            raise ValueError('Invalid Value for param: {0}'.format(param))

    @classmethod
    def _static_method_1(cls):
        print('executed method 1')

    @classmethod
    def _static_method_2(cls):
        print('executed method 2')

    _static_method_choices = {'param_value_1': _static_method_1, 'param_value_2': _static_method_2}

    def main_method(self):
        self._static_method_choices[self.param].__get__(None, self.__class__)()

这里有一点需要注意,就是我们把x1和x2这两个参数也变成了类中的变量。因为在classmethod当中我们是无法调用到实例域下的变量的,所以必须要将它们变成类级别当中的变量才可以访问到。另外classmethod是规定了需要传入class的相关参数的,并且是不可以直接调用的,所以我们要使用__get__方法获取原始的函数。

把方法变成静态方法:

class CatalogStatic:

    def __init__(self, param):
        if param in self._static_method_choices:
            self.param = param
        else:
            raise ValueError('Invalid Value for param: {0}'.format(param))

    @staticmethod
    def _static_method_1():
        print('executed method 1')

    @staticmethod
    def _static_method_2():
        print('executed method 2')

    _static_method_choices = {'param_value_1': _static_method_1, 'param_value_2': _static_method_2}

    def main_method(self):
        self._static_method_choices[self.param].__get__(None, self.__class__)()

用法和上面类似,只是将classmethod换成了staticmethod而已。


用法浅谈

关于目录这个设计模式的讲解到这里就结束了,下面我们讨论一下它的使用方法,究竟在什么场景下我们会用到这么个设计模式呢?

其实很简单,就是我们实例创建和使用是分离的场景。比如我们是某功能的提供方,而使用方是另外的人。我们提供类的实例给对方使用,这样做的好处是如果一旦需求发生变化,比如说之前开发的功能A要加一些改动,我们只需要自己改动Catalog类当中的逻辑就可以了,下游可以不需要做任何修改。再比如我们可以把创建实例的时候传入的参数做成可配置的,这样我们就可以通过修改配置来调用不同的逻辑。

关于这个设计模式还有一些改动的方案,比如我们可以把参数的传递放在调用方。调用方获得的实例都是一样的,调用的时候传入不同的参数获得不同的效果。

今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、在看、点赞)。


分享好友

分享这个小栈给你的朋友们,一起进步吧。

TechFlow
创建时间:2020-03-19 11:13:43
机器学习、算法与数据结构、大数据相关和Python。 从纯基础开始的算法领域入门以及进阶内容。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • chengycz
    栈主

小栈成员

查看更多
  • 兔子爱喝红茶
  • 小雨滴
  • ittttliu
  • 栈栈
戳我,来吐槽~