Выбор одного из нескольких методов создания объекта - PullRequest
0 голосов
/ 12 февраля 2019

У меня есть класс Python, назовем его MyClass, с несколькими связанными методами.Мне нужно, чтобы поведение одного из этих методов, называемого .dynamic_method(), было радикально различным в зависимости от значения набора параметров при создании объекта.Будет несколько вариантов dynamic_method, которые будут разделять очень мало кода между ними, поэтому имеет смысл определять их как отдельные функции / методы, а не как один большой метод с множеством опций и условий.Мне не нужно иметь возможность изменять поведение этого метода после создания объекта, все может быть установлено во время __init__().

Для справки MyClass управляет наборами данных, а .dynamic_method имеет дело с некоторыми аспектами ввода-вывода этих данных, которые меняются в зависимости от набора данных и контекста.

Это технически не трудно достичь,но ни один из предложенных мною методов не кажется абсолютно правильным:

  1. Определите варианты метода как функции отдельно и присоедините один из них как связанный метод во время __init__(), используя один из методовздесь описано: Добавление метода к существующему экземпляру объекта .Мне кажется, что это наиболее естественный выбор, но многие ответы в этой теме настоятельно не рекомендуют использовать этот шаблон.
  2. Определите все возможные методы в определении класса как .dynamic_method_a, .dynamic_method_b и т. Д.,и установите параметр / псевдоним так, чтобы MyClass.dynamic_method() вызывал правильный.Это приведет к очень длинному определению класса и большому количеству неиспользуемых методов для каждого объекта.
  3. Сохраните методы как отдельные функции, и .dynamic_method() просто вызовет правильную функцию с self в качестве первого аргумента.,Это похоже на преднамеренное неправильное использование концепции методов и нарушает линирование.
  4. Используйте фабричный шаблон или функцию генератора объектов.Я бы предпочел этого избежать, так как это значительно усложнит кодовую базу.

Что можно было бы назвать наиболее «пифоническим» способом достижения такого поведения?Один из вышеперечисленных методов, или есть очевидная альтернатива, которую я пропустил?

Ответы [ 3 ]

0 голосов
/ 12 февраля 2019

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

  • гибкость для будущих изменений
  • при чтении кода легче понятькакое поведение ожидать от объекта только по его классу
  • Если вы используете IDE с хинтингом типов (pycharm или другим), ваше кодирование будет проще, так как IDE может понять, какой метод объект будет использовать перед выполнением.время

пример кода:

class DataStruct:

    def __init__(self, input_: str):
        self.Field = input_

class MyClass:

    def __init__(self, data: DataStruct):
        self.Data: DataStruct = data

    def dynamic_method(self):
        # Abstract Method
        pass

    def __str__(self):
        return self.Data.Field

class MyClassFunctionalityOne(MyClass):

    def __init__(self, data: DataStruct):
        super().__init__(data)

    def dynamic_method(self):
        self.Data.Field = self.Data.Field.upper()


class MyClassFunctionalityTwo(MyClass):

    def __init__(self, data: DataStruct):
        super().__init__(data)

    def dynamic_method(self):
        self.Data.Field = "and now for something completely different"


class MyClassFactory:

    def __init__(self):
        pass

    @classmethod
    def manufacture(cls, input_: DataStruct) -> MyClass:
        #replace this if..elif chain to contain the tests on the data you need for determine the right method
        if input_.Field.count('one') > 0:
            obj: MyClass = MyClassFunctionalityOne(input_)
        elif input_.Field.count('two')> 0:
            obj: MyClass = MyClassFunctionalityTwo(input_)
        else:
            obj = None

        return obj


# script starts here
received_data = DataStruct('This kind of data should result in functionality one')
object1 = MyClassFactory.manufacture(received_data)
received_data = DataStruct('This kind of data should result in functionality two')
object2 = MyClassFactory.manufacture(received_data)
print (type(object1))
print (type(object2))
print ('*'*5, 'objects before dynamic_method', '*'*5)
print (object1)
print (object2)
object1.dynamic_method()
object2.dynamic_method()
print ('*'*5, 'objects after dynamic_method', '*'*5)
print (object1)
print (object2)

выход:

<class '__main__.MyClassFunctionalityOne'>
<class '__main__.MyClassFunctionalityTwo'>
 ***** objects before dynamic_method *****
This kind of data should result in functionality one
This kind of data should result in functionality two
***** objects after dynamic_method *****
THIS KIND OF DATA SHOULD RESULT IN FUNCTIONALITY ONE
and now for something completely different
0 голосов
/ 12 февраля 2019

Я бы предпочел перейти к варианту 3, поскольку я считаю, что он чище, но это зависит от того, сколько вариантов реализации вам нужно.Вариант 4 (использовать фабрику) также хорош, но он генерирует больше кода.С другой стороны, вариант 4 гораздо более масштабируемый и обслуживаемый.

Итак, поскольку вы хотите изменить поведение dynamic_method() в __init__(), вы можете сделать это следующим образом:

class MyClass(object):
     def __init__(self, param=None):
         self.dynamic_method = {
             "a": self.dynamic_a,
             "b": self.dynamic_b,
             "c": self.dynamic_c}[param]

     def dynamic_a(self):
         print("a functionality")

     def dynamic_b(self):
         print("b functionality")

     def dynamic_c(self):
         print("c functionality")

>> m = MyClass("b")
>> m.dynamic_method()
b functionality
0 голосов
/ 12 февраля 2019

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

Если у вас нет каких-либо важных причин для изменения атрибута метода (первый вариант), я бы просто написал хороший if-else вdynamic_method() для вызова других функций (3-й вариант) или, если я действительно хочу быть модным, я бы сделал временный коммутатор из dict следующим образом:

def dynamic_method(self):
    return {
        <value to run foo1>: foo1,
        <value to run foo2>: foo2,
        <value to run foo3>: foo3
    }[self.method_variation](self)

def foo1(self):
    pass

def foo2(self):
    pass

def foo3(self):
    pass
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...