Как внедрить в код Dependency Injection и принцип единой ответственности - PullRequest
1 голос
/ 23 мая 2019

Я создаю новое простое приложение для токенизации и удаления стоп-слов в заданном тексте. Для этого я создал файлы ниже:

// Stopword.js
class StopWord {
    constructor (stopWords) {
        this.stopWords = stopWords
    }
    remove (value) {
        // do whatever we need to remove this.stopWords from the passed value and return it
    }

}
// PreProcess.js
const StopWord = require('./StopWord')
class PreProcess {
    setValue (value) {
        this.value = value
        return this
    }
    removeStopWords (stopWords) {
        const stopWord = new StopWord(stopWords)
        return stopWord.remove(this.value)
    }

}
// Indexed.js
class Indexer {
    setValue (value) {
        this.value = value
        return this
    }
    setStopWords (stopWords) {
        this.stopWords = stopWords
        return this
    }
    index () {
        return this.preprocess
            .setValue(this.value)
            .removeStopWords(stopWords)
    }

}
// main.js
const indexer = new Indexer()
const result = indexer
    .setValue('a sample text ')
    .setStopWords(['a', 'an'])
    .index()

console.log(result)

Предположим, в некоторых случаях мы хотим динамически загружать стоп-слова из базы данных (разные стоп-слова для разных пользователей). Первый вопрос, который возникает, в каком классе нам нужно загружать стоп-слова из базы данных? Очевидно, что класс PreProcess внедряется в индексатор с помощью Dependency Injection. Класс StopWord также можно вводить с помощью DI, но мне было интересно, что это достаточно хорошо. Второй вопрос: в какие классы нужно вводить, в какие?

1 Ответ

0 голосов
/ 23 мая 2019

Не пытайтесь усложнить ситуацию по мере необходимости :-) Из названий, которые у вас есть в настоящее время, я бы не знал, как использовать эту функциональность. Что такое индексатор? Что такое препроцессор? Эти вещи могут быть чем угодно. Поэтому вместо этого сделайте это простым и с точки зрения предметной области прямо в точку (я приведу пример на TypeScript, чтобы вы могли видеть, какие типы используются и где):

Сначала мы определим доменные службы стоп-слов (наш бизнес-домен):

/** we'll use this to provide stop words as array of strings */
class StopWords {
    public stopWords: string[] = [];
    constructor(words?: string[]) { this.stopWords = words }
}
/** single responsibility of this class: actually remove the stopwords */
class StopWordsRemovalService {
    /** inject stopwords through constructor */
    constructor(stopWords: StopWords) {}
    /** here we do the real removal work */
    public removeAllStopWordsFrom(text: string): string { return ''; }
}

Теперь мы можем предоставить стоп-слова из любого места (подробнее об инфраструктуре):

/** TypeScript provids interfaces, which you don't have in plain JS. It merely 
 * defines the class method signatures, which is quite useful in software design */
interface StopWordsProvider {
    getStopWords(): StopWords;
}
class DefaultStopWordsProvider implements StopWordsProvider {
    getStopWords(): StopWords {
        return new StopWords(['a', 'an']);
    }
}
class DbStopWordsProvider implements StopWordsProvider {
    getStopWords(): StopWords {
        return db.query("SELECT stopWords FROM ...");
    }
}

И, наконец, мы свяжемся вместе:

const swremoval: StopWordsRemovalService  = new StopWordsRemovalService(new DefaultStopWordsProvider().getStopWords());
swremoval.removeAllStopWordsFrom('a sample text');

Чтобы связать все вместе, вы можете теперь использовать инфраструктуру внедрения зависимостей, такую ​​как InversifyJS


Обновление: нам нужно будет возвращать разные стоп-слова по идентификатору пользователя.

Первый вопрос, который приходит мне в голову: насколько важен идентификатор пользователя для бизнеса? Если для определения стоп-слов требуется идентификатор пользователя , всегда , идентификатор пользователя является неотъемлемой частью нашего домена! Если для определения стоп-слов требуется идентификатор пользователя , иногда , он может не являться неотъемлемой частью нашего домена. Давайте рассмотрим два случая:

ИД пользователя всегда , необходимый для получения стоп-слов

Если идентификатор пользователя важен для домена и всегда требуется для определения стоп-слов, то давайте сделаем его частью договора:

/** TypeScript provids interfaces, which you don't have in plain JS. It merely 
 * defines the class method signatures, which is quite useful in software design */
interface StopWordsProvider {
    /** Returns all the stop words for the given user */
    getStopWords(userID: number): StopWords;
}

Теперь все классы, реализующие этот интерфейс, должны учитывать идентификатор пользователя.

Идентификатор пользователя иногда требуется для получения стоп-слов

Если идентификатор пользователя требуется только для некоторых поисков, мы не изменим контракт с стоп-словами (т. Е. Интерфейс)! Вместо этого мы предоставим UserSpecificStopWordsProvider, который выполняет поиск. Для настройки идентификатора пользователя мы будем использовать фабричный шаблон:

/** The user-specific stopwords provider is configured through
 *  constructor-based injection */
class UserSpecificStopWordsProvider implements StopWordsProvider {
    constructor(private userId: number) {}
    getStopWords(): StopWords {
        return db.query("SELECT * FROM sw WHERE userId = :userId", this.userId);
    }
}

/** To simplify the usage, we'll provide a factory to do this. */
class UserSpecificStopWordsProviderFactory {
    factoryProvider(userId: number): StopWordsProvider {
        // potentially inject more dependencies here
        return new UserSpecificStopWordsProvider(userId);
    }
}

/** We can then use it as follows: */
const factory = new UserSpecificStopWordsProviderFactory();
factory.factoryProvider(5).getStopWords();
...