Python Объектно-ориентированное программирование: композиция - PullRequest
2 голосов
/ 05 января 2020

Я изучал, как реализовать композицию в моем python программировании, но я изо всех сил пытаюсь понять, почему она предпочтительнее наследования.

Например, вот мой код:

class Particle:
   # Constructor (public)
   def __init__(self, _name, _charge, _rest_energy, _mass, _velocity):
       # Attributes (private)
       self.__name = _name
       self.__charge = _charge
       self.__restEnergy = _rest_energy
       self.__mass = _mass
       self.__velocity = _velocity

   # Getter functions (public)
   def getName(self):
       return self.__name

   def getCharge(self):
       return self.__charge

   def getRestEnergy(self):
       return self.__restEnergy

   def getMass(self):
       return self.__mass

   def getVelocity(self):
       return self.__velocity

   # Setter procedures (public)
   def setName(self, _name):
       self.__name = _name

   def setCharge(self, _charge):
       self.__charge = _charge

   def setRestEnergy(self, _rest_energy):
       self.__restEnergy = _rest_energy

   def setMass(self, _mass):
       self.__mass = _mass

   def setVelocity(self, _velocity):
       self.__velocity = _velocity


class Quark:
   # Constructor (public)
   def __init__(self, _name, _charge, _strangeness):
       # Attributes (private)
       self.__name = _name
       self.__charge = _charge
       self.__strangeness = _strangeness

   # Getter functions (public)
   def getName(self):
       return self.__name

   def getCharge(self):
       return self.__charge

   def getStrangeness(self):
       return self.__strangeness


class Hadron:
   # Constructor (public)
   def __init__(self, _name, _charge, _rest_energy, _mass, _velocity, _quarks):
       # Attributes (private)
       self.__particle = Particle(_name, _charge, _rest_energy, _mass, _velocity)
       self.__quarks = _quarks

   # Getter functions (public)
   def getParticle(self):
       return self.__particle

   def getQuark(self):
       return self.__quarks

   def getStrangeness(self):
       _quarks = self.__quarks
       _strangeness = 0
       for _quark in _quarks:
           _strangeness += _quark.getStrangeness()
       return _strangeness

   def getRelCharge(self):
       _quarks = self.__quarks
       _relCharge = 0
       for _quark in _quarks:
           _relCharge += _quark.getCharge()
       return _relCharge

   def getName(self):
       return self.__particle.getName()

   def getCharge(self):
       return self.__particle.getCharge()

   def getRestEnergy(self):
       return self.__particle.getRestEnergy()

   def getMass(self):
       return self.__particle.getMass()

   def getVelocity(self):
       return self.__particle.getVelocity()

   # Setter functions (public)
   def setName(self, _name):
       self.__particle.setName(_name)

   def setCharge(self, _charge):
       self.__particle.setCharge(_charge)

   def setRestEnergy(self, _rest_energy):
       self.__particle.setRestEnergy(_rest_energy)

   def setMass(self, _mass):
       self.__particle.setMass(_mass)

   def setVelocity(self, _velocity):
       self.__particle.setVelocity(_velocity)

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

Am Я что-то не так делаю?

Ответы [ 2 ]

6 голосов
/ 05 января 2020

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

Композиция не всегда лучший вариант. «Композиция по наследованию» часто повторяется, потому что наследование часто злоупотребляет идеей, что это уменьшает объем кода, который вам нужно написать. Это совершенно неправильная мотивация, чтобы основывать свое решение на этом.

Если у вас есть два класса, A и B, общее общее руководство:

  • Если B является A, вы, вероятно, хотите наследование.
  • Если B имеет A, вы, вероятно, хотите композицию.

В вашем случае, от моего чрезвычайно ограниченное знание физики элементарных частиц, Hadron - это Particle, поэтому наследование, вероятно, лучше подходит. A Hadron не содержит / , имеет a Particle, поэтому я думаю, что вы пытаетесь противодействовать зерну, вызывая композицию здесь.

3 голосов
/ 07 января 2020

Карцигеникат уже дал очень хороший ответ. Я просто хотел добавить кое-что об этой части:

Я изо всех сил пытаюсь понять, почему (композиция) предпочтительнее наследования.

На самом деле, речь идет о композиции / делегировании, не только композиция (которая сама по себе не обеспечивает те же функции). Дело в том, что наследование на самом деле служит двум целям: подтипу и повторному использованию реализации.

Подтип обозначает отношение «является» - если B является (правильным) подтипом A, вы можете использовать B везде, где можете использовать A. На самом деле Принцип замещения Лискова выражает это наоборот: «B является правильным подтипом A, если любой код, принимающий A, может вместо этого принять B ". Обратите внимание, что это ничего не говорит о наследовании и реализации, и что ни один из них не требуется (на теоретическом уровне) для подтипирования.

Теперь со статически типизированными языками у вас есть использовать наследование для подтипа, точка - и вы в конечном итоге получите повторное использование реализации в качестве бонуса, если есть какая-либо реализация, чтобы хотя бы повторно использовать.

OTHO Python для динамической типизации не требуется наследование для подтипирования (если, конечно, код, использующий ваш объект, не выполняет никакой проверки типов) - достаточно иметь совместимый интерфейс. Таким образом, в Python наследование главным образом связано с повторным использованием реализации.

Теперь, технически, повторное использование реализации через наследование является формой композиции / делегирования - ваш объект является экземпляром своего собственного класса, но также и всех его суперклассов, и любой атрибут не разрешен ни в вашем экземпляре, ни в этот класс будет проверен на родительских классах. Основное отличие от «ручного» составления / делегирования заключается в том, что наследование более ограничено - вы не можете изменить, кому вы делегируете, например, во время выполнения или для каждого экземпляра (ну ... в Python, вы на самом деле может , технически, изменить класс экземпляра во время выполнения, но на практике это очень плохая идея и никогда не работает как положено - здесь, делали это xD).

Таким образом, при повторном использовании / реализации реализация наследования является ограниченной и в основном статической c формой составления / делегирования. Это хорошо для довольно многих случаев использования - обычно для создания более или менее абстрактных базовых классов, которые должны быть унаследованы, et c, - но некоторые другие случаи лучше решать с помощью более динамичного c решения (канонические примеры являются шаблонами разработки состояния и стратегии, но есть множество других).

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

Типичный пример "ошибки новичка" наследовать от некоторого встроенного типа коллекции (скажем, list, например) и пытаться "ограничить" его конкретными потребностями c. Это никогда не работает должным образом и обычно требует гораздо больше работы, чем использование композиции / делегирования. Затем они понимают, что (по-прежнему, например) OrderedDict был бы гораздо лучшей основой для их собственного варианта использования, а затем у них возникает проблема с тем, что клиентский код теперь зависит от унаследованного list интерфейса ... Использование композиции / делегирования с самого начала предотвратило бы много боли - оставив интерфейс ограниченным соответствующими функциями вместо утечки унаследованного интерфейса и сохранив элемент «повторного использования реализации» таким, какой он есть на самом деле: деталь реализации о том, что клиентский код никогда не должен был знать.

Основная проблема на самом деле заключается в очень многих очень плохих текстах «OO 101», которые представляют наследование как одну из ключевых функций OO (а это не так - настоящая «Ключевыми OO-функциями» являются инкапсуляция - не путать с сокрытием данных BTW - и диспетчеризация на основе типов polymorphi c), что приводит к тому, что новички пытаются злоупотреблять наследованием, даже не осознавая, что существуют другие - а иногда и гораздо лучшие - решения.

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

О, да: вы можете узнать о __getattr__ волхвах c методе (для делегирования) - и протокол дескриптора и встроенный тип property (для поддержки вычисляемых атрибутов), пока вы в нем (подсказка: вам не нужны эти приватные атрибуты / publi c средства доступа в Python) .

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