Фабрики классов и абстрактные базовые классы
Я пытаюсь определить количество классов на основе абстрактного базового класса. Каждый из этих классов в основном определяет форму ячейки для пакета визуализации. Ячейка состоит из нескольких вершин (точек), и каждому подклассу потребуется разное количество точек. Каждый класс можно рассматривать как контейнер для фиксированного числа координат точек.
В качестве примера рассмотрим базовый класс Shape
, который является просто контейнером для списка координат:
class Shape(object):
"""Cell shape base class."""
def __init__(self, sequence):
self.points = sequence
@property
def points(self):
return self._points
@points.setter
def points(self, sequence):
# Error checking goes here, e.g. check that `sequence` is a
# sequence of numeric values.
self._points = sequence
В идеале я хочу быть в состоянии определить, скажем, Square
класс, где points.setter
метод проверяет, что sequence
имеет длину четыре. Кроме того, я бы хотел, чтобы пользователь не смог создать экземпляр Shape
, Есть ли способ, которым я могу определить Shape
быть абстрактным базовым классом? Я попытался изменить определение формы следующим образом:
import abc
class Shape(object):
"""Cell shape base class."""
__metaclass__ = abc.ABCMeta
def __init__(self, sequence):
self.points = sequence
@abc.abstractproperty
def npoints(self):
pass
@property
def points(self):
return self._points
@points.setter
def points(self, sequence):
# Error checking goes here...
if len(sequence) != self.npoints:
raise TypeError('Some descriptive error message!')
self._points = sequence
Это требует подклассов для определения свойства npoints
, Я могу тогда определить класс Square
как
class Square(Shape):
@property
def npoints(self):
return 4
Тем не менее, это было бы довольно утомительно реализовать для большого количества подклассов (и с более чем одним свойством для реализации). Я надеялся определить фабрику классов, которая создала бы мои подклассы для меня, что-то вроде:
def Factory(name, npoints):
return type(name, (Shape,), dict(npoints=npoints))
Triangle = Factory('Triangle', 3)
Square = Factory('Square', 4)
# etc...
Является ли эта фабричная функция класса подходящим подходом, или я забил npoints
имущество? Лучше ли заменить звонок на type
с чем-то более многословным, как:
def Factory(name, _npoints):
class cls(Shape):
@property
def npoints(self):
return _npoints
cls.__name__ = name
return cls
Альтернативный подход заключается в определении атрибута класса _NPOINTS
и изменить npoints
собственностью Shape
в
@property
def npoints(self):
return _NPOINTS
Однако тогда я теряю преимущество использования абстрактного базового класса, поскольку:
- Я не вижу, как определить атрибут класса с помощью
type
, а также - Я не знаю, как определить атрибут абстрактного класса.
У кого-нибудь есть мысли о том, как лучше реализовать этот абстрактный базовый класс и функцию фабрики классов, или даже вообще лучший дизайн?
1 ответ
Не зная больше о вашем проекте, я не могу дать конкретного совета по общему дизайну. Я просто приведу несколько общих советов и мыслей.
Динамически генерируемые классы часто являются признаком того, что вам не нужны отдельные классы - просто напишите один класс, который объединяет все функциональные возможности. В чем проблема с
Shape
класс, который получает свои свойства во время создания экземпляра? (Конечно, есть причины использовать динамически генерируемые классы -namedtuple()
Заводская функция является одним из примеров. Однако я не смог найти никаких конкретных причин в вашем вопросе.)Вместо использования абстрактных базовых классов вы часто просто документируете предполагаемый интерфейс, а затем пишете классы, соответствующие этому интерфейсу. Из-за динамической природы Python вам не нужен общий базовый класс. Часто есть другие преимущества для общего базового класса - например, общая функциональность.
Проверяйте ошибки кода приложения только в том случае, если это не приводит к странным ошибкам в несвязанных местах. Если, скажем, ваша функция ожидает повторяемость, просто предположим, что вы получили повторяемость. Если пользователь передал что-то еще, ваш код потерпит неудачу, когда он все равно попытается повторить переданный объект, и сообщения об ошибке обычно будет достаточно, чтобы разработчик приложения понял ошибку.