Шаблон проектирования Python: класс, который возвращает разные объекты в зависимости от параметров

#python #oop #design-patterns #abstract-class #multiple-inheritance

#python #ооп #шаблоны проектирования #абстрактный класс #множественное наследование

Вопрос:

Этот вопрос касается шаблонов проектирования в Python и адресован разработчикам программного обеспечения.

У меня есть несколько классов, унаследованных от одного и того же абстрактного класса (все они имеют схожий интерфейс, но функции-члены имеют очень разные реализации). Я хочу создать класс (или что-то еще), который объединяет (переносит) их все. Под объединением я подразумеваю шаблон, который создает и возвращает объекту определенный класс в зависимости от входных параметров. Наиболее тривиальным решением является функция Python:

 def fabric_function(arg):
    if isinstance(arg, float):
        return Class1(arg)
    if isinstance(arg, str):
        return Class2(arg)
 

Есть ли лучший шаблон для этого? Я бы действительно предпочел, чтобы это был класс, который возвращает либо объект Class1 , либо Class2 в зависимости от параметров. Я хочу, чтобы пользователь создал экземпляр класса Class , который работает как экземпляр Class1 or Class2 в зависимости от входных параметров, поэтому пользователю не нужно принимать решение о том, какой из них использовать, и вывод type(obj) должен быть Class во всех случаях. В этом случае я мог бы изменить функцию-член __new__ , отвечающую за процесс создания объекта, но я не уверен, что это чистый способ сделать это. Может быть, мне следует использовать множественное наследование и решить в конструкторе, какой родительский класс наследовать по-настоящему?

Комментарии:

1. и вывод из type(obj) должен быть Class во всех случаях . Насколько это сложное требование? В чем цель? Легко иметь фабрику Class1 Class2. Возможно иметь тот, который говорит Class в обоих случаях (используя type для создания класса на лету), но, честно говоря, это ужасный взлом, который не понятен потребителям и не обязательно хорош для таких вещей, как isinstance . Пахнет проблемой XY, если это жесткий запрос.

2. Это не так сложно, но я не хочу, чтобы пользователь знал о существовании Class1 и Class2, они являются техническими классами и не имеют ничего общего с интерфейсом библиотеки, который он / она собирается использовать.

3. и, как я уже сказал, это халтура . вы вводите странное поведение для чего-то, что реальному конечному пользователю никогда не было бы интересно, а пользователю-программисту было бы неприятно. Проблема XY. в любом случае поведение проверки класса pythonic вращается вокруг isinstance , а не type . это достаточно важно для языка, на который линтеры жалуются type , а не isinstance используют. итак, опять же, информированный пользователь не будет использовать type в своем коде, хотя он может использовать в repl.

4. Это звучит как серьезное злоупотребление OO, и вы не предоставили никакого обоснования для «Я не хочу, чтобы пользователь знал о существовании Class1 и Class2» . Это разрешения или сквозная защита, например, одно есть Employee.request_pay_rise , а другое есть Manager.approve_pay_rise ? Пожалуйста, покажите больше подробностей о Class1, Class2 и почему им удается быть родственными классами, но вы хотите скрыть их связь. Если «они являются техническими классами и не имеют ничего общего с интерфейсом библиотеки, который он / она собирается использовать» , тогда просто дайте общему родительскому классу неясное имя с предварением «_»

Ответ №1:

Я полагаю, что Class1 и Class2 как-то связаны (например, «Квадрат» и «Круг» являются «Фигурами»). То, что вы описываете, является абстрактной фабрикой.

Вам нужен класс, предоставляющий Create() метод, принимающий минимальный контекст, который помог бы вам выяснить, какую конкретную реализацию нужно создать.

Ответ №2:

Однажды я проделал нечто подобное, используя словарь. I требует, чтобы функции инициализации ваших производных классов имели одинаковые сигнатуры для работы.

Используйте словарь с некоторым неизменяемым типом по вашему выбору в качестве ключа и поместите имена классов в качестве значений:

 TYPE_MAP = {typeA: Class1,
            typeB: Class2,
            typeC: Class3}

def fabric_function(arg):
    return TYPE_MAP[type(arg)](arg)
 

Я не совсем уверен, насколько pythonic / clean вы считаете это, но это очень помогло мне избежать этой if isinstance... цепочки, которую вы опубликовали выше. Кроме того, его очень легко расширить, так как вам нужно только настроить словарь, а не заводскую функцию.