美文网首页
架构师训练营第2周作业 面向对象设计原则

架构师训练营第2周作业 面向对象设计原则

作者: 浩哥有料 | 来源:发表于2020-06-17 23:55 被阅读0次

请描述什么是依赖倒置原则,为什么有时候依赖倒置原则又被称为好莱坞原则?

依赖倒置原则的标准描述如下:

  1. 高层模块不应依赖低层模块,两者都应当依赖于抽象
  2. 抽象不应依赖具体,具体应当依赖抽象

这一原则的描述乍一看是难以理解的,但其实,摸清什么是依赖,什么被倒置了,就不难理解这一原则。

当一个模块A直接调用另一个模块B时,我们就说模块A依赖模块B,对应到代码上,我们在模块A中需要直接import模块B,才能调用到模块B中的功能。在我们平时编写代码的时候,模块A,即调用方,往往是高层模块,它是业务用例的表达,而模块B往往是低层模块,它是具体的技术实现,但是为什么依赖倒置原则说高层模块不应当依赖于低层模块呢?

试想,由于高层模块需要import(引用)低层模块,因此当低层模块出现问题的时候,高层模块的加载和运行也会收到影响。另外当低层模块发生变化的时候,作为调用方的高层模块也需要跟着改变以满足新的调用方式。这样一来,高层模块不但是依赖低层模块,甚至是在被它牵着鼻子走,这显然不符合“高层模块”这一身份,其抽象性、复用性、封闭性都受到了极大的影响。

软件开发领域有一条经典的方法论:任何的耦合都可以通过增加一层来解决,依赖倒置原则正是在高层模块和低层模块之间加了一个抽象接口,将高层模块需要调用的功能抽象出来,而由低层模块去实现这一接口,这样一来,高层模块和低层模块都只直接依赖于这一接口,而由于低层模块实现了这一接口,因此借助多态特性,高层模块无需了解低层模块的细节也可以在需要的时候随时调用它,但却无需始终依赖它。

抽象接口的加入使得原本的依赖关系发生了变化,原本的依赖关系是高层模块直接指向低层模块,而现在是高层模块和低层模都指向抽象接口,因此说依赖方向发生了倒转,这也是该原则被称为依赖倒置原则的原因。依赖关系倒转过后,低层模块作为抽象接口的具体实现,体现着“具体依赖于抽象”的原则,而抽象接口本身则无需再关心是由谁来实现它,因为借助多态,无论谁来实现它,对高层模块来说都是没有影响的。

依赖倒置原则有个别名叫“好莱坞原则”,这个说法源于好莱坞制片方对演员们说的一句话“不要call我,我会call你”,到了面向对象这里,这一说法就被改成“不要调用我,我会去调用你”。其实我认为这种说法跟依赖倒置原则原本的表述不是完全符合,容易给人造成误导,前半句“不要调用我”是对的,高层模块不再直接调用低层模块,而“我会去调你”则完全没有体现出抽象接口的作用,看上去像是让低层模块去调用高层模块,而事实上不过是低层模块依赖高层模块所定义的抽象接口。

请描述一个你熟悉的框架,是如何实现依赖倒置原则的。

依赖倒置往往会跟“控制反转”和“依赖注入”这两个词同时出现,依赖倒置描述的是原则,依赖注入描述的是方法,而控制反转描述的则是结果。依赖倒置使得高层模块不再直接依赖低层模块,但在运行时必须要指定一个具体的低层模块,即抽象接口的具体实现,这一操作即被称为“依赖注入”,顾名思义,它是在运行的时候再将高层模块需要调用的低层模块动态注入到程序中,并借助多态进行调用。在运用依赖注入的时候,我们将使用接口的何种具体实现这一决定权交给了调用方自己,而不是在编写代码的时候进行预先定义,这种做法就被称为“控制反转”。

我平时工作使用的语言是Python,Python具有很好的动态特性,因此我在自己编写的两个案例中以两种方法实现了依赖倒置。

方法一

这是一个代码部署工具,仿照了DDD中的分层设计思想,将领域模型中的repository作为抽象接口,而将具体实现放在持久化层。考虑到代码的来源可能是Git仓库、SVN、本地文件夹等等,因此编写了不同的code_repo的实现。

from functools import wraps, partial


__mapper__ = {}


def register(name):
    def decorator(cls):
        __mapper__[name] = cls
        return cls
    return decorator


def dependency(name):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            cls = __mapper__[name]
            kwargs[name] = cls
            new_func = partial(func, **kwargs)
            new_func(*args)
        return wrapper
    return decorator

这里使用了Python中的装饰器,分别定义了registerdependency两个装饰器函数,register可以将一个类以特定的名字注册到mapper字典中,而dependency则可以在一个函数的参数列表中使用特定的名字从mapper中拿到对应的类。

# Register this class as the implementation of code repository.
@register('code_repo')
class GitlabCodeRepo(CodeRepository):
    """Implementation of code repository."""

    @staticmethod
    def get_code(package_name, package_type):
        """Pull latest source code of specific package from Gitlab.

        Args:
            package_name (str): Name of a Gitlab repo.

        Returns:

        """
        if package_name in os.listdir(SOURCE_DIR):
            shutil.rmtree('{}/{}'.format(SOURCE_DIR, package_name),
                          onerror=handle_remove_readonly)
        gitlab_puller(package_name, package_type)

    @staticmethod
    @dependency('package_repo')
    def prepare_staging(package_name, package_repo=None):
        """Prepare staging area for a package.

        This function will check if the source code of the package exists, then
        it will clear staging area and config builder for the package, finally
        save the updated package into package repository.

        Args:
            package_name (str): Name of the package.
            package_repo (PackageRepository): Package repository implementation
                class.

        Raises:
            RuntimeError: If source code of the given package name not exists
                in source area.

        """
        source_dir = '{}/{}'.format(SOURCE_DIR, package_name)
        staging_dir = '{}/{}'.format(STAGING_DIR, package_name)
        if os.path.isdir(source_dir):
            if os.path.isdir(staging_dir):
                shutil.rmtree(staging_dir, onerror=handle_remove_readonly)
        else:
            raise RuntimeError(
                'You should get source code of {} first.'.format(package_name))
        package = package_repo.get(package_name)
        package.builder = Builder(source_dir, staging_dir, package.build_module)
        package_repo.update(package)

在具体使用时,在类的开头使用register装饰器,即可将该类注册到mapper,成为特定名字的具体实现。当然,这些类全部都继承自一个抽象基类,调用方只需按照抽象基类来编写调用即可。在函数上使用dependency装饰器,可以将函数参数列表中的同名默认参数的实参替换成特定的类,从而实现了依赖注入。

方法二

这是一个视频转码框架,由于需要对输入的源文件进行一系列的解析获得元数据,而针对不同需求所需要使用的解析过程是不同的,因此根据依赖倒置原则编写了一系列解析器,运行时根据当前配置注入具体的解析器。



同样是定义一个抽象基类(在Python中相当于抽象接口),各个模块中的子类继承该基类并实现特定的方法。

class ParsingService(object):

    def __init__(self, config_service):
        self.config_service = config_service

    def get_parsers(self):
        parser_names = self.config_service.get_preset_config(
            'parsing/parsers')
        parsers = []
        custom_path = self.config_service.get_preset_config(
            'parsing/custom_loading_path')
        for name in parser_names:
            if custom_path:
                try:
                    module = get_module(name, custom_path)
                except ImportError:
                    module = import_module(
                        'batch_transcoder.backend.parsing.{}'.format(name))
            else:
                module = import_module(
                    'batch_transcoder.backend.parsing.{}'.format(name))
            cls = getattr(module, module.__parser_class_name__)
            parser = cls(self.config_service)
            parsers.append(parser)
        return parsers

在实际运行的时候,从配置文件中读取需要用到的具体实现,读取到的结果是一个列表,其中包含每个具体实现所在的模块的名字,然后借助Python的动态加载,通过模块名将模块加载进来。我们还需要从模块中得到具体的类,因此每一个具体实现的模块开头都包含了一个__parser_class_name__变量,用来指定具体实现的类名,从而便于依赖注入的时候找到具体的类并进行实例化。

请用接口隔离原则优化Cache类的设计,画出优化后的类图。


Cache接口.png

相关文章

  • 面向对象设计原则

    面向对象设计原则 面向对象设计原则是设计模式的基础,每个设计模式都符合一一种或多种面向对象的设计原则。 常用的面向...

  • 面向对象设计原则(二)开闭原则

    面向对象设计原则之开闭原则 开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。 开闭原则的...

  • 01-设计模式原则

    面向对象的设计原则 面向对象的设计原则也被称为SOLID。SOLID原则包括单一职责原则、开闭原则、里氏替换原则、...

  • Swift设计模式----目录

    面向对象设计原则: 开闭原则 单一职责原则 依赖倒置原则 接口分离原则 迪米特法则 里氏替换原则 面向对象设计模式...

  • 面向对象设计原则

    面向对象设计原则

  • 架构师训练营第2周作业 面向对象设计原则

    请描述什么是依赖倒置原则,为什么有时候依赖倒置原则又被称为好莱坞原则? 依赖倒置原则的标准描述如下: 高层模块不应...

  • (1)面向对象的六大原则

    什么是面向对象原则 面向对象原则——oop(object Oriented Principle),遵循原则设计程序...

  • 面向对象设计原则

    面向对象设计原则 面向对象设计原则概述[https://www.jianshu.com/p/57137d81c55...

  • 设计模式原则-开闭原则

    开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实...

  • 设计模式的原则

    面向对象的原则是面向对象的目标,而设计模式是为了实现这些原则的手段和方法。这也就是为什么要有设计模式。 面向对象的...

网友评论

      本文标题:架构师训练营第2周作业 面向对象设计原则

      本文链接:https://www.haomeiwen.com/subject/jdaexktx.html