Многоуровневое внедрение зависимостей в python - PullRequest
0 голосов
/ 26 марта 2020

У меня есть 5 классов, как показано ниже. Выполнение начинается с класса 5. Здесь внедрение зависимостей используется, когда объект слушателя передается в конструктор класса Bot, а затем этот объект вызывает метод listen (). Эта часть в порядке.

Проблема начинается с этого момента.

Из метода listen() я вызываю другой метод с именем process(), который находится в классе Processor (файл processor.py). Затем метод process() вызывает два других метода из 2 разных классов Tokenizer и Core.

Как вы можете видеть, все они связаны, и внедрение зависимостей здесь не используется. Я не уверен, как это сделать.

Я опробовал примеры пакетов python dependency-injector и pinject, но эти примеры касаются одноуровневых зависимостей, а не случаев, подобных этому, насколько я могу. понять.

Я думал о создании всех необходимых объектов в одном модуле и передаче его в качестве параметров всем классам, когда это необходимо. Но это не похоже на хорошую практику.

Что я могу сделать, чтобы отделить код выше? Как мне go о модульности этого?

# core.py

class Core:
    def decide(self, data):
        """ Makes decisions based on input data """

        # blah blah

-

# tokenizer.py

import nltk

from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

stoplist = set(stopwords.words('english'))

class Tokenizer:
    def tokenize(self, query):
        """ Tokenize the input query string """
        tokenized_query = word_tokenize(query)
        clean_query = [w for w in tokenized_query if not w in stoplist]
        return clean_query

-

# processor.py

import tokenizer
import core

class Processor:
    def process(self, query):
        """ 
        Send the input query to tokenizer 
        tokenized input is send to the Core module for decision making
        """
        tokenized_input = tokenizer.Tokenizer().tokenize(query)
        core.Core().decide(tokenized_input)

-

# listener.py

import processor

class Listener:
    def listen(self):
        """ Continuosly listens to user input """
        while True:
            query=input()
            processor.Processor().process(query)

-

# bot.py

import listener

class Bot:
    def __init__(self, listener):
        listener.listen()

Bot(listener.Listener())

Ответы [ 2 ]

1 голос
/ 10 апреля 2020

Я думаю, что вы можете переосмыслить этот дизайн, имея в виду две концепции:

  1. Python Множественное наследование для разработки ваших классов.
  2. Использование super () и Python Порядок разрешения методов (MRO) для внедрения макетов для зависимостей классов в тестовом коде.

Что касается первого пункта, ваши классы будут выглядеть следующим образом:

# core.py

class Core:
    def decide(self, data):
        """ Makes decisions based on input data """

        # blah blah

# tokenizer.py

class Tokenizer:
    def tokenize(self, query):
        """ Tokenize the input query string """
        return query

# processor.py
# from tokenizer import Tokenizer
# from core import Core

class Processor(Core, Tokenizer):
    def process(self, query):
        """ 
        Send the input query to tokenizer 
        tokenized input is send to the Core module for decision making
        """
        tokenized_input = super().tokenize(query)
        super().decide(tokenized_input)

# listener.py
# from processor import Processor

class Listener(Processor):
    def listen(self):
        """ Continuosly listens to user input """
        while True:
            query=input()
            super().process(query)

# bot.py
#from listener import Listener

class Bot(Listener):
    def start_listener(self):
        super().listen()


Bot().start_listener()

При этом ОО-дизайн и использование super () , мы можем воспользоваться MRO, чтобы внедрить mock в зависимости наших классов, я собираюсь показать, как вводить mock в нашу SUT (испытуемый объект) для его зависимостей.

Примеры для Bot и Процессор :

class MockCore(Core):
    def decide(self, data):
        """ Here you can implement the behavior of the mock """

class MockTokenizer(Tokenizer):
    def tokenize(self, query):
        """ Here you can implement the behavior of the mock """
        return query

class ProcessorSut(Processor, MockCore, MockTokenizer):
    'Here we are injecting mocks for Processor dependencies'

class Bot(Listener):
    def start_listener(self):
        super().listen()

class MockListener(Listener):
    def listen(self):
        """ Here you can implement the behavior of the mock """
        return

class BotSut(Bot, MockListener):
    'Here we are injecting a mock for the Listener dependency of Bot'

Видя MRO наших классов SUT, мы можем понять, почему множественное наследование и использование super () позволяет нам вводить mock таким образом.

Результирующее MRO для BotSut и ProcessorSut :

Help on class BotSut in module __main__:

class BotSut(Bot, MockListener)
 |  Here we are injecting a mock for the Listener dependency of Bot
 |  
 |  Method resolution order:
 |      BotSut
 |      Bot
 |      MockListener
 |      Listener
 |      Processor
 |      Core
 |      Tokenizer
 |      builtins.object
...
...
Help on class ProcessorSut in module __main__:

class ProcessorSut(Processor, MockCore, MockTokenizer)
 |  Here we are injecting mocks for Processor dependencies
 |  
 |  Method resolution order:
 |      ProcessorSut
 |      Processor
 |      MockCore
 |      Core
 |      MockTokenizer
 |      Tokenizer
 |      builtins.object

Для получения дополнительной информации :

0 голосов
/ 27 марта 2020

Вы можете сделать атрибуты класса служебных классов:

class Core:
    def decide(self, data):
        return False


class Tokenizer:
    def tokenize(self, query):
        return []  # ...


class Processor:
    tokenizer_class = Tokenizer
    core_class = Core

    def process(self, query):
        tokenized_input = self.tokenizer_class().tokenize(query)
        return self.core_class().decide(tokenized_input)


class Listener:
    processor_class = Processor

    def listen(self):
        while True:
            query = input()
            self.processor_class().process(query)

Затем вы можете использовать макетные / патч-функции вашего тестового фреймворка, например, Pytest monkeypatch:

def test_foo(monkeypatch):
    monkeypatch.setattr(Processor, 'tokenizer_class', FooTokenizer)
    # etc...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...