#python #oop #design-patterns
#python #ооп #шаблоны проектирования
Вопрос:
Это вопрос в основном о дизайне.
Давайте предположим, что у нас есть класс, который выполняет функцию инициализации различных подклассов определенного типа с помощью некоторых параметров, которые он повторяет. Проблема возникает, когда __init__
метод получает разные параметры для каждого подтипа. Есть ли какой-либо способ избежать if
инструкций внутри функции, которая инициализирует классы, просто чтобы знать, какие параметры передавать? Возможно, какой-то шаблон проектирования, о котором я не знаю. Или это результат плохого дизайна?
ниже приведен пример того, что я имею в виду. обратите внимание на статический метод manage, который содержит if … else… в нем, и если бы было больше типов workers, у нас было бы больше if, чего я пытаюсь избежать. Имейте в виду, что пример минимален, а if
инструкции могут быть намного сложнее.
from abc import ABCMeta
class BaseWorker(metaclass=ABCMeta):
def work(self):
pass
class Worker1(BaseWorker):
def __init__(self, name):
self.name = name
def work(self):
pass
class Worker2(BaseWorker):
def __init__(self, name, age):
self.name = name
self.age = age
def work(self):
pass
class Manager(object):
@staticmethod
def manage(attributes_list):
for attributes in attributes_list:
if "age" in attributes:
w = Worker2(name=attributes["name"], age=attributes["age"])
else:
w = Worker1(name=attributes["name"])
w.work()
if __name__ == '__main__':
dynamic_attributes = [
{"name": "davay"},
{"age": "55", "name": "ok"},
# and so on...
]
Manager.manage(dynamic_attributes)
И желаемое решение было бы
@staticmethod
def desired_manage(attributes_list):
for attributes in attributes_list:
w = worker_factory(attributes)
w.work()
** Обратите внимание, что worker_factory
это просто произвольное название способа решения этой проблемы, это не означает, что factory pattern — это правильный путь.
Более того, если мы попробуем factory pattern, из того, что я вижу, if
операторы просто переместятся туда, и это ничего не решит.
Спасибо!
Комментарии:
1. Вы могли бы поместить атрибуты map в соответствующие им классы в dict и сделать что-то вроде
klass_ = mapping.get(attr, default_class)
. Это то, что вы ищете?2. @snakecharmerb это возможная идея, но что, если
if
инструкции были более сложными? Я ищу что-то более надежное.3. Если условия более сложные, я бы, вероятно, использовал цепочку if / elif, хотя, возможно, вы могли бы что-то сделать с кортежами в качестве ключей. Я действительно не понимаю, почему вы не хотите использовать ifs: если код должен ветвиться, он должен ветвиться, а фабричная функция / метод — хорошее место для изоляции ветвящегося кода.
4. @snakecharmerb причина, по которой мне не нравятся операторы цепочки if
if
, заключается в том, что каждый раз, когда я захочу добавить новый класс (в данном случае новый worker), мне придется увеличивать цепочку, что означает, что в существующем коде будут изменения. Я не хочу изменять существующий код, это нарушает принципы SOLID и подвержено ошибкам.
Ответ №1:
Одним из способов сделать это было бы зарегистрировать каждый подкласс в диспетчере вместе с вызываемым параметром, который определяет, следует ли создавать экземпляр подкласса.
Что-то вроде этого (непроверенное):
class Manager(object):
_registry = []
_default = None
@classmethod
def register(cls, callable, klass):
cls._registry.append((callable, klass))
return
@classmethod
def register_default(cls, klass):
cls._default = klass
return
@staticmethod
def manage(attributes_list):
for attributes in attributes_list:
for callable, klass in Manager._registry:
if callable(attributes):
w = klass(**attributes)
break
else:
w = Manager._default(**attrs)
w.work()
class BaseWorker(metaclass=ABCMeta):
def work(self):
pass
class Worker1(BaseWorker):
def __init__(self, name):
self.name = name
Manager.register_default(Worker1)
class Worker2(BaseWorker):
def __init__(self, name, age):
self.name = name
self.age = age
Manager.register(Worker2, lambda attrs: 'age' in attrs)
Этот метод имеет некоторые недостатки:
- Условия тестируются в порядке вставки, поэтому вам нужно убедиться, что более строгие условия, например,
'name' in attrs and 'age' in attrs
, тестируются перед более мягкими условиями, такими'name' in attrs
как. Это может быть непросто, если подклассы определены в разных модулях. Возможно, добавьте приоритет к каждому(callable, klass)
кортежу, который можно использовать для сортировки реестра - Вложенный цикл может быть медленным, если
attributes_list
он большой (и / или существует много подклассов)
Ответ №2:
если подклассы в конечном итоге будут совместно использовать рабочий метод, то вам не нужны эти подклассы, и их реализация будет плохим дизайном, добавляющим ненужных трудностей. вы могли бы просто использовать значения по умолчанию в init для необязательных параметров, и тогда он может получать разные параметры для каждого подтипа без жалоб.
from abc import ABCMeta
class BaseWorker(metaclass=ABCMeta):
def work(self):
pass
class Worker(BaseWorker):
def __init__(self, name, age=None, address=None, sex=None):
self.name = name
self.age = age
address= address
sex= sex
def work(self):
pass
class Manager(object):
@staticmethod
def manage(attributes_list):
for attributes in attributes_list:
w = Worker(**attributes)
w.work()
if __name__ == '__main__':
dynamic_attributes = [
{"name": "davay"},
{"name": "aa", "age": "55"},
{"name": "bb", "sex": "female"},
{"name": "cc", "address": "123 street", "age": "43"},
# and so on...
]
Manager.manage(dynamic_attributes)
Комментарии:
1. Это с учетом того, что json находится в определенном порядке. Возможно, это не так. Классы не будут выполнять одинаковую работу, реализация будет другой.
2. привет @max , нет необходимости располагать в определенном порядке. и это не json, выглядит как один, но это список словарей python.
3. вы правы, но все равно это не имеет значения, потому что у разных workers будет разная реализация метода
work
.4. скажем, если вы инициализируете worker с именем, это будет работать иначе, чем инициализация worker с именем и возрастом? это будет глючный дизайн, imo. извините, я не смог помочь.