Моя ситуация примерно такая:

class AbstractClass:
    def __init__(self, property_a):
        self.property_a = property_a

    @property
    def some_value(self):
        """Code here uses property_a but not property_b to determine some_value"""

    @property
    def property_a(self):
        return self.property_a

    @property
    def property_b(self):
        """Has to be implemented in subclass."""
        raise NotImplementedError


class Concrete1(AbstractClass):
    """Code here including an implementation of property_b"""


class Concrete2(AbstractClass):
    """Code here including an implementation of property_b"""

Также существует условие, что если property_b меньше, чем property_a, то property_a недействителен и, следовательно, результат some_value также недействителен.

Я имею в виду, что ... если в любой момент времени существования объекта вызов property_b даст число меньше, чем вызов property_a, возникнет проблема. Однако свойство property_b не является полем . Он определяется динамически на основе полей n , где n > = 1. Невозможно проверить это условие при установке property_b, потому что само property_b никогда не устанавливается. На самом деле, сеттеры не предполагается использовать где-нибудь здесь. Все поля, скорее всего, будут установлены в конструкторах, а затем оставлены в покое. Это означает, что property_a будет известен в конструкторе для AbstractClass и property_b только после оценки конструктора для конкретных классов.

<обновление>
Основная проблема заключается в следующем: мне нужно проверить property_a на валидность, но когда установлено property_a (наиболее интуитивно понятное место для проверки), property_b не определено.

Я хочу убедиться, что property_b никогда не будет меньше, чем property_a. Как мне с этим справиться?

Проверить property_a на property_b в ...

  1. AbstractClass.__init__. На самом деле это невозможно, потому что property_b еще не определен.
  2. AbstractClass.property_a. Это кажется проблематичным, потому что я выбрасываю исключение в геттере.
  3. Каждая конкретная реализация property_b. Я бы не только генерировал исключение в геттере, но и дублировал код. Также property_b логически не зависит от property_a.
  4. AbstractClass.some_value. Это по-прежнему вызывает исключение в получателе. Кроме того, логически невозможно, чтобы property_b было меньше, чем property_a все время , не только при попытке определить some_value. Кроме того, если подклассы решат добавить другие свойства, зависящие от property_a, они могут забыть сверить его с property_b.
  5. Бетоноукладчики для property_b. Такого не существует. property_b иногда определяется из значения, установленного в конструкторе, иногда вычисляется из нескольких значений. Также дублирование кода.
  6. Конкретные методы класса __init__. Дублирование кода. Кто-то может забыть.
  7. ???

ОБНОВЛЕНИЕ

Я думаю, что путаницу вызывает то, что property_b - это не просто поле. property_b полагается на расчеты. Это действительно больше функция, чем свойство, если это помогает думать об этом таким образом.

1
Instance Hunter 16 Окт 2009 в 18:28
Имейте в виду, что я говорю это как человек, который сделал это сам: считали ли вы, что где-то слишком усложняете? Я не совсем понимаю ваш вопрос, но, конечно, не может быть это сложно определить три свойства. Вы уверены, что не существует решения на 90%, которое вы могли бы реализовать?
 – 
Jason Baker
16 Окт 2009 в 20:01
Что ж, я мог бы перенести свойство b и какое-то значение в подклассы, но тогда у меня было бы дублирование кода, и вся цель моей архитектуры состоит в том, чтобы суперклассы выполняли как можно больше работы, чтобы подклассы были как можно меньше. У меня в этом модуле 12 классов на 4 уровнях, но фактического кода всего 140 строк. Архитектура почти тривиальна для объяснения, отражает предметную область и заканчивается «листовыми» классами, которые настолько малы, что их обычно можно разместить на одном экране даже с их (обширной) документацией.
 – 
Instance Hunter
16 Окт 2009 в 20:09
Я думаю, что 90% -ное решение будет связано с советом Алекса Мартелли, но для упрощения сделайте так, чтобы подклассы никогда не меняли ничего, что влияет на свойство b. Затем я могу просто вызвать валидатор в конце каждого конструктора.
 – 
Instance Hunter
16 Окт 2009 в 20:11

3 ответа

Лучший ответ

Добавьте метод _validate_b(self, b) (одинарное подчеркивание в начале для обозначения «защищенного», т. Е. Вызываемого из производных классов, но не из общего клиентского кода), который проверяет значение b (которое известно только подклассам) по сравнению со значением a (которое абстрактный суперкласс знает).

Сделайте подклассы ответственными за вызов метода проверки всякий раз, когда они делают что-то, что может изменить их внутреннее значение для b. В вашем очень общем случае суперкласс не может определить, когда значение b изменяется; поскольку ответственность за это изменение полностью лежит на подклассах, то ответственность за запуск проверки также должна быть на них (суперкласс может выполнить проверку, учитывая предложенное новое значение b, но он не может знать < em> когда необходимо проверить правильность). Итак, четко задокументируйте это.

Если большинство подклассов попадают в широкие категории с точки зрения стратегий, которые они используют для воздействия на их значения b, вы можете разделить это на любой из промежуточных абстрактных классов (унаследованных от общего и специализирующихся на общем подходе к «определению b», включая проверку call) или (лучше, если это возможно) какой-либо формы шаблона проектирования стратегии (обычно реализуемой либо через композиционное, либо смешанное наследование). Но это больше связано с удобством (для авторов конкретных подклассов), чем с «гарантией правильности», поскольку данный конкретный подкласс может обходить такие механизмы.

Если вам нужно, вы можете предложить «режим отладки / тестирования», в котором свойства проверяются с избыточностью при доступе (вероятно, не рекомендуется в производственном использовании, но для отладки и тестирования это поможет отловить ошибочные подклассы, которые неправильно вызывают методы проверки).

3
Alex Martelli 16 Окт 2009 в 19:43
@ Дэниел, пожалуйста! Хотелось бы, чтобы был способ лучше автоматизировать это, но я действительно думаю, что этот более явный подход будет более разумным для ваших целей.
 – 
Alex Martelli
16 Окт 2009 в 23:33

Золотое правило - «инкапсулировать» property_b так, чтобы подкласс обеспечивал часть реализации, но не всю ее.

class AbstractClass:
    def __init__(self, property_a):
        self._value_of_a = property_a

    @property
    def get_b( self ):
       self.validate_a()
       self._value_of_b = self.compute_b()
       self.validate_other_things()
       return self._value_of_b

   def compute_b( self ):
       raise NotImplementedError

Трудно сказать точно, что должно произойти, когда у вас два класса и вы спрашиваете о распределении ответственности.

Похоже, вы хотите, чтобы суперкласс отвечал за некоторые аспекты отношений между a и b.

Похоже, что вы хотите, чтобы подкласс отвечал за какой-то другой аспект вычисления b, а не за отношения.

Если это то, что вы хотите, тогда в вашем проекте должна быть возложена ответственность путем декомпозиции вещей на часть, за которую отвечает суперкласс, и на часть, за которую отвечает подкласс.

1
S.Lott 16 Окт 2009 в 21:57
Это дает мне другие поводы для размышлений. Спасибо, что придерживались этого, несмотря на мое неясное объяснение.
 – 
Instance Hunter
16 Окт 2009 в 21:43
Неясное объяснение (иногда) симптом чего-то простого, скрытого замешательством или бесполезной сложности. В 90% случаев сложность является результатом неправильного распределения ответственности. Часто это происходит потому, что обязанности или участники не указаны просто и четко. Упрощая и разъясняя свое объяснение, вы избавляетесь от ненужной сложности. Продолжайте упрощать.
 – 
S.Lott
16 Окт 2009 в 21:56

Я предлагаю вам не вызывать raise NotImplementedError, а вместо этого вызвать метод, который вызывает эту ошибку. Затем подклассы должны переопределить этот метод (вместо property_b). В property_b вы вызываете метод, а затем проверяете результат.

Обоснование: вам следует проверить значение как можно скорее (когда кто-то его изменит). В противном случае может быть установлено недопустимое значение, что вызовет проблему намного позже в коде, когда никто не может сказать, как оно туда попало.

В качестве альтернативы вы можете сохранить значение и трассировку стека. Когда значение используется, вы можете затем проверить значение и распечатать исходную трассировку стека, поскольку «здесь было изменено значение».

0
Aaron Digulla 16 Окт 2009 в 18:37
Думаю, возможно, я был несколько неясен. Это относится к случаю 3. Свойство b - не то, что действительно нужно проверять. Действительность свойства b совершенно не зависит от свойства a.
 – 
Instance Hunter
16 Окт 2009 в 18:39
Кроме того, когда свойство a установлено, его невозможно проверить. Свойство b должно быть определено до того, как свойство a можно будет проверить.
 – 
Instance Hunter
16 Окт 2009 в 18:39
Вы должны выполнить проверку, как только у вас появятся оба свойства. Создайте метод проверки и вызовите его из обоих установщиков, как только оба свойства станут действительными. Сделайте это в базовом классе и пусть производные классы реализуют вспомогательные методы, чтобы избежать дублирования кода.
 – 
Aaron Digulla
16 Окт 2009 в 19:07