Состав наследства составляют: 1.3. Состав наследства

Содержание

Ст. 1156 ГК РФ. Переход права на принятие наследства (наследственная трансмиссия)

1. Если наследник, призванный к наследованию по завещанию или по закону, умер после открытия наследства, не успев его принять в установленный срок, право на принятие причитавшегося ему наследства переходит к его наследникам по закону, а если все наследственное имущество было завещано — к его наследникам по завещанию (наследственная трансмиссия). Право на принятие наследства в порядке наследственной трансмиссии не входит в состав наследства, открывшегося после смерти такого наследника.

2. Право на принятие наследства, принадлежавшее умершему наследнику, может быть осуществлено его наследниками на общих основаниях.

Если оставшаяся после смерти наследника часть срока, установленного для принятия наследства, составляет менее трех месяцев, она удлиняется до трех месяцев.

По истечении срока, установленного для принятия наследства, наследники умершего наследника могут быть признаны судом принявшими наследство в соответствии со статьей 1155 настоящего Кодекса, если суд найдет уважительными причины пропуска ими этого срока.

3. Право наследника принять часть наследства в качестве обязательной доли (статья 1149) не переходит к его наследникам.

См. все связанные документы >>>

1. Возможны случаи, когда наследник, призванный к наследованию, умирает вслед за наследодателем, не успев принять наследство в установленный срок или отказаться от него. Тогда право на принятие наследства умершего наследника переходит к его наследникам по закону, а если все наследственное имущество было завещано — к его наследникам по завещанию. Такой переход называется наследственной трансмиссией.

Он возможен при наличии двух условий: если наследство открыто (наследодатель умер или был объявлен умершим) и если вслед за наследодателем умирает наследник, не успев в установленный законом срок принять наследство или отказаться от него. Закон особо подчеркивает, что право на принятие наследства в порядке наследственной трансмиссии не входит в состав наследства, открывшегося после смерти такого наследника, не успевшего принять наследство в установленный срок.

Это означает, что речь идет о принадлежащих наследнику самостоятельных правах: о праве на принятие наследства в порядке наследственной трансмиссии и о правах на принятие основного наследства. Поэтому наследник имеет возможность принять оба наследства, либо принять наследство в порядке наследственной трансмиссии и отказаться от основного наследства, либо принять основное наследство и отказаться (не принимать) от наследства в порядке наследственной трансмиссии, либо отказаться от обоих.

Если наследник, не принявший наследство, умер после истечения срока, установленного для принятия наследства, и при жизни не подал заявление в суд о восстановлении пропущенного срока для принятия наследства, то наследование в порядке наследственной трансмиссии не возникает. Право на принятие наследства в порядке наследственной трансмиссии не возникает также, если имеется завещание наследодателя, в котором он подназначил другого наследника на случай, если наследник умрет до открытия наследства, не успев его принять (см. комментарий к ст. 1121 ГК).

2. Пункт 2 комментируемой статьи устанавливает срок для принятия наследства, который должен быть не менее трех месяцев со дня смерти умершего наследника, не успевшего принять наследство (трансмитента). В случае когда этот срок поглощается общим сроком принятия наследства, он может составлять и более трех месяцев (например, наследодатель умер 20 марта, а трансмитент 15 апреля — в этой ситуации срок для нового наследника будет подчиняться общим правилам о шести месяцах и продлится до 20 сентября).

В случае когда трансмитент умирает менее чем за три месяца до окончания общего шестимесячного срока, новый срок продлевается за пределы общего (например, наследодатель умер 20 марта, трансмитент — 15 сентября, в этой ситуации срок для нового наследника продлится до 15 декабря).

3. Наследственная трансмиссия не применяется при необходимом (обязательном) наследовании, т.е. когда в качестве умершего наследника (трансмитента) выступает лицо, лишенное или ограниченное наследодателем в наследственных правах, но тем не менее получающее согласно закону часть наследственной массы в обязательном порядке (см. комментарий к ст. 1149).

как передать ценное имущество наследникам :: Финансы :: РБК

Первое поколение российских богачей приближается к возрасту, когда пора думать о передаче активов следующему поколению. РБК разобрался в тонкостях процедуры передачи зарубежного и российского имущества наследникам

Фото: Toru Hanai / Reuters

Средний возраст бизнесменов из рейтинга Forbes «200 богатейших бизнесменов России» сейчас составляет 54,3 года, при этом среди них 45 человек, перешедших через 60-летний рубеж, и пять миллиардеров, которым уже за 70. Между тем средняя продолжительность жизни у россиян, по последним данным Росстата, составляет 72,4 года. Собеседники РБК из сферы private banking подтверждают, что сейчас их состоятельные клиенты все больше задумываются о передаче своих активов наследникам. При этом богатые россияне предпочитают составлять завещания, подробно указывая, какие именно активы и кому они передают. Такой подход облегчает и ускоряет процедуру наследования, а нотариус, ведущий дело, может более адекватно оценить завещанное имущество.

«Культура наследования в нашей стране возрождается. Наследственный механизм помогает избежать ситуаций, когда наследники не могут поделить между собой состояние умершего родственника и все процедуры затягиваются, а активы за это время сгорают: акции могут обесцениться, а бизнес-партнеры обанкротить фирму. В итоге наследникам ничего не достается», — говорит руководитель практики наследования UFG Wealth Management Екатерина Маркова.​

Что российские миллиардеры заявляли по поводу своего наследства

По данным швейцарского банка Julius Baer, 60% состоятельных россиян инвестируют преимущественно в собственный бизнес, а 40% — в основном в ликвидные активы (акции, облигации, денежные средства) и недвижимость. РБК разобрался, как выглядит процедура передачи зарубежных и российских активов наследникам.

Тонкости трансграничного наследования



Главная сложность при завещании активов связана с тем, что имущество наследодателя может находиться в иностранных юрисдикциях. Согласно недавнему исследованию международного брокера недвижимости Tranio и британской компании Adam Smith Conferences, чаще всего богатые клиенты отдают предпочтение Кипру, Великобритании, Монако, Мальте и Швейцарии.

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

Недвижимость наследуется согласно правовым нормам страны, в которой такое имущество находится. Таким образом, особняк в Лондоне будет передаваться наследникам в соответствии с законодательством Великобритании. «Российский нотариус не будет выписывать свидетельство о вступлении в наследство имуществом в Великобритании. Он выпишет свидетельство о вступлении в наследство в России, а далее нужно будет проходить эту процедуру в Великобритании», — приводит пример ​руководитель Sberbank Private Banking Евгения Тюрикова.​

Разница в законодательстве о наследовании в разных странах может быть весьма существенной, отмечают опрошенные РБК эксперты. Во-первых, это касается налога, который платит наследник, получая имущество. Так, в Великобритании базовая ставка налога на наследство составляет 40% при превышении общей суммы активов в £325 тыс. При этом активы, переданные супругу, независимо от суммы, налогом не облагаются.

Налоги на наследование на Кипре и Мальте отсутствуют, а в Монако от налогообложения освобождено только наследование между родителями и детьми, а также между супругами. В России вместе с активами наследники получают и все связанные с ними долги. Так же обстоят дела и в Швейцарии, где правопреемник несет ответственность за погашение всех долгов. В Великобритании, наоборот, из наследственной массы сначала оплачиваются долги, а уже потом чистые активы без обременений переходят наследнику, отмечает Екатерина Маркова.

В зависимости от юрисдикции различаются процедура и срок вступления в право наследования, подчеркивает Евгения Тюрикова. «В России процедуру ведет нотариус, и срок вступления в право наследования в норме составляет шесть месяцев с даты смерти наследодателя. В других странах процедуру могут вести чиновники муниципалитетов, судьи, особые юристы, и срок может составлять от трех месяцев до года», — объясняет эксперт.

На Кипре, например, нотариусы отсутствуют в принципе, поэтому ведение наследственных дел относится к компетенции специального суда, добавляет юрист UFG Wealth Management Николай Варгасов.

Также в разных юрисдикциях по-разному определяются степени родства между наследниками и наследодателями. «В России братья и сестры отнесены ко второй очереди наследования, а в других странах могут быть отнесены к первой или третьей очереди наследования», — отмечает Евгения Тюрикова.

В Великобритании первый претендент на наследство — супруг. Затем следуют дети умершего, родители, братья и сестры. В Швейцарии в ходу несколько иные принципы. «Швейцарский закон различает три группы законных наследников: потомки завещателя, родители наследодателя и бабушки и дедушки наследодателя. В пределах одной группы наследники наследуют в равной степени. В дополнение к упомянутым группам оставшийся в живых супруг всегда является законным наследником», — объясняет управляющий директор Julius Baer по региону России, Центральной и Восточной Европы Евгений Смушкович.

Еще в России, как и в некоторых других странах, существует понятие обязательной доли — малолетние дети, например, не могут быть лишены наследства вообще, даже завещанием. Поэтому завещать «все старшему сыну» в России нельзя. Если младшие дети — несовершеннолетние, они тоже будут иметь право на половину того, что им причиталось бы по закону, добавляет Тюрикова.

Дополнительные сложности при наследовании зарубежных активов могут возникнуть в связи с разными концепциями структурирования владения зарубежными активами, например в случае если наследодатель владел акциями иностранных компаний не напрямую, а через номинальных акционеров, отмечает юрист практики оказания услуг частным клиентам PwC Legal Мария Гришина.

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

Завещание или траст?

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

Учредитель траста может включить своих родственников в список бенефициаров. Соответственно, после его смерти они продолжат получать доход от имущества, переданного в траст. Главное, убедиться, что выбранный управляющий будет честно выполнять свою работу по распоряжению имуществом.

«При передаче имущества в траст при жизни учредителя имущество выбывает из его собственности и не включается в состав наследства, — поясняет Мария Гришина. — Траст и иные персональные холдинговые механизмы скорее представляют собой самостоятельный способ передачи активов будущим поколениям, альтернативу наследованию как таковому».

Евгения Тюрикова напоминает, что создать траст можно далеко не в каждой юрисдикции (в России и Монако, например, такой механизм законодательно не предусмотрен). Однако куда более серьезным отличием траста от процедуры завещания банкир считает механизм перехода прав на имущество. «При классической процедуре наследования переходит право собственности на имущество, при использовании траста — право собственности на доход от имущества», — объясняет она.

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

Ключевое отличие семейного фонда от траста состоит в том, что последний в отличие от фонда не образует юридическое лицо. Кроме того, траст не позволяет оставить за его учредителем существенный уровень контроля, так как ключевой характеристикой траста является независимость назначенного управляющего, объясняет юрист UFG Wealth Management Николай Варгасов.

«Семейный фонд позволяет более гибко отрегулировать систему корпоративного управления, оставляя при желании существенный уровень контроля за учредителем. По общему правилу частные фонды дороже трастов в учреждении и обслуживании, поэтому выбор наследственного механизма в том числе зависит от размера активов семьи», — делает вывод эксперт.

По мнению Анастасии Асташкевич, при наличии активов за рубежом трасты и семейные фонды выглядят более предпочтительным вариантом, чем завещание. «Семейные фонды дают возможность не приостанавливать управление активами и являются наиболее гибкой формулой передачи наследства. Трасты менее гибки, но они выгодны, если наследники не планируют принимать активное участие в управлении активом», — поясняет юрист.

Фото: Simon Dawson / Bloomberg

Как быть с российскими активами



В России получить наследство можно двумя путями — по завещанию (согласно 62-й главе ГК РФ) либо по основной процедуре наследования, прописанной в 63-й главе ГК РФ. В первом случае все права на владение активами наследодателя получает человек, указанный в завещании.

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

Как и в иностранных юрисдикциях, в России можно завещать и бизнес, и недвижимость, и ценные бумаги. В последнем случае особенно важно идентифицировать передаваемые активы. «В завещании должно быть указано, что это за ценные бумаги, какой у них номер выпуска», — рассказывает партнер адвокатского бюро А2 Михаил Александров.

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

Распоряжаться полученным имуществом человек сможет лишь после того, как нотариус, проведя все положенные процедуры проверки, выдаст свидетельство о наследовании. По словам опрошенных адвокатов, длится этот процесс полгода. При передаче в наследство ликвидных активов (ценных бумаг и денежных средств) это создает некоторые проблемы, замечает руководитель отдела юридического сопровождения ГК «Финам» Сергей Володькин.

«С момента открытия наследства (то есть смерти наследодателя) зачастую происходит много событий (зачислений, списаний, сделок, операций), меняющих ценность имущества. И нотариусы, действуя строго по закону, отказываются это учитывать и выдают свидетельства именно на ту наследственную массу, которая была на момент смерти», — говорит эксперт.

Также важно учитывать, что статья 1128 Гражданского кодекса предусматривает несколько вариантов наследования депозитов. Как пояснил начальник юридического управления СДМ-банка Александр Голубев, владелец банковских вкладов, желающий передать эти деньги наследникам, может составить завещание или завещательное распоряжение.

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

Если же человек оставил после себя оба документа, то нотариус будет руководствоваться тем, который составлен позже. При отсутствии завещания, как и любое наследство, вклад делится между родственниками в зависимости от степени родства.

«Ни разу на моей практике не встречались случаи, когда у человека возникали трудности с получением вклада в наследство. Этот процесс хорошо отрегулирован. Наследнику нужно обратиться к нотариусу с заявлением о наследстве и спустя шесть месяцев получить наследство», — говорит финансовый омбудсмен Павел Медведев.

Перечисление средств на счет нового вкладчика происходит по свидетельству о праве на наследство, выданному нотариусом. По словам финансового омбудсмена, наследник получит и сам вклад, и начисленные на него проценты. При этом условия вклада (срок, ставка, возможность закрытия) остаются неизменны для нового владельца.

Инвестор может выбрать и другой вариант передачи активов своим родственникам — не через завещание, а дарение. Однако в этом случае, предупреждает юрист Михаил Александров, договор дарения может быть оспорен иждивенцами, которым полагается доля в наследстве. «Например, если ценные бумаги подарили дальнему родственнику или не члену семьи, прямые наследники могут заявить, что оформление договора дарения является мнимой сделкой», — поясняет эксперт.

Что делать наследнику

Если речь идет о наследстве за рубежом, то наследнику придется обращаться в ту юрисдикцию, где хранятся завещанные активы. «Сначала следует дождаться получения свидетельства о праве на наследство, затем на основании данного документа, получив апостиль и заверив перевод, следует обратиться к нотариусу зарубежного государства, где зарегистрирован актив», — перечисляет необходимые шаги Анастасия Асташкевич. Далее все зависит от конкретной страны и ее законодательства о наследовании.

В России, согласно части 3 ГК РФ, наследник должен подать заявление об оформлении наследства нотариусу в течение шести месяцев после смерти родственника. В исключительных случая этот срок может быть увеличен, например если наследник долгое время проживает в другой стране или в принципе не знает о смерти родственника. Срок восстановления права на вступление в наследство в зависимости от ситуации может составлять от года до трех лет. Для этого наследнику придется обратиться в суд и предоставить доказательства того, что причины, по которым был пропущен шестимесячный срок, действительно уважительные — то есть он проживал за границей или не знал о смерти родственника.​

Процесс передачи активов (как бизнеса, так и недвижимости и ценных бумаг) новому владельцу потребует определенных финансовых затрат от него. Во-первых, в России наследникам из числа дальних родственников и людей, не являющихся членами семьи, придется заплатить подоходный налог (13%). Близким родственникам наследство передается безвозмездно, говорит финансовый омбудсмен Павел Медведев.

Во-вторых, при передаче наследства в виде ценных бумаг регистратор должен открыть новый счет для наследника, на который необходимо перевести завещанные активы. «Если наследник открывает счет в организации, в которой имел счет завещатель, то операция перевода, как правило, бесплатная. Если наследник хочет перевести ценные бумаги на счет другой организации, средний процент за операцию составляет от 0,5 до 1% от перечисляемой суммы», — рассказывает он. После получения ценных бумаг преемник вправе продать их. Монетизировать эти активы, получив вместо акций в качестве наследства эквивалентную сумму денежных средств, невозможно, предупреждают эксперты.

Самострои не могут входить в состав наследства: решение ВС

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

Соответствующее положение содержится в постановлении КГС ВС от 9 июня 2020 года по делу № 347/2273/17. С его полным текстом можно ознакомиться в системе анализа судебных решений VERDICTUM. Тестовый доступ в систему можно оформить здесь.

Обстоятельства дела

Постановлением частного нотариуса лицу отказано в выдаче свидетельства о праве на наследства по закону на обязательную долю в наследстве на 1/2 часть жилого дома, 1/2 часть земельного участка в связи отсутствием документов, необходимых для совершения нотариального действия.

Лицо не согласилось с таким решением нотариуса, обратилось в суд. Удовлетворяя иск, суд первой инстанции исходил из того, что истец является единственным наследником первой очереди, неработоспособной матерью умершей, потому имеет право на наследование обязательной части в наследственном имуществе, которая составляет 1/2 часть и в установленный срок обратилась к нотариусу с заявлением о принятии наследства.

Апелляционный суд отменил решение суда предыдущей инстанции.

Лицо подало кассационную жалобу.

Позиция Верховного Суда

ВС оставил кассационную жалобу без удовлетворения, учитывая следующее.

Если строительство объекта недвижимого имущества осуществлялось по закону, то в случае смерти застройщика до завершения строительства его права и обязанности как застройщика входят в состав наследства на основании статьи 1216 ГК.

Выпиской из инвентаризационных материалов подтверждено, что согласно архивных данных, за умершей зарегистрированы на праве собственности: жилой дом, погреб, навес, колодец, ограждение.

Других хозяйственных зданий не указано.

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

ЛІГА:ЗАКОН разработала LIGA360:АДВОКАТ — комплексное решение, которое сочетает главные инструменты в едином рабочем пространстве для адвоката:

— полная база НПА;

— комментарии к кодексам;

— 85 млн судебных решений;

— подобные дела;

— правовые позиции Верховного Суда;

— 6,5 млн досье компаний и ФЛП.

Заказывайте доступ со скидкой 30 %.

Наследство, вопрос-ответ — Юридический центр

ПРИНЯТИЕ НАСЛЕДСТВА

(КАК ПРИНЯТЬ НАСЛЕДСТВО?)

Чтобы приобрести наследство, наследник должен его принять в течение шести месяцев со дня открытия наследства. Принять наследство можно двумя способами (ст. 1153 ГК РФ):

— подать заявление нотариусу;

— или совершить действия, свидетельствующие о фактическом принятии наследства.

Принятие наследства путем подачи заявления нотариусу

Для принятия наследства в течение шести месяцев со дня смерти наследодателя необходимо подать соответствующее заявление нотариусу: заявление о принятии наследства или заявление о выдаче свидетельства о праве на наследство.

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

За выдачу свидетельства о праве на наследство, взымается госпошлина (или нотариальный тариф).

ВАЖНО: От уплаты госпошлины освобождаются, наследники, не достигшие совершеннолетия ко дню открытия наследства, а также лица при наследовании квартиры, если они проживали совместно с наследодателем на день его смерти и продолжают проживать в этой квартире после его смерти.

В случае наследовании по завещанию выдается свидетельство о праве на наследство по завещанию;

В случае наследовании по закону — свидетельство о праве на наследство по закону.

Свидетельства о праве на наследство , на некоторые объекты имущества подлежат государственной регистрации.

Фактическое принятие наследства

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

Кроме того, доказательствами о фактическом принятии наследства, являются : справка жилищно-эксплуатационной организации о проживании в объекте недвижимости, квитанции об уплате налогов, договоры о проведении ремонта в квартире.

ВАЖНО:

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

Другие вопросы:

Как установить факт принятия наследства?

Как признать завещание недействительным?

Как восстановить срок принятия наследства?

Каков срок принятия наследства?

 

УСТАНОВЛЕНИЕ ФАКТА ПРИНЯТИЯ НАСЛЕДСТВА

(КАК УСТАНОВИТЬ ФАКТ ПРИНЯТИЯ НАСЛЕДСТВА?)

Для приобретения наследства наследник должен его принять. Принятое наследство признается принадлежащим наследнику со дня открытия наследства независимо от времени его фактического принятия, а также независимо от момента государственной регистрации права наследника на наследственное имущество.

Способы принятия наследства подача по месту открытия наследства нотариусу заявление о принятии наследства или заявление наследника о выдаче свидетельства о праве на наследство;

совершение действий, свидетельствующих о фактическом принятии наследства, если

— наследник вступил во владение или в управление наследственным имуществом;

-наследник принял меры по сохранению наследственного имущества;

— наследник произвел за свой счет расходы на содержание наследственного имущества;

-наследник оплатил за свой счет долги наследодателя;

Во всех случаях общий срок принятия наследства составляет шесть месяцев со дня его открытия наследства.

Установление факта принятия наследства — означает определить, что наследство было своевременно принято путем совершения действий, свидетельствующих о фактическом его принятии. Для того , чтобы получить свидетельство о праве на наследство по истечении срока, установленного для принятия наследства.

При этом получение свидетельства о праве на наследство — это ваше право, а не обязанность.

Для того чтобы установить факт принятия наследства и получить свидетельство о праве на наследство по истечении шестимесячного срока, необходимо:

Обратиться к нотариусу за свидетельством о праве на наследство, в случае отказа и при наличии справки об отказе в выдаче свидетельства о праве на наследство, что является основанием для обращения с соответствующим заявлением в суд.

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

Другие вопросы:

Как принять наследство?

Как признать завещание недействительным?

Каков срок принятия наследства?

Как восстановить срок принятия наследства?

 

ПРИЗНАНИЕ НЕДЕЙСТВИТЕЛЬНОЕ ЗАВЕЩАНИЕ

(КАК ПРИЗНАТЬ ЗАВЕЩАНИЕ НЕДЕЙСТВИТЕЛЬНЫМ)

В зависимости от основания завещание может быть недействительным в силу признания его таковым судом (оспоримое завещание) или независимо от такого признания (ничтожное завещание).

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

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

Основаниями для признания завещания недействительным может являться :

подпись завещателя подделана кем либо;

завещатель в момент составления завещания страдал заболеваниями, которые не позволяли ему понимать значение своих действий;

завещание составлено с нарушениями в его оформлении, и др.

ВАЖНО: Не могут быть основание: описки и другие незначительные нарушения порядка его составления, подписания или удостоверения, если суд установил, что они не влияют на понимание волеизъявления завещателя.

По общему правилу рассматривает исковое заявление районный суд по месту жительства или месту нахождения ответчика (лицо в пользу которого составлено завещание).

Если истцом заявлены требования о признании права собственности на наследственное имущество, при этом оспаривается завещания, исковое заявление подлежит рассмотрению по месту нахождения объектов недвижимости.

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

В зависимости от обстоятельств дела исковое заявление одновременно с требованием о признании завещания недействительным может содержать, требование о признании недействительным выданного свидетельства о праве на наследство.

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

ВАЖНО: Срок исковой давности по требованию о признании оспоримого завещания

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

Другие вопросы:

Как принять наследство?

Как установить факт принятия наследства?

Каков срок принятия наследства?

Как восстановить срок принятия наследства?

 

СРОКИ ПРИНЯТИЯ НАСЛЕДСТВА

(КАКОВ СРОК ПРИНЯТИЯ НАСЛЕДСТВА ПО ЗАКОНУ И ПО ЗАВЕЩАНИЮ?)

Для приобретения наследства требуется его принять. Принятое наследство признается принадлежащим наследнику со дня открытия наследства независимо от времени его фактического принятия и момента государственной регистрации права наследника на наследственное имущество.

ВАЖНО: Принятие части наследства означает принятие им всего причитающегося ему наследства, в чем бы оно ни заключалось и где бы ни находилось. При призвании наследника к наследованию одновременно по нескольким основаниям он может принять наследство, причитающееся ему по одному из оснований, по нескольким из них или по всем. Не допускается принятие наследства под условием или с оговорками.

Срок принятия наследства не зависит от того, принимается оно по закону или по завещанию.

По общему правилу срок принятия наследства составляет шесть месяцев со дня открытия наследства.

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

При объявлении гражданина умершим днем открытия наследства является день вступления в законную силу решения суда об объявлении гражданина умершим.

Существует два способа принятия наследства : заявление о принятии наследства либо заявление о выдаче свидетельства о праве на наследство;

предпринять действия, свидетельствующие о фактическом принятии наследства.

Считается, что наследник принял наследство, если он, в частности:

— вступил во владение или в управление наследственным имуществом;

— принял меры по сохранению наследственного имущества;

— понес расходы на содержание наследственного имущества;

— оплатил за свой счет долги наследодателя.

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

Оформлять наследство можно в течение неограниченного времени. Срок обращения за получением свидетельства о праве на наследство наследником, принявшим наследство по заявлению о принятии наследства или фактически, не ограничен.

Принятое наследство признается принадлежащим наследнику со дня открытия наследства независимо от времени его фактического принятия, а также независимо от момента государственной регистрации права на наследственное имущество, когда такое право подлежит государственной регистрации.

Другие вопросы:

Как принять наследство?

Как установить факт принятия наследства?

Как признать завещание недействительным?

Как восстановить срок принятия наследства?

 

ВОССТАНОВЛЕНИЕ ПРОПУЩЕННОГО СРОКА ПРИНЯТИЯ НАСЛЕДСТВА

(КАК ВОССТАНОВИТЬ СРОК ПРИНЯТИЯ НАСЛЕДСТВА?)

По общему правилу наследство может быть принято в течение шести месяцев со дня открытия наследства . Пропуск этого срока приводит к утрате права на наследство.

Существует два способа принятия наследства по истечении установленного срока.

Принятие наследства во внесудебном порядке (согласный порядок)

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

ВАЖНО: Если срок пропущен единственным наследником либо все наследники пропустили срок для принятия наследства, восстановление пропущенного срока возможно только в судебном порядке.

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

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

Принятие наследства по истечении установленного срока в судебном порядке

Наследственные споры о восстановлении срока принятия наследства и признании наследника принявшим наследство рассматриваются в порядке искового производства, где в качестве ответчиков привлекаются наследники, приобретшие наследство. В случае их отсутствия в качестве ответчика привлекается субъект Российской Федерации (при наследовании выморочного имущества)

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

Суд удовлетворит требования , если одновременно будут доказаны следующие обстоятельства:

— наследник не знал и не должен был знать об открытии наследства или пропустил указанный срок по другим уважительным причинам. К числу таких причин относят обстоятельства, связанные с личностью истца, например тяжелую болезнь, беспомощное состояние, неграмотность и т.п., если они препятствовали принятию наследником наследства в течение всего срока, установленного для этого законом (ст. 205 ГК РФ). Не являются уважительными такие обстоятельства, как кратковременное расстройство здоровья, незнание гражданско-правовых норм о сроках и порядке принятия наследства, отсутствие сведений о составе наследственного имущества и т.п.;

— наследник, пропустивший срок принятия наследства, обратился в суд в течение шести месяцев после отпадения причин пропуска этого срока. Указанный шестимесячный срок, установленный для обращения в суд с данным требованием, не восстанавливается, и наследник, пропустивший его, лишается права на восстановление срока принятия наследства.

Другие вопросы:

Как принять наследство?

Как установить факт принятия наследства?

Как признать завещание недействительным?

Каков срок принятия наследства?

В каких случаях наследство в виде квартиры получит тот, кто даже не указан в завещании

30.03.2021

На сегодняшний день существуют два основания наследования:

  • Наследование по закону на основании главы 63 ГК РФ.
  • Наследование по завещанию на основании главы 62 ГК РФ.

Распорядиться имуществом на случай смерти можно путем совершения завещания.

Завещание является односторонней сделкой, которая создает права и обязанности после открытия наследства.

В каких случаях наследство в виде квартиры получит тот, кто даже не указан в завещании

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

Как уже было сказано выше, составление завещания- волеизъявление на момент смерти. В нем могут быть указаны любые лица, даже те, с которыми завещатель не имеет родственных отношений.
Но также важно знать и об обязательных наследниках, такими являются несовершеннолетние или нетрудоспособные дети наследодателя, его нетрудоспособные супруг и родители, а также нетрудоспособные иждивенцы наследодателя на основании статья 1149 ГК РФ.

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

В каких случаях наследство в виде квартиры получит тот, кто даже не указан в завещании

Также существует супружеская доля, в соответствии со статьей 1150 ГК РФ, принадлежащее пережившему супругу наследодателя в силу завещания или закона право наследования не умаляет его права на часть имущества, нажитого во время брака с наследодателем и являющегося их совместной собственностью. Доля умершего супруга в этом имуществе, определяемая в соответствии со статьей 256 гражданского Кодекса, входит в состав наследства и переходит к наследникам в соответствии с правилами, установленными гражданским кодексом Российской Федерации.

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

Как итог, можно сделать вывод, что даже если было составлено завещание на квартиру, ее не всегда может получить наследник, который указан в завещании. Исключением может быть выделение обязательной доли наследникам по закону, а также выделением супружеской доли, которая составляет одну вторую долю.

Об авторе

Нотариус города Москвы Теребков Алексей Владимирович

Теребков Алексей Владимирович.

Ведение наследственных дел

Оформление наследственных прав ранее требовало от наследника получения множества справок и документов.

На сегодняшний день законодатель значительно упростил процедуру вступления в права наследования и получение наследственного имущества. Оформление наследства в Санкт-Петербурге значительно упростилось.

Подать заявление о принятии наследства, оставшегося после гражданина, прописанного на момент смерти в Санкт-Петербурге можно у нотариуса любого района города.

Для того, чтобы открыть наследственное дело и оформить наследство, необходимо иметь для первого приема всего 3 документа:

— свидетельство о смерти;
— справка о регистрации (ф9), в которой указано, что умерший снят с регистрационного учета;
— паспорт наследника.

Все остальные документы можно предоставить позднее, на повторных приемах.

Получение справок, подтверждающих принадлежность имущества наследодателю, также возложено на нотариуса.

С 01 февраля 2019 года нотариус, выдавший свидетельство о праве на наследство, обязан незамедлительно, но не позднее окончания рабочего дня подать его в электронной форме для государственной регистрации прав наследников в Едином Государственном Реестре Недвижимости (ЕГРН). Это значительно удешевляет и упрощает процедуру регистрации при оформлении наследства, особенно при оформлении наследства наследниками, не проживающими постоянно в Санкт-Петербурге и наличии в составе наследства недвижимого имущества, находящегося в других регионах Российской Федерации.

При этом не надо забывать, что, по общему правилу, заявление о принятии наследства наследниками, надо подать нотариусу до истечения 6-ти месячного срока с момента смерти наследодателя.

Исключение составляют наследники, прописанные совместно с наследодателем.

Уточнить подробности оформления наследства в Санкт-Петербурге и записаться на прием можно по телефону: +7-812-241-70-00 и +7-812-984-89-02.

Вариант просмотра документа без iframe — Просмотр документа

Номер документа
по регистрации МЮ
строгое соответствие

Вид документа
ЛюбойАнкетаВременная инструкцияВременное положениеВременные нормативыВременные нормыВременные правилаВременный порядокВыпискаДекларацияДоговорДополненияЗаконЗаявлениеИзмененияИзменения и дополненияИнструкцияКлассификаторКлассификацияКодексКомментарийКоммюникеКонвенцияКонституцияКонцепцияМеморандумМероприятияМетодикаМетодические рекомендацииМетодические указанияМетодическое пособиеНоменклатураНормативыНормыОбращениеОграниченияОсновные направленияОсновные принципыПактПереченьПисьмоПлан счетовПоложениеПоправкаПорядокПостановлениеПравилаПриказПриложениеПримерное положениеПримерный договорПримерный уставПринципыПрограммаПротоколРазъяснениеРазъяснительное письмоРаспоряжениеРегламентРезолюцияРекомендацииРешениеСведенияСистемаСитуацияСоглашениеСообщениеСписокСправкаСтавкиСтандартСтратегияСтруктураТелеграммаТехнические требованияТиповое положениеТиповой договорТиповой контрактТиповой проспектТиповой финансовый планТребованиеУказУказанияУсловияУставФормаХартия

Документы:

  • Все
  • действующие
  • утратившие силу

Выбор языка:

  • Русский
  • Ўзбекча
  • Оба языка

Дата документа:

  • любая дата
  • точная дата
  • период

Принявший орган:
Любой
Агентство «Узархив»
Агентство «Узкоммунхизмат»
Агентство информации и массовых коммуникаций
Агентство по внешним экономическим связям
Агентство по интеллектуальной собственности
Агентство по развитию рынка капитала
Агентство по управлению государственными активами
АК Пахта Банк
АКБК «Турон»
Антимонопольный комитет
Ассоциация дехканских и фермерских хозяйств
Ассоциация ”Химпром”
Банк «Замин»
Верховный Совет
Верховный суд
Высшая аттестационная комиссия при КМ РУз
Высший хозяйственный суд
ГАЖК ”Узбекистон темир йуллари”
Генеральная Ассамблея Организации Объединенных Наций
Генеральная прокуратура
Главное архивное управление при КМ РУз
Главное государственное налоговое управление при КМ РУз
Главное налоговое управление г.Ташкента
Главное таможенное управление Государственного налогового комитета
Главное управление геодезии, картографии и государственного кадастра
Главное управление Государственного страхования
Главное управление ЦБ РУз по г.Ташкенту
Главный вычислительный центр
Главный государственный санитарный врач
Госавианадзор (Государственная инспекция по надзору за безопасностью полетов)
Госгортехнадзор
Госкомконкуренции
Государственная инспекция «Саноатгеоконтехназорат»
Государственная инспекция «Саноатконтехназорат»
Государственная инспекция по карантину растений при КМ
Государственная инспекция по надзору за качеством образования при Кабинете Министров
Государственная инспекция по пожарному надзору
Государственная комиссия по контролю за наркотиками
Государственная комиссия по приему в образовательные учреждения
Государственная комиссия по радиочастотам
Государственная межведомственная комиссия по внедрению контрольно-кассовых машин
Государственное патентное ведомство
Государственный банк
Государственный комитет ветеринарии и развития животноводства РУз
Государственный комитет ветеринарии Республики Узбекистан
Государственный комитет по автомобильным дорогам
Государственный комитет по архитектуре и строительству
Государственный комитет по геологии и минеральным ресурсам
Государственный комитет по демонополизации и развитию конкуренции
Государственный комитет по земельным ресурсам
Государственный комитет по земельным ресурсам, геодезии, картографии и государственному кадастру
Государственный комитет по инвестициям
Государственный комитет по лесному хозяйству
Государственный комитет по науке и технике
Государственный комитет по охране природы
Государственный комитет по печати
Государственный комитет по приватизации, демонополизации и развитию конкуренции
Государственный комитет по прогнозированию и статистике
Государственный комитет по развитию туризма
Государственный комитет по содействию приватизированным предприятиям и развитию конкуренции
Государственный комитет по статистике
Государственный комитет по управлению государственным имуществом
Государственный комитет по физической культуре и спортуєє
Государственный комитет по экологии и охране окружающей среды
Государственный комитет промышленной безопасности
Государственный комитет связи, информатизации и телекоммуникационных технологий
Государственный ком
Государственный комитете по лесному хозяйству
Государственный налоговый комитет
Государственный таможенный комитет
Государственный центр тестирования при Кабинете Министров
Законодательная палата Олий Мажлиса
Интеграционный Комитет ЕврАзЭС
Кабинет Министров
Комиссия по лицензированию в сфере транспорта и связи
Комитет по делам об экономической несостоятельности предприятий
Комитет по демонополизации и развитию конкуренции при Министерстве финансов
Комитет по координации развития науки и технологий при КМ РУз
Комитет по охране Государственной границы
Комитет по управлению государственными резервами
Конституционный суд
Межгосударственный Совет ЕврАзЭС
Международная организация труда
Международный фонд экологии и здоровья «Экосан»
Министерство внешней торговли
Министерство внешних экономических связей
Министерство внешних экономических связей, инвестиций и торговли
Министерство внутренних дел
Министерство водного хозяйства
Министерство высшего и среднего специального образования
Министерство дошкольного образования
Министерство занятости и трудовых отношений
Министерство здравоохранения
Министерство инвестиций и внешней торговли
Министерство инновационного развития
Министерство иностранных дел
Министерство коммунального обслуживания
Министерство макроэкономики и статистики
Министерство народного образования
Министерство обороны
Министерство по делам культуры
Министерство по делам культуры и спорта
Министерство по развитию информационных технологий и коммуникаций
Министерство по чрезвычайным ситуациям
Министерство связи
Министерство сельского и водного хозяйства
Министерство сельского хозяйства
Министерство социального обеспечения
Министерство строительства
Министерство транспорта
Министерство труда
Министерство труда и социальной защиты населения
Министерство физической культуры и спорта
Министерство финансов
Министерство экономики
Министерство экономики и промышленности
Министерство экономического развития и сокращения бедности
Министерство энергетики
Министерство энергетики и электрификации
Министерство юстиции
НАК «Узбекистон хаво йуллари»
Народный банк
Национальная гвардия
Национальная компания «Узбектуризм»
Национальная палата инновационного здравоохранения
Национальное агентство «Узбеккино»
Национальное агентство проектного управления при Президенте
Национальный банк
Национальный банк внешнеэкономической деятельности
Олий Мажлис
Организация Объединенных Нацийјј
Палата товапроизводителей и предпринимателей
Пенсионный фонд
Правительственная комиссия по вопросам банкротства и санации предприятий
Правительственная комиссия по совершенствованию механизма расчетов и укреплению дисциплины платежей
Президент Республики Узбекистан
Республиканская комиссия по денежно-кредитной политике при правительстве Республики Узбекистан
Республиканская комиссия по сокращению просроченной дебиторской и кредиторской задолженности УКАЗ
П
Республиканская фондовая биржа «Ташкент»
Республиканский совет по координации деятельности контролирующих органов
Республиканский фонд «Махалля»
Сберегательный банк
Сенат Олий Мажлиса
Служба государственной безопасности
Служба национальной безопасности
Совет глав государств — членов ШОС
Совет глав государств СНГ
Совет глав правительств СНГ
Совет по железнодорожному транспорту СНГ
Совет Федерации профсоюзов Узбекистана
Счетная палата
Ташкентский городской Кенгаш народных депутатов
Узавтойул (Государственно-акционерная компания)
Узавтойул (Концерн)
Узавтотранс
Узбеклегпром
Узбекнефтегаз
Узбексавдо
Узбекская республиканская товарно-сырьевая биржа
Узбекский государственный центр стандартизации, метрологии и сертификации
Узбекское агентство автомобильного и речного транспорта
Узбекское агентство автомобильного транспорта
Узбекское агентство по печати и информации
Узбекское агентство почты и телекоммуникаций
Узбекское агентство связи и информатизации
Узбекское агентство стандартизации, метрологии и сертификации
Узбекэнерго
Узгосжелдорнадзор (Государственная инспекция по надзору за безопасностью железнодорожных перевозок)
Узгоснефтегазинспекция (Государственная инспекция по контролю за использованием нефтепродуктов и газ
Узгосрезерв
Узгосстандарт
Узгосэнергонадзор (Государственная инспекция по надзору в электроэнергетике)
Узгосэнергонадзор (Государственное агентство по надзору в электроэнергетике)
Узжилсбербанк
Узнефтепродукт
Узоптбиржеторг
Узоптплодоовощ
Узплодоовощвинпром
Узтрансгаз
Узфармсаноат
Узхлебопродукт
Узхлопкопром
Фонд социального страхования при Кабинете Министров Республики Узбекистан
Хоким г. Ташкента
Хоким Ташкентской области
Центр по координации и контролю за функционированием рынка ценных бумаг
Центр по координации и развитию рынка ценных бумаг при Госкомконкуренции
Центр профессионального образования
Центр среднего специального, профессионального образования
Центральная избирательная комиссия
Центральный банк
Центральный депозитарий ценных бумаг
Экономический и Социальный Совет ООН
Экономический Суд СНГ
Экспертно-проверочная методическая комиссия при Центральном Госархиве

Текст в названии документа:
Текст в документе:

Руководство по ООП Python — Настоящий Python

В Python все является объектом. Модули — это объекты, определения классов и функции — это объекты, и, конечно же, объекты, созданные из классов, тоже являются объектами.

Наследование является обязательной функцией каждого объектно-ориентированного языка программирования. Это означает, что Python поддерживает наследование, и, как вы увидите позже, это один из немногих языков, поддерживающих множественное наследование.

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

Объект Суперкласс

Самый простой способ увидеть наследование в Python — это перейти в интерактивную оболочку Python и написать небольшой фрагмент кода. Вы начнете с написания простейшего из возможных классов:

>>>

  >>> класс MyClass:
...     проходить
...
  

Вы объявили класс MyClass , который мало что делает, но он иллюстрирует самые основные концепции наследования. Теперь, когда у вас объявлен класс, вы можете использовать функцию dir () для вывода списка его членов:

>>>

  >>> c = MyClass ()
>>> dir (c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
  

dir () возвращает список всех членов в указанном объекте.Вы не указали участников в MyClass , так откуда берется список? Вы можете узнать с помощью интерактивного переводчика:

>>>

  >>> o = object ()
>>> dir (o)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
  

Как видите, два списка почти идентичны.В MyClass есть несколько дополнительных членов, например __dict__ и __weakref__ , но каждый отдельный член класса объекта также присутствует в MyClass .

Это связано с тем, что каждый класс, который вы создаете в Python, неявно является производным от объекта . Вы могли бы быть более явным и написать class MyClass (object): , но это излишне и ненужно.

Примечание. В Python 2 необходимо явным образом наследовать от объект по причинам, выходящим за рамки данной статьи, но вы можете прочитать об этом в разделе «Классические классы нового стиля» документации Python 2.

Исключения являются исключением

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

Вы можете увидеть проблему с помощью интерактивного интерпретатора Python:

>>>

  >>> класс MyError:
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: исключения должны быть производными от BaseException
  

Вы создали новый класс, чтобы указать тип ошибки.Затем вы попытались использовать его, чтобы вызвать исключение. Возникает исключение, но в выходных данных указано, что исключение имеет тип TypeError , а не MyError , и что все исключения должны быть производными от BaseException .

BaseException — это базовый класс, предоставляемый для всех типов ошибок. Чтобы создать новый тип ошибки, вы должны унаследовать свой класс от BaseException или одного из его производных классов. По соглашению в Python пользовательские типы ошибок выводятся из исключения Exception , которое, в свою очередь, является производным от BaseException .

Правильный способ определить тип ошибки следующий:

>>>

  >>> класс MyError (Исключение):
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
__main __. MyError
  

Как видите, когда вы вызываете MyError , в выходных данных правильно указывается тип возникшей ошибки.

Создание иерархии классов

Наследование — это механизм, который вы будете использовать для создания иерархий связанных классов.Эти связанные классы будут иметь общий интерфейс, который будет определен в базовых классах. Производные классы могут специализировать интерфейс, предоставляя конкретную реализацию там, где это применимо.

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

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

Вы начинаете с реализации класса PayrollSystem , который обрабатывает расчет заработной платы:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')
  

Система расчета заработной платы реализует .Метод calculate_payroll () , который берет коллекцию сотрудников и печатает их id , имя и проверяет сумму с помощью метода .calculate_payroll () , доступного для каждого объекта сотрудника.

Теперь вы реализуете базовый класс Employee , который обрабатывает общий интерфейс для каждого типа сотрудника:

  # В часах в год

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя
  

Сотрудник — это базовый класс для всех типов сотрудников.Он построен с идентификатором id и именем . Вы говорите, что каждому Сотруднику должен быть назначен id , а также имя.

Система HR требует, чтобы каждый обработанный Сотрудник предоставлял интерфейс .calculate_payroll () , который возвращает еженедельную зарплату для сотрудника. Реализация этого интерфейса различается в зависимости от типа Сотрудник .

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

  # В час.ру

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary
  

Вы создаете производный класс SalaryEmployee , который наследует Employee . Класс инициализируется идентификатором id и именем , требуемым базовым классом, и вы используете super () для инициализации членов базового класса.Вы можете прочитать все о super () в Supercharge Your Classes With Python super ().

SalaryEmployee также требуется параметр инициализации weekly_salary , который представляет сумму, которую сотрудник зарабатывает в неделю.

Класс предоставляет требуемый метод .calculate_payroll () , используемый системой HR. Реализация просто возвращает сумму, хранящуюся в weekly_salary .

В компании также работают рабочие, получающие почасовую оплату, поэтому вы добавляете HourlyEmployee в систему управления персоналом:

  # В час.ру

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate
  

Класс HourlyEmployee инициализируется идентификатором id и именем , как и базовый класс, плюс hours_worked и hour_rate , необходимые для расчета заработной платы.Метод .calculate_payroll () реализуется путем возврата количества отработанных часов, умноженного на почасовую ставку.

Наконец, в компании работают торговые партнеры, которым выплачивается фиксированная заработная плата плюс комиссия, основанная на их продажах, поэтому вы создаете класс CommissionEmployee :

  # В часах в год

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        себя.комиссия = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы выводите CommissionEmployee из SalaryEmployee , потому что оба класса должны учитывать weekly_salary . В то же время CommissionEmployee инициализируется значением комиссии , которое основано на продажах для сотрудника.

.calculate_payroll () использует реализацию базового класса для получения фиксированной зарплаты и добавляет значение комиссии.

Поскольку CommissionEmployee является производным от SalaryEmployee , у вас есть доступ напрямую к свойству weekly_salary , и вы могли бы реализовать .calculate_payroll () , используя значение этого свойства.

Проблема с прямым доступом к свойству заключается в том, что если реализация SalaryEmployee.calculate_payroll () изменится, вам также придется изменить реализацию CommissionEmployee.calculate_payroll () .Лучше полагаться на уже реализованный метод в базовом классе и расширять функциональность по мере необходимости.

Вы создали свою иерархию первого класса для системы. UML-диаграмма классов выглядит так:

На схеме показана иерархия наследования классов. Производные классы реализуют интерфейс IPayrollCalculator , который требуется для PayrollSystem . Реализация PayrollSystem.calculate_payroll () требует, чтобы переданные объекты сотрудника содержали id , имя и реализацию calculate_payroll () .

Интерфейсы представлены аналогично классам со словом interface над именем интерфейса. Имена интерфейсов обычно начинаются с заглавной буквы I .

Приложение создает своих сотрудников и передает их в систему расчета заработной платы для обработки расчета заработной платы:

  # В program.py

импортные часы

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = hr.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = час.Система начисления заработной платы()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

Вы можете запустить программу в командной строке и посмотреть результат:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

Программа создает три объекта сотрудников, по одному для каждого из производных классов.Затем он создает систему расчета заработной платы и передает список сотрудников своему методу .calculate_payroll () , который рассчитывает заработную плату для каждого сотрудника и распечатывает результаты.

Обратите внимание, что базовый класс Employee не определяет метод .calculate_payroll () . Это означает, что если вы создадите простой объект Employee и передадите его в PayrollSystem , то получите ошибку. Вы можете попробовать это в интерактивном интерпретаторе Python:

>>>

  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, «Недействительный»)
>>> payroll_system = hr.PayrollSystem ()
>>> payroll_system.calculate_payroll ([сотрудник])

Зарплата для: 1 - Недействительно
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
  Файл "/hr.py", строка 39, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
AttributeError: объект "Сотрудник" не имеет атрибута "calculate_payroll"
  

Хотя вы можете создать экземпляр объекта Employee , этот объект не может использоваться системой PayrollSystem .Почему? Потому что не может .calculate_payroll () для Сотрудника . Чтобы соответствовать требованиям PayrollSystem , вам нужно преобразовать класс Employee , который в настоящее время является конкретным классом, в абстрактный класс. Таким образом, ни один сотрудник никогда не будет просто Сотрудником , а будет тот, который реализует .calculate_payroll () .

Абстрактные базовые классы в Python

Класс Employee в приведенном выше примере называется абстрактным базовым классом.Абстрактные базовые классы существуют для наследования, но никогда не создаются. Python предоставляет модуль abc для определения абстрактных базовых классов.

Вы можете использовать начальные подчеркивания в имени вашего класса, чтобы сообщить, что объекты этого класса не должны создаваться. Подчеркивание обеспечивает удобный способ предотвратить неправильное использование вашего кода, но не мешает заинтересованным пользователям создавать экземпляры этого класса.

Модуль abc в стандартной библиотеке Python предоставляет функциональные возможности для предотвращения создания объектов из абстрактных базовых классов.

Вы можете изменить реализацию класса Employee , чтобы исключить возможность его создания:

  # В часах в год

from abc import ABC, abstractmethod

класс Сотрудник (ABC):
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

    @abstractmethod
    def calculate_payroll (самостоятельно):
        проходить
  

Вы наследуете Employee от ABC , что делает его абстрактным базовым классом. Затем вы украшаете .Calcul_payroll () с декоратором @abstractmethod .

У этого изменения есть два хороших побочных эффекта:

  1. Вы сообщаете пользователям модуля, что объекты типа Employee создавать нельзя.
  2. Вы говорите другим разработчикам, работающим над модулем hr , что если они являются производными от Employee , то они должны переопределить абстрактный метод .calculate_payroll () .

Вы можете видеть, что объекты типа Сотрудник не могут быть созданы с помощью интерактивного интерпретатора:

>>>

  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, 'аннотация')

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: невозможно создать экземпляр абстрактного класса Employee с абстрактными методами
Calcul_payroll
  

Выходные данные показывают, что экземпляр класса не может быть создан, поскольку он содержит абстрактный метод calculate_payroll () . Производные классы должны переопределять метод, чтобы разрешить создание объектов своего типа.

Наследование реализации против наследования интерфейса

Когда вы производите один класс от другого, производный класс наследует оба:

  1. Интерфейс базового класса: Производный класс наследует все методы, свойства и атрибуты базового класса.

  2. Реализация базового класса: Производный класс наследует код, реализующий интерфейс класса.

В большинстве случаев вам нужно унаследовать реализацию класса, но вы захотите реализовать несколько интерфейсов, чтобы ваши объекты можно было использовать в разных ситуациях.

Современные языки программирования разработаны с учетом этой базовой концепции. Они позволяют наследовать от одного класса, но вы можете реализовать несколько интерфейсов.

В Python нет необходимости явно объявлять интерфейс. Любой объект, реализующий желаемый интерфейс, может использоваться вместо другого объекта. Это известно как утка , набирающая . Утиный набор текста обычно объясняется так: «Если он ведет себя как утка, значит, это утка».

Чтобы проиллюстрировать это, теперь вы добавите класс DisgruntledEmployee в приведенный выше пример, который не является производным от Employee :

.

  # В disgruntled.py

класс DisgruntledEmployee:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

    def calculate_payroll (самостоятельно):
        возврат 1000000
  

Класс DisgruntledEmployee не является производным от Employee , но предоставляет тот же интерфейс, который требуется для PayrollSystem . PayrollSystem.calculate_payroll () требуется список объектов, реализующих следующий интерфейс:

  • Свойство или атрибут id , который возвращает идентификатор сотрудника
  • name свойство или атрибут, представляющий имя сотрудника
  • А .Calcul_payroll () метод, который не принимает никаких параметров и возвращает сумму заработной платы процессу

Всем этим требованиям удовлетворяет класс DisgruntledEmployee , поэтому PayrollSystem все еще может рассчитывать свою заработную плату.

Вы можете изменить программу, чтобы использовать класс DisgruntledEmployee :

  # В program.py

импортные часы
недовольный импорт

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = час.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
disgruntled_employee = disgruntled.DisgruntledEmployee (20000, 'Анонимный')
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee,
    disgruntled_employee
])
  

Программа создает объект DisgruntledEmployee и добавляет его в список, обрабатываемый системой PayrollSystem .Теперь вы можете запустить программу и увидеть ее результат:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Заработная плата для: 20000 - Аноним
- Сумма чека: 1000000
  

Как видите, PayrollSystem все еще может обрабатывать новый объект, потому что он соответствует желаемому интерфейсу.

Поскольку вам не нужно наследовать определенный класс, чтобы ваши объекты могли повторно использоваться программой, вы можете спросить, почему вы должны использовать наследование, а не просто реализовать желаемый интерфейс.Вам могут помочь следующие правила:

  • Используйте наследование для повторного использования реализации: Производные классы должны использовать большую часть своей реализации базового класса. Они также должны моделировать отношения как . Класс Customer также может иметь идентификатор id и имя , но Customer не является Employee , поэтому вам не следует использовать наследование.

  • Реализуйте интерфейс для повторного использования: Если вы хотите, чтобы ваш класс повторно использовался определенной частью вашего приложения, вы реализуете требуемый интерфейс в своем классе, но вам не нужно предоставлять базовый класс или наследовать от него. другой класс.

Теперь вы можете очистить приведенный выше пример, чтобы перейти к следующей теме. Вы можете удалить файл disgruntled.py , а затем изменить модуль hr в исходное состояние:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {сотрудник.Calcul_payroll ()} ')
            Распечатать('')

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы удалили импорт модуля abc , поскольку класс Employee не обязательно должен быть абстрактным.Вы также удалили из него абстрактный метод calculate_payroll () , поскольку он не предоставляет никакой реализации.

По сути, вы наследуете реализацию атрибутов id и name класса Employee в производных классах. Поскольку .calculate_payroll () - это просто интерфейс для метода PayrollSystem.calculate_payroll () , вам не нужно реализовывать его в базовом классе Employee .

Обратите внимание, как класс CommissionEmployee является производным от SalaryEmployee . Это означает, что CommissionEmployee наследует реализацию и интерфейс SalaryEmployee . Вы можете увидеть, как метод CommissionEmployee.calculate_payroll () использует реализацию базового класса, поскольку он полагается на результат super (). Calculate_payroll () для реализации своей собственной версии.

Проблема взрыва класса

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

Вы начали построение иерархии классов из типов сотрудников , используемых системой PayrollSystem для расчета заработной платы. Теперь вам нужно добавить некоторые функции к этим классам, чтобы их можно было использовать с новой ProductivitySystem .

Система ProductivitySystem отслеживает производительность в зависимости от ролей сотрудников. Существуют разные роли сотрудников:

  • Менеджеры: Они ходят и кричат ​​на людей, говорящих им, что делать.Они наемные работники и зарабатывают больше денег.
  • Секретарши: Они делают всю бумажную работу для менеджеров и следят за тем, чтобы все счета были выставлены и оплачены вовремя. Они тоже наемные работники, но зарабатывают меньше денег.
  • Сотрудники отдела продаж: Они много звонят по телефону, чтобы продать товары. У них есть зарплата, но они также получают комиссионные с продаж.
  • Заводских рабочих: Производят продукцию для предприятия. Оплата им почасовая.

С учетом этих требований вы начинаете видеть, что Employee и его производные классы могут принадлежать не к модулю hr , а где-то еще, потому что теперь они также используются ProductivitySystem .

Вы создаете модуль сотрудников и перемещаете туда классы:

  # В employee.py

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        супер().__init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        супер().__init __ (id, name, weekly_salary)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Реализация остается той же, но вы перемещаете классы в модуль employee . Теперь вы измените свою программу, чтобы поддержать изменение:

  # В program.py

импортные часы
импортные сотрудники

salary_employee = сотрудники.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = сотрудники.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = employee.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

Вы запускаете программу и проверяете, что она по-прежнему работает:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

Когда все готово, вы начинаете добавлять новые классы:

  # У сотрудников.ру

Менеджер класса (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} кричит и кричит {hours} часов.')

классный секретарь (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит {hours} часов на оформление офисных документов.')

class SalesPerson (CommissionEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит на телефон {hours} часов.')

класс FactoryWorker (HourlyEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} производит гаджеты в течение {часов} часов.')
  

Сначала вы добавляете класс Manager , производный от SalaryEmployee . Класс предоставляет метод work () , который будет использоваться системой повышения производительности. Метод занимает часов, отработал сотрудник.

Затем вы добавляете секретарь , SalesPerson и FactoryWorker , а затем реализуете интерфейс work () , чтобы их можно было использовать в системе повышения производительности.

Теперь вы можете добавить ProductivitySytem class:

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            employee.work (часы)
        Распечатать('')
  

Класс отслеживает сотрудников в методе track () , который берет список сотрудников и количество часов для отслеживания. Теперь вы можете добавить в свою программу систему повышения производительности:

  # В program.py

импортные часы
импортные сотрудники
производительность импорта

менеджер = сотрудники.Менеджер (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = employee.FactoryWorker (2, 'Джейн Доу', 40, 15)
сотрудники = [
    менеджер,
    секретарь,
    sales_guy,
    рабочий,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (сотрудников, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (сотрудники)
  

Программа формирует список сотрудников разного типа.Список сотрудников отправляется в систему продуктивности для отслеживания их работы в течение 40 часов. Затем тот же список сотрудников отправляется в систему расчета заработной платы для расчета их заработной платы.

Вы можете запустить программу, чтобы увидеть результат:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600
  

Программа показывает сотрудников, работающих по 40 часов через систему продуктивности. Затем он рассчитывает и отображает платежную ведомость для каждого из сотрудников.

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

На следующей диаграмме показана новая иерархия классов:

На диаграмме показано, как растет иерархия классов. Дополнительные требования могут иметь экспоненциальный эффект на количество классов с этим дизайном.

Наследование нескольких классов

Python - один из немногих современных языков программирования, поддерживающих множественное наследование.Множественное наследование - это возможность одновременно наследовать класс из нескольких базовых классов.

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

Этот подход накладывает некоторые ограничения на ваши проекты.Вы можете унаследовать реализацию одного класса только путем прямого наследования от него. Вы можете реализовать несколько интерфейсов, но не можете наследовать реализацию нескольких классов.

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

Оказывается, иногда нанимают временных секретарей, когда нужно делать слишком много документов. Класс TemporarySecretary выполняет роль секретаря в контексте ProductivitySystem , но для целей расчета заработной платы это HourlyEmployee .

Вы смотрите на дизайн своего класса. Он немного вырос, но вы все еще можете понять, как это работает. Кажется, у вас есть два варианта:

  1. Унаследовать от Секретарь : Вы можете унаследовать от Секретарь , чтобы унаследовать .work () для роли, а затем переопределите метод .calculate_payroll () , чтобы реализовать его как HourlyEmployee .

  2. Производный от HourlyEmployee : Вы можете наследовать от HourlyEmployee , чтобы унаследовать метод .calculate_payroll () , а затем переопределить метод .work () , чтобы реализовать его как секретарь .

Затем вы помните, что Python поддерживает множественное наследование, поэтому вы решаете унаследовать от секретарь и HourlyEmployee :

  # У сотрудников.ру

class TemporarySecretary (Секретарь, Почасовой сотрудник):
    проходить
  

Python позволяет наследовать от двух разных классов, указав их в скобках в объявлении класса.

Теперь вы измените свою программу, добавив нового временного секретаря:

  импорт часов
импортные сотрудники
производительность импорта

manager = employee.Manager (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = сотрудники.FactoryWorker (4, 'Джейн Доу', 40, 15)
временный_секретарий = сотрудники.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
company_employees = [
    менеджер,
    секретарь,
    sales_guy,
    рабочий,
    временный_секретарь,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (company_employees, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (company_employees)
  

Вы запускаете программу для проверки:

  $ программа на питоне.ру

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
TypeError: __init __ () принимает 4 позиционных аргумента, но было дано 5
  

Вы получаете исключение TypeError , в котором говорится, что позиционные аргументы 4 там, где ожидалось, но были даны 5 .

Это связано с тем, что вы получили TemporarySecretary сначала из Секретарь , а затем из HourlyEmployee , поэтому переводчик пытается использовать Секретарь.__init __ () для инициализации объекта.

Ладно, перевернем:

  класс Временный секретарь (почасовый сотрудник, секретарь):
    проходить
  

Теперь запустите программу еще раз и посмотрите, что произойдет:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

Теперь кажется, что вам не хватает параметра weekly_salary , который необходим для инициализации Секретарь , но этот параметр не имеет смысла в контексте TemporarySecretary , потому что это HourlyEmployee .

Может быть, реализация TemporarySecretary .__ init __ () поможет:

  # В employee.py

class TemporarySecretary (Почасовой сотрудник, секретарь):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
  

Попробуйте:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "Сотрудник".py ", строка 54, в __init__
  super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

Это тоже не сработало. Хорошо, пора вам погрузиться в порядок разрешения метода (MRO) Python, чтобы увидеть, что происходит.

Когда осуществляется доступ к методу или атрибуту класса, Python использует класс MRO, чтобы найти его. MRO также используется super () , чтобы определить, какой метод или атрибут вызывать.Вы можете узнать больше о super () в Supercharge Your Classes With Python super ().

Вы можете оценить MRO класса TemporarySecretary с помощью интерактивного интерпретатора:

>>>

  >>> из сотрудников ввозят временного секретаря
>>> Временный секретарь .__ mro__

(<класс 'employee.TemporarySecretary'>,
 <класс 'employee.HourlyEmployee'>,
 <класс 'сотрудники. Секретарь'>,
 <класс 'employee.SalaryEmployee'>,
 <класс сотрудников.Сотрудник '>,
 <класс 'объект'>
)
  

MRO показывает порядок, в котором Python будет искать соответствующий атрибут или метод. В этом примере это то, что происходит, когда мы создаем объект TemporarySecretary :

  1. Вызывается метод TemporarySecretary .__ init __ (self, id, name, hours_worked, hour_rate) .

  2. Вызов super () .__ init __ (id, name, hours_worked, hour_rate) соответствует HourlyEmployee.__init __ (self, id, name, hour_worked, hour_rate) .

  3. HourlyEmployee вызывает super () .__ init __ (id, name) , который MRO будет соответствовать секретарю .__ init __ () , который унаследован от SalaryEmployee .__ init __ (self, id, name, weekly_salary ) .

Поскольку параметры не совпадают, возникает исключение TypeError .

Вы можете обойти MRO, изменив порядок наследования и напрямую позвонив HourlyEmployee.__init __ () следующим образом:

  класс Временный секретарь (секретарь, почасовый сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)
  

Это решает проблему создания объекта, но вы столкнетесь с аналогичной проблемой при вычислении заработной платы. Вы можете запустить программу, чтобы увидеть проблему:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
Отслеживание (последний вызов последний):
  Файл ".\ program.py ", строка 20, в 
    payroll_system.calculate_payroll (сотрудники)
  Файл "hr.py", строка 7, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
  Файл employee.py, строка 12, в файле calculate_payroll
    return self.weekly_salary
AttributeError: объект TemporarySecretary не имеет атрибута weekly_salary
  

Проблема теперь в том, что, поскольку вы изменили порядок наследования, MRO находит метод .calculate_payroll () для SalariedEmployee перед методом в HourlyEmployee .Вам нужно переопределить .calculate_payroll () в TemporarySecretary и вызвать из него правильную реализацию:

  класс Временный секретарь (секретарь, почасовый сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)

    def calculate_payroll (самостоятельно):
        return HourlyEmployee.calculate_payroll (self)
  

Метод calculate_payroll () напрямую вызывает HourlyEmployee.Calcul_payroll () , чтобы убедиться, что вы получите правильный результат. Вы можете снова запустить программу, чтобы убедиться, что она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

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

Как видите, множественное наследование может сбивать с толку, особенно когда вы сталкиваетесь с проблемой ромба.

На следующей диаграмме показана проблема ромба в иерархии классов:

На диаграмме показана проблема с ромбами при текущем дизайне класса. TemporarySecretary использует множественное наследование для наследования от двух классов, которые в конечном итоге также являются производными от Employee . Это приводит к двум путям достижения базового класса Employee , чего вы хотите избежать в своих проектах.

Проблема ромба возникает, когда вы используете множественное наследование и унаследованы от двух классов, имеющих общий базовый класс. Это может привести к вызову неправильной версии метода.

Как вы видели, Python предоставляет способ принудительного вызова нужного метода, и анализ MRO может помочь вам понять проблему.

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

Производные классы Employee используются двумя разными системами:

  1. Система продуктивности , отслеживающая продуктивность сотрудников.

  2. Система расчета заработной платы , которая рассчитывает заработную плату сотрудников.

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

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            result = employee.work (часы)
            print (f '{имя сотрудника}: {результат}')
        Распечатать('')

класс ManagerRole:
    def работа (самостоятельно, часы):
        ответь f'scream и кричит в течение {hours} часов ».

класс Секретарь
    def работа (самостоятельно, часы):
        return f'expends {hours} часов на оформление офисных документов.'

класс SalesRole:
    def работа (самостоятельно, часы):
        return f 'тратит {hours} часов на телефон.'

класс FactoryRole:
    def работа (самостоятельно, часы):
        вернуть гаджеты на {hours} часов ».
  

Модуль продуктивности реализует класс ProductivitySystem , а также связанные роли, которые он поддерживает. Классы реализуют требуемый системе интерфейс work () , но они не являются производными от Employee .

То же самое можно сделать с модулем hr :

  # В час.ру

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')

класс SalaryPolicy:
    def __init __ (self, weekly_salary):
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

класс HourlyPolicy:
    def __init __ (self, hours_worked, hour_rate):
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

класс CommissionPolicy (SalaryPolicy):
    def __init __ (self, weekly_salary, Commission):
        super () .__ init __ (недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Модуль hr реализует систему PayrollSystem , которая рассчитывает заработную плату для сотрудников.Он также реализует классы политики для расчета заработной платы. Как видите, классы политик больше не являются производными от Employee .

Теперь вы можете добавить необходимые классы в модуль сотрудника :

  # В employee.py

из часов импорта (
    SalaryPolicy,
    CommissionPolicy,
    Почасовая политика
)
из импорта производительности (
    ManagerRole,
    Секретарь роль,
    SalesRole,
    FactoryRole
)

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

Менеджер класса (Сотрудник, ManagerRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

класс Секретарь (Employee, SecretRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

class SalesPerson (Сотрудник, SalesRole, CommissionPolicy):
    def __init __ (self, id, name, weekly_salary, Commission):
        CommissionPolicy.__init __ (самостоятельно, недельная_ зарплата, комиссия)
        super () .__ init __ (идентификатор, имя)

класс FactoryWorker (Сотрудник, FactoryRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)

класс TemporarySecretary (Сотрудник, SecretRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)
  

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

Обратите внимание, что вам по-прежнему необходимо явно инициализировать политики заработной платы в конструкторах. Вы, наверное, видели, что инициализации Manager и Secret идентичны. Кроме того, одинаковы инициализации FactoryWorker и TemporarySecretary .

Вы не захотите иметь такого рода дублирование кода в более сложных проектах, поэтому вы должны быть осторожны при проектировании иерархий классов.

Вот диаграмма UML для нового дизайна:

На схеме показаны отношения для определения Секретарь и TemporarySecretary с использованием множественного наследования, но избегая проблемы ромба.

Вы можете запустить программу и посмотреть, как она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс: кричит и кричит 40 часов.Джон Смит: тратит 40 часов на оформление офисных документов.
Кевин Бэкон: 40 часов разговаривает по телефону.
Джейн Доу: производит гаджеты 40 часов.
Робин Уильямс: тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

Вы видели, как наследование и множественное наследование работают в Python.Теперь вы можете изучить тему композиции.

Состав vs. наследование: как выбрать?

В начале ...

... не было наследования и композиции, только код.

И код был громоздким, повторяющимся, блочным, несчастливым, многословным и утомительным.

Копирование и вставка были основными механизмами повторного использования кода. Процедуры и функции были редкими, новомодные устройства рассматривались с подозрением. Вызвать процедуру было дорого! Отделение фрагментов кода от основной логики вызвало путаницу!

Было темное время.

Затем мир озарил объектно-ориентированное программирование (ООП)… И мир в значительной степени игнорировал это в течение нескольких десятилетий 1 . Вплоть до графических пользовательских интерфейсов 2 , которые оказались очень, очень нуждались в ООП. Когда вы нажимаете кнопку в окне, что может быть проще, чем отправить этой кнопке (или ее суррогату) сообщение Click 3 ?

После этого ООП взлетело. Было написано множество 4 книг и появилось бесчисленное множество 5 статей.Итак, к настоящему времени все подробно разбираются в объектно-ориентированном программировании, не так ли?

К сожалению, код (и Интернет) говорит «нет».

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

Мантры, считающиеся вредными

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

Я также не поклонник нелепых заголовков типа «Наследование - это зло» 6 , особенно когда автор пытается подкрепить такое возмутительное утверждение ненадлежащим использованием наследования… а затем обвиняет наследование. Как плотник, заявляющий, что молотки бесполезны, потому что они плохо заворачивают винты.

Начнем с основ.

Определения

Вот определение объектно-ориентированного программирования, которое я буду использовать до конца статьи: предположим, что у нас есть «классический» язык ООП, т.е.е., который поддерживает классы с полями, методами и одиночным наследованием. Без интерфейсов, без миксинов, без аспектов, без множественного наследования, без делегатов, без замыканий, без лямбда-выражений, только основы:

  • Класс: именованное понятие в доменном пространстве с необязательным суперклассом, определенным как набор полей и методов.
  • Поле: именованное свойство некоторого типа, которое может ссылаться на другой объект (см. Состав)
  • Метод: именованная функция или процедура с параметрами или без них, реализующая некоторое поведение класса.
  • Наследование: класс может наследовать - использовать по умолчанию - поля и методы своего суперкласса. Наследование является транзитивным, поэтому класс может наследовать от другого класса, который наследуется от другого класса, и так далее, вплоть до базового класса (обычно Object, возможно, неявного / отсутствующего). Подклассы могут переопределять некоторые методы и / или поля, чтобы изменить поведение по умолчанию.
  • Состав: когда тип поля является классом, поле будет содержать ссылку на другой объект, тем самым создавая связь между ними.Не вдаваясь в нюансы разницы между простой ассоциацией, агрегацией и композицией, давайте интуитивно определим композицию, как когда класс использует другой объект для обеспечения некоторых или всех своих функций.
  • Инкапсуляция: взаимодействуя с объектами, а не напрямую с реализацией методов и полей, мы скрываем и защищаем реализацию класса. Если потребитель ничего не знает об объекте, кроме его общедоступного интерфейса, он не может полагаться на какие-либо внутренние детали реализации.

Наследование является фундаментальным

Наследование фундаментально объектно-ориентированному программированию. Язык программирования может иметь объекты и сообщения, но без наследования он не является объектно-ориентированным (просто «объектно-ориентированным», но все же полиморфным).

… и композиция

тоже
Состав также является основополагающим для на каждом языке. Даже если язык не поддерживает композицию (что в наши дни редко!), Люди все равно мыслят в терминах частей и компонентов.Без композиции невозможно разбить сложные проблемы на модульные решения.

(Инкапсуляция тоже имеет важное значение, но мы не будем здесь много говорить об этом.)

Так в чем суета?

Композиция и наследование имеют фундаментальное значение, так что же в этом такого?

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

Композиция довольно проста для понимания - мы можем видеть композицию в повседневной жизни: у стула есть ножки, стена из кирпича и раствора и так далее. Хотя определение наследования простое, оно может стать сложным и запутанным, если его использовать неразумно. Наследование - это скорее абстракция, о которой мы можем только говорить, а не касаться напрямую. Хотя во многих ситуациях можно имитировать наследование с помощью композиции, часто это бывает громоздко. Цель композиции очевидна: сделать из частей целое.Цель наследования немного сложнее, потому что наследование служит двум целям: семантике и механике.

Семантика наследования

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

Механика наследования

Наследование захватывает механику, кодируя представление данных (полей) и поведения (методов) класса и делая его доступным для повторного использования и расширения в подклассах. Механически подкласс наследует реализацию суперкласса и, следовательно, его интерфейс.

Двойная цель наследования 7 в большинстве современных языков ООП, как мне кажется, является причиной большинства недоразумений. Многие думают, что «повторное использование кода» является основной целью наследования, но это не единственная его цель.Чрезмерный упор на повторное использование может привести к трагически ошибочным проектам. Давайте посмотрим на пару примеров.

Как злоупотребить наследованием - Пример 1

Давайте начнем с простого и чрезвычайно распространенного примера неправильного использования наследования:

 class Stack extends ArrayList {
    public void push (значение объекта) {…}
    public Object pop () {…}
}
 

Этот класс будет функционировать как стек, но его интерфейс фатально раздут. Открытый интерфейс этого класса - это не просто push и pop, как можно было бы ожидать от класса с именем Stack, он также включает в себя получение, установку, добавление, удаление, очистку и множество других сообщений, унаследованных от ArrayList, которые не подходят для Куча.

Вы можете переопределить все нежелательные методы и, возможно, адаптировать некоторые полезные (например, clear), но это большая работа, чтобы скрыть ошибку моделирования. Фактически, три ошибки моделирования, одна семантическая, одна механическая, одна обе:

  1. Семантически утверждение «Стек является списком массивов» неверно; Стек не является правильным подтипом ArrayList. Предполагается, что стек должен обеспечивать выполнение последним вошел - первым вышел, ограничение легко удовлетворяется интерфейсом push / pop, но не обеспечивается интерфейсом ArrayList.
  2. Механически наследование от ArrayList нарушает инкапсуляцию; использование ArrayList для хранения коллекции объектов стека - это вариант реализации, который следует скрыть от потребителей.
  3. Наконец, реализация стека путем наследования от ArrayList является междоменной связью: ArrayList - это коллекция с произвольным доступом; Стек - это концепция организации очередей со специально ограниченным (неслучайным) доступом 8 . Это разные области моделирования.

Последний вопрос важен, но немного тонок, поэтому давайте рассмотрим его на другом примере.

Как злоупотребить наследованием - Пример 2

Создание класса концепции предметной области путем наследования от класса реализации является распространенным злоупотреблением наследованием. Например, предположим, что мы хотим что-то сделать с определенным сегментом наших клиентов. Легко и очевидно, что нужно создать подкласс ArrayList, назвать его CustomerGroup и начать кодирование, верно?

Неправильно. Это будет отношения междоменного наследования, и их следует избегать:

  1. ArrayList уже является подклассом списка, коллекция утилит - реализация класса .
  2. CustomerGroup - это еще один подкласс - домен , класс .
  3. Доменные классы должны использовать классы реализации , а не наследовать от них.

Пространство реализации должно быть невидимым на уровне домена. Когда мы думаем о том, что делает наше программное обеспечение, мы работаем на уровне домена; мы не хотим отвлекаться на подробности о том, как он работает. Если мы сосредоточимся только на «повторном использовании кода» через наследование, мы будем неоднократно попадать в эту ловушку.

Проблема не в одиночном наследовании

Одиночное наследование по-прежнему является наиболее распространенной моделью ООП; одинарное наследование - это обязательно наследование реализации, которое может привести к сильной связи между классами. Проблема, казалось бы, в том, что у нас есть только один путь наследования, который можно использовать для моделирования как наших механических, так и семантических потребностей. Если вы используете его для одного, вы не можете использовать его для другого. Так разве множественное наследование не решает эту проблему?

. Отношения наследования не должны пересекать границы домена (домен реализации или домен приложения).Заставляя CustomerGroup наследовать от ArrayList , но также и от (скажем) DemographicSegment, эти два поддомена запутываются, запутывая таксономию.

Предпочтительное (по крайней мере, для меня!) Решение - унаследовать от служебных классов столько, сколько необходимо для реализации ваших механических структур, а затем использовать эти структуры в доменных классах посредством композиции, а не наследования. Позвольте мне еще раз заявить:

Если вы не создаете класс реализации, вы не должны наследовать от класса реализации.

Это одна из самых распространенных проблем новичков - ведь это так удобно! - и причины, по которым это неправильно, не часто обсуждаются в литературе по программированию, поэтому я повторю это еще раз: классы области приложения должны использовать классы реализации , а не быть одними из них. Держите эти таксономии / домены отдельно.

Итак, когда и как нам использовать наследование?

Использование колодца наследования

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

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

Как определиться: состав или наследование?

Если у вас есть ситуация, когда сработает либо композиция, либо наследование, подумайте о разделении обсуждения дизайна на две части:

  1. Представление / реализация концепций вашей предметной области - одно измерение
  2. Семантика концепций вашей предметной области и их отношения друг к другу - это второе измерение

В общем, наследование в пределах одного из этих параметров нормально.Проблема возникает, когда мы забываем разделить два измерения и начинаем наследование через межпространственные границы.

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

Ничто не заменит объектное моделирование и критическое дизайнерское мышление.Но если у вас есть какие-то рекомендации, учтите их -

Наследование следует использовать только в следующих случаях:

  1. Оба класса находятся в одном логическом домене
  2. Подкласс - это правильный подтип суперкласса
  3. Реализация суперкласса необходима или подходит для подкласса
  4. Усовершенствования, внесенные подклассом, в основном аддитивны.

Бывают моменты, когда все это сходится:

  • Моделирование предметной области более высокого уровня
  • Каркасы и расширения каркасов
  • Дифференциальное программирование

Если вы не делаете ничего из этого, вероятно, вам не понадобится часто наследование классов.«Предпочтение» композиции - это не вопрос «лучше», это вопрос «наиболее подходящего» для ваших нужд в конкретном контексте.

Надеюсь, эти рекомендации помогут вам заметить разницу.

Удачного кодирования!

Приложение

Особая благодарность за ценный вклад и комментарии перечисленным ниже ThoughtWorkers: Питу Ходжсону, Тиму Брауну, Скотту Робинсону, Мартину Фаулеру, Минди Ор, Шону Ньюхэму, Сэму Гибсону и Махендре Кария.


1. Первый официально объектно-ориентированный язык, SIMULA 67, родился в 1967 году.Объектно-ориентированному программированию 48 лет!
2. Программисты систем и приложений приняли C ++ в середине 1980-х, но повсеместному распространению ООП пришлось ждать еще десять лет.
3. да, я слишком упрощаю, игнорируя слушателей / делегатов событий и т. Д .; стараюсь сделать эту статью короткой!
4. Amazon заявляет о 24 777 книгах по теме объектно-ориентированного программирования на момент написания этой статьи
5. Поиск в Google заявляет ~ 8 миллионов результатов для точной фразы «объектно-ориентированное программирование» на момент написания этой статьи
6. Поиск в Google дает ориентировочные результаты 37 600 результатов для точной фразы «наследство - зло» на момент написания этой статьи
7.Семантика (интерфейсы) и механика (представление) могут быть разделены за счет дополнительной сложности языка; см., например, спецификацию языка D К. Дж. Дэйта и Хью Дарвена.
8. С некоторой грустью обратите внимание на то, что класс Java Stack наследуется от Vector.
9. Проектирование классов для повторного использования посредством наследования выходит за рамки данной статьи. Просто имейте в виду, что потребители экземпляров и подклассов имеют разные потребности, и обе из них должны быть удовлетворены дизайном вашего класса.

oop - Состав против наследования

, если класс A имеет композиционные отношения с другим классом B

Предположим, вы имеете в виду A <*> --- B или A <*> ---> B , а не наоборот ( B <*> --- A или B <*> - -> A ), потому что это не очень понятно в вашем вопросе.

класс A не может существовать без класса b

Речь идет не о классах , а о экземплярах этих классов, а B может иметь экземпляры независимо от этого состава, поэтому ограниченный срок службы не для всех экземпляров B .

работает как наследование? наследует ли класс A атрибуты и методы от класса B?

A наследует B означает A является B , это абсолютно не тот случай, если A <*> --- B или A <*> ---> B или наоборот.

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

Об атрибутах B , в зависимости от способа реализации композиции, возможно, они являются частью соответствующего экземпляра A , это имеет место, например, в C ++, не использующем указатель для композиции. Но в любом случае это не означает, что эти атрибуты B являются атрибутами A , для доступа к ним необходимо сначала получить доступ к экземпляру B через композиции, и это также предполагает, что экземпляр A имеет право прямого доступа к этим атрибутам B , поэтому они обычно являются общедоступными .

Обратите внимание: также у вас может быть A <*> ---> B <*> ---> A и т. Д., И, к счастью, это означает, что A наследует B и B наследует A , что невозможно

Наследование против состава. А что использовать? | by Brian

Вы быстро получите довольно глубокое и широкое наследство. В некоторые дни вы можете получить глубокую иерархию классов. Одно изменение от высшего класса может уничтожить все нижележащие классы.В тот момент, когда вы решите наследовать от другого класса, вы привяжете к нему свой класс. Вы никогда не знаете, что будет делать суперкласс, поскольку это находится вне вашего контроля: D Это известно как проблема хрупкого базового класса

Иерархию классов очень, очень сложно изменить после развертывания. Один из вариантов - переписать все это целиком.

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

 git pull master 

Все в порядке - конфликтов нет.Пришло время создать ветку для работы. Перво-наперво запустите проект - все еще хорошо, компиляция прошла успешно. Затем я проверяю свою последнюю разработанную функцию - и бум, она запуталась.

После некоторой отладки я наконец выяснил, почему. Это из-за некоторых изменений, внесенных в корневой класс!

Иногда мы действительно используем наследование ненадлежащим образом. Принцип подстановки Лискова был бы наиболее важным ориентиром для определения того, подходит ли наследование вашему проекту или нет.

Например: B наследуется от A. Используйте подстановку Лискова, чтобы обосновать связь между B и A, затем измените связь и подтвердите ее снова. Если кажется, что отношения имеют смысл в обоих направлениях, лучше не использовать наследование.

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

Одна проблема с наследованием заключается в том, что оно побуждает вас предсказывать будущее - это ловушка. Наследование побуждает создавать таксономию объектов на самых ранних этапах проекта. Поступая так, вы, вероятно, совершите дизайнерские ошибки. В некоторых случаях он также нарушает инкапсуляцию, открывая свойства / методы для использования подклассами.

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

Наследование и состав. Наследование и Состав - это два… | Энрике Сиберт Домарески

Наследование и композиция - это два способа создания связи между классами и объектами в объектно-ориентированных языках программирования. В этой статье мы собираемся понять, как мы можем его использовать и когда нам следует использовать наследование или композицию

Наследование

Наследование - это принцип объектно-ориентированного программирования, который позволяет классам совместно использовать атрибуты и методы, помимо наследования. '.Это полезно для повторного использования кода или поведения. В .NET не допускается множественное наследование, поэтому вы можете наследовать только от одного класса.

Это определение наследования из документации Microsoft:

Наследование - один из фундаментальных атрибутов объектно-ориентированного программирования. Он позволяет вам определить дочерний класс, который повторно использует (наследует), расширяет или изменяет поведение родительского класса

В наследовании у нас есть два типа классов: базовый класс и производный класс . Базовый класс - это класс, который дает характеристики другому классу. Производный класс - это класс, унаследовавший характеристики от базового класса.

Давайте посмотрим на пример наследования в реальном сценарии с использованием C #. У меня есть этот класс под названием Entity. Это общий класс с двумя свойствами: идентификатором и датой создания объекта. Каждый класс, унаследованный от Entity, также будет иметь доступ к этим двум свойствам.Это абстрактный класс, потому что этот класс не может быть создан, его можно только унаследовать.

Теперь давайте создадим новый класс, который будет наследовать от класса Entity. Чтобы использовать наследование в .NET, мы используем две точки, следующие за именем класса, который мы хотим наследовать:

 Book: Entity 

В данном случае:

  • Класс Book является производным от классом .
  • Класс Entity - это базовый класс .

Это класс Book, унаследованный от Entity:

Теперь в классе Book вы можете получить доступ к свойствам Id и CreatedDate через наследование:

 var book = new Book 
{
Id = 1,
Name = "Clean Код ",
Автор =" Роберт К. Мартин ",
CreatedDate = DateTime.Now
};

Следует иметь в виду, что наследование может вызвать с высокой связью между вашими классами. Например, если вы измените что-то в базовом классе, это напрямую повлияет на весь производный класс.

Поэтому всегда, когда вы думаете, следует ли вам использовать наследование или нет, вы можете спросить себя, является ли класс «» для чего-либо. Например, Book - это сущность , поэтому в этих случаях можно использовать наследование. Но если у нас есть другой класс, например, класс Category, мы не можем сказать, что Book является категорией, поэтому в этом случае класс Book не должен наследоваться от Category, вместо этого, если мы хотим создать связь между этими двумя классами. , мы должны использовать композицию.

Composition

Composition - это способ создать отношения между классами, и он лучше, чем наследование почти во всех случаях, потому что композиция не делает ваш код сильно связанным. Это определение композиции из книги «Справочник архитектора программного обеспечения»:

Композиция - это отношение, в котором объект не может существовать независимо от другого объекта.

Чтобы использовать композицию, вы можете спросить себя, есть ли у класса « -» что-нибудь.Например, в книге класса « есть категория ». Обратите внимание, что в этом случае мы не можем сказать, что книга является категорией , но мы можем сказать, что книга имеет категорию , поэтому в этом случае мы должны использовать композицию (см. Строку 10):

Сейчас в книге class у вас есть доступ к свойствам из класса Category через Composition:

 var book = new Book 
{
Name = "Clean Code",
Author = "Robert C. Martin",
Category = new Category () {
Description = "Компьютеры и технологии"
}
};

Заключение

Практически во всех случаях лучше использовать Композицию вместо Наследования, потому что тогда вы сделаете свой код менее связанным.Но это не означает, что вам никогда не следует использовать наследование, в некоторых случаях будет более разумным использовать наследование, чем композицию. Всегда помните о правилах « is-a » и « has-a », потому что это поможет вам решить, следует ли использовать наследование или композицию.

Спасибо за чтение!

Принцип композиции над наследованием

Принцип из книги Банда четырех

Вердикт

В Python, как и в других языках программирования,
этот великий принцип вдохновляет архитекторов программного обеспечения
чтобы уйти от объектной ориентации
и наслаждайтесь более простыми практиками
объектно-ориентированного программирования.

Это второй выдвинутый принцип
по книге "Банда четырех" в самом начале,
в главе «Введение».
Принцип настолько важен, что они отображают его с отступом и курсивом,
как это:

Отдайте предпочтение композиции объекта над наследованием класса.

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

Задача: взрыв подкласса

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

Модуль регистрации

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

 import sys
импорт системного журнала

# Начальный класс.

класс Logger (объект):
    def __init __ (сам, файл):
        self.file = файл

    def log (self, message):
        self.file.write (сообщение + '\ n')
        self.file.flush ()

# Еще два класса, которые отправляют сообщения в другое место.

класс SocketLogger (Регистратор):
    def __init __ (self, sock):
        self.sock = носок

    def log (self, message):
        себя.sock.sendall ((сообщение + '\ n'). encode ('ascii'))

класс SyslogLogger (Регистратор):
    def __init __ (себя, приоритет):
        self.priority = приоритет

    def log (self, message):
        syslog.syslog (собственный приоритет, сообщение)
 

Проблема возникает, когда к этой первой оси дизайна присоединяется другая.
Представим, что теперь сообщения журнала нужно фильтровать -
некоторые пользователи хотят видеть только сообщения со словом «Ошибка» в них,
и разработчик отвечает новым подклассом Logger :

 # Новое направление дизайна: фильтрация сообщений.класс FilteredLogger (Регистратор):
    def __init __ (сам, шаблон, файл):
        self.pattern = шаблон
        super () .__ init __ (файл)

    def log (self, message):
        если self.pattern в сообщении:
            super (). log (сообщение)

# Оно работает.

f = FilteredLogger ('Ошибка', sys.stdout)
f.log ('Игнорируется: это не важно')
f.log ('Ошибка: но вы хотите это увидеть')
 
 Ошибка: но вы хотите это увидеть
 

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

Может, программисту повезет
и никаких дополнительных комбинаций не потребуется.
Но в общем случае приложение завершится с 3 × 2 = 6 классами:

 Регистратор FilteredLogger
SocketLogger FilteredSocketLogger
SyslogLogger FilteredSyslogLogger
 

Общее количество классов увеличится геометрически
если m и n , оба продолжают расти.Это «разрастание классов»
и «взрыв подклассов»
чего «Банда четырех» хочет избежать.

Решение - распознать
что класс, отвечающий как за фильтрацию сообщений, так и за регистрацию сообщений
слишком сложно.
В современной объектно-ориентированной практике
его обвинят в нарушении «принципа единой ответственности».

Но как мы можем раздавать
две функции фильтрации сообщений и вывода сообщений
в разных классах?

Решение №1: Шаблон адаптера

Одним из решений является шаблон адаптера:
чтобы решить, что исходный класс регистратора
не нуждается в улучшении,
потому что любой механизм вывода сообщений
можно обернуть так, чтобы он выглядел как файловый объект, ожидаемый регистратором.

  1. Итак, мы сохраняем оригинальный регистратор .
  2. И еще у нас есть FilteredLogger .
  3. Но вместо создания подклассов, зависящих от места назначения,
    мы адаптируем каждое место назначения к поведению файла
    а затем передать адаптер Logger в качестве выходного файла.

Вот адаптеры для каждого из двух других выходов:

 импортный разъем

класс FileLikeSocket:
    def __init __ (self, sock):
        self.sock = носок

    def write (self, message_and_newline):
        себя.sock.sendall (message_and_newline.encode ('ascii'))

    def flush (сам):
        проходить

класс FileLikeSyslog:
    def __init __ (себя, приоритет):
        self.priority = приоритет

    def write (self, message_and_newline):
        message = message_and_newline.rstrip ('\ n')
        syslog.syslog (собственный приоритет, сообщение)

    def flush (сам):
        проходить
 

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

И так избежать взрыва подкласса!
Объекты регистратора и объекты адаптера
можно свободно смешивать и сопоставлять во время выполнения
без необходимости создавать какие-либо дополнительные классы:

 sock1, sock2 = socket.socketpair ()

fs = FileLikeSocket (sock1)
logger = FilteredLogger ('Ошибка', фс)
logger.log ('Предупреждение: сообщение номер один')
logger.log ('Ошибка: сообщение номер два')

print ('Получен сокет:% r'% sock2.recv (512))
 
 Сокет получил: b'Error: сообщение номер два \ n '
 

Обратите внимание, что это было только для примера
что класс FileLikeSocket написан выше -
в реальной жизни этот адаптер встроен в стандартную библиотеку Python.
Просто вызовите метод makefile () любого сокета.
получить комплектный адаптер, делающий розетку похожей на файл.

Решение № 2: Образец моста

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

Как и в корпусе адаптера,
отдельный эшелон классов теперь управляет письмом.Но вместо того, чтобы искажать наши выходные классы
чтобы соответствовать интерфейсу объекта
файла Python -
что потребовало неловкого маневра
добавления новой строки в регистратор
что иногда приходилось снимать заново в адаптере -
Теперь мы можем сами определить интерфейс обернутого класса.

Итак, давайте спроектируем внутренний объект «реализации» для приема необработанного сообщения,
вместо добавления новой строки,
и уменьшите интерфейс до единственного метода emit ()
вместо того, чтобы также поддерживать метод flush ()
обычно это было безуспешно.

 # «Абстракции», которые увидят вызывающие абоненты.

класс Logger (объект):
    def __init __ (сам, обработчик):
        self.handler = обработчик

    def log (self, message):
        self.handler.emit (сообщение)

класс FilteredLogger (Регистратор):
    def __init __ (сам, шаблон, обработчик):
        self.pattern = шаблон
        super () .__ init __ (обработчик)

    def log (self, message):
        если self.pattern в сообщении:
            super (). log (сообщение)

# «Реализации», скрытые за кадром.

класс FileHandler:
    def __init __ (сам, файл):
        себя.file = файл

    def emit (self, message):
        self.file.write (сообщение + '\ n')
        self.file.flush ()

класс SocketHandler:
    def __init __ (self, sock):
        self.sock = носок

    def emit (self, message):
        self.sock.sendall ((сообщение + '\ n'). encode ('ascii'))

класс SyslogHandler:
    def __init __ (себя, приоритет):
        self.priority = приоритет

    def emit (self, message):
        syslog.syslog (собственный приоритет, сообщение)
 

Объекты абстракции и объекты реализации
теперь можно свободно комбинировать во время выполнения:

 обработчик = FileHandler (sys.стандартный вывод)
logger = FilteredLogger ('Ошибка', обработчик)

logger.log ('Игнорируется: это не будет регистрироваться')
logger.log ('Ошибка: это важно')
 

Это более симметрично, чем адаптер.
Вместо вывода в файл, встроенного в Logger
но нефайловый вывод, требующий дополнительного класса,
теперь всегда строится работающий регистратор
путем составления абстракции с реализацией.

Еще раз,
взрыв подкласса избегается
потому что два вида классов составляются вместе во время выполнения
без необходимости расширения какого-либо класса.

Решение № 3: Шаблон декоратора

Что, если бы мы хотели применить два разных фильтра к одному и тому же журналу?
Ни одно из вышеперечисленных решений не поддерживает несколько фильтров -
скажем, один фильтрует по приоритету, а другой соответствует ключевому слову.

Вернитесь к фильтрам, определенным в предыдущем разделе.
Причина, по которой мы не можем складывать два фильтра
это асимметрия
между интерфейсом, который они предлагают, и интерфейсом, который они оборачивают:
они предлагают метод log ()
но вызовите метод emit () их обработчика.Обертывание одного фильтра другим приведет к ошибке AttributeError
когда внешний фильтр пытался вызвать внутренний фильтр emit () .

Если вместо этого мы изменим наши фильтры и обработчики, чтобы предложить тот же интерфейс,
так что все они одинаково предлагают метод log () ,
Затем мы подошли к шаблону декоратора:

 # Все регистраторы выполняют реальный вывод.

класс FileLogger:
    def __init __ (сам, файл):
        self.file = файл

    def log (self, message):
        себя.file.write (сообщение + '\ n')
        self.file.flush ()

класс SocketLogger:
    def __init __ (self, sock):
        self.sock = носок

    def log (self, message):
        self.sock.sendall ((сообщение + '\ n'). encode ('ascii'))

класс SyslogLogger:
    def __init __ (себя, приоритет):
        self.priority = приоритет

    def log (self, message):
        syslog.syslog (собственный приоритет, сообщение)

# Фильтр вызывает тот же метод, что и предлагает.

класс LogFilter:
    def __init __ (self, pattern, logger):
        self.pattern = шаблон
        себя.logger = регистратор

    def log (self, message):
        если self.pattern в сообщении:
            self.logger.log (сообщение)
 

Впервые,
код фильтрации переместился за пределы какого-либо конкретного класса регистратора.
Вместо этого теперь это отдельная функция
который можно обернуть вокруг любого регистратора, который мы захотим.

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

 log1 = FileLogger (sys.stdout)
log2 = LogFilter ('Ошибка', log1)

log1.log ('Шумно: этот логгер всегда производит вывод')

log2.log ('Игнорируется: будет отфильтровано')
log2.log ('Ошибка: это важно и печатается')
 
 Шумный: этот регистратор всегда выдает вывод
Ошибка: это важно и печатается
 

И поскольку классы декораторов симметричны -
они предлагают точно такой же интерфейс, который они оборачивают -
теперь мы можем складывать несколько разных фильтров поверх одного и того же журнала!

 log3 = LogFilter ('серьезный', log2)

log3.log ('Ошибка: это плохо, но не так уж плохо')
log3.log ('Ошибка: это довольно серьезно')
 

Ошибка

: это довольно серьезно
 

Но обратите внимание на одно место, где нарушается симметрия этого дизайна:
в то время как фильтры можно складывать,
процедуры вывода не могут быть объединены или сложены.
Сообщения журнала по-прежнему могут быть записаны только на один выход.

Решение №4: За пределами банды четырех шаблонов

Модулю регистрации

Python требовалась еще большая гибкость:
не только для поддержки нескольких фильтров,
но для поддержки нескольких выходов для одного потока сообщений журнала.На основе дизайна модулей логирования на других языках -
см. PEP 282
Раздел «Влияния» для основных источников вдохновения -
модуль ведения журнала Python
реализует свой собственный шаблон «Композиция поверх наследования».

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

Или, по крайней мере, в этом суть идеи.
Ведение журнала в стандартной библиотеке на самом деле более сложное.
Например,
каждый обработчик может иметь собственный список фильтров
в дополнение к перечисленным его регистратором.
И каждый обработчик также указывает минимальный «уровень» сообщения.
например, ИНФОРМАЦИЯ или ПРЕДУПРЕЖДЕНИЕ , что,
довольно сбивает с толку,
не применяется ни самим обработчиком
ни одним из фильтров обработчика,
но вместо этого с помощью оператора if
похоронен глубоко внутри регистратора, где он петляет над обработчиками.Таким образом, общий дизайн выглядит немного беспорядочно.

Но мы можем использовать основные идеи регистратора стандартной библиотеки -
что сообщения регистратора
может заслужить как несколько фильтров , так и несколько выходов -
для полного разделения классов фильтров и классов обработчиков:

 # Теперь есть только один регистратор.

класс Logger:
    def __init __ (себя, фильтры, обработчики):
        self.filters = фильтры
        self.handlers = обработчики

    def log (self, message):
        если все (f.match (сообщение) для f в self.фильтры):
            для h в self.handlers:
                h.emit (сообщение)

# Фильтры теперь знают только о строках!

класс TextFilter:
    def __init __ (сам, шаблон):
        self.pattern = шаблон

    def match (self, text):
        вернуть self.pattern в тексте

# Обработчики выглядят так же, как «регистраторы» в предыдущем решении.

класс FileHandler:
    def __init __ (сам, файл):
        self.file = файл

    def emit (self, message):
        self.file.write (сообщение + '\ n')
        self.file.flush ()

класс SocketHandler:
    def __init __ (self, sock):
        себя.sock = носок

    def emit (self, message):
        self.sock.sendall ((сообщение + '\ n'). encode ('ascii'))

класс SyslogHandler:
    def __init __ (себя, приоритет):
        self.priority = приоритет

    def emit (self, message):
        syslog.syslog (собственный приоритет, сообщение)
 

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

Фактически,
слово «журнал» полностью исчезло из названия класса фильтра,
и по очень важной причине:
в нем больше нет ничего особенного для ведения журнала!
TextFilter теперь полностью многоразовый
в любом контексте, который связан со строками.
Наконец, отделившись от конкретной концепции ведения журнала,
будет легче тестировать и поддерживать.

Опять же,
как и все решения проблемы "композиция вместо наследования",
классы составляются во время выполнения без необходимости наследования:

 f = TextFilter ('Ошибка')
h = FileHandler (sys.стандартный вывод)
logger = Регистратор ([f], [h])

logger.log ('Игнорируется: это не будет регистрироваться')
logger.log ('Ошибка: это важно')
 

Здесь есть важный урок:
принципы проектирования, такие как композиция вместо наследования,
в конце концов,
важнее отдельных паттернов, таких как Адаптер или Декоратор.
Всегда следуйте принципу.
Но не всегда стесняйтесь
выбрать узор из официального списка.
Дизайн, к которому мы пришли
более гибкий и простой в обслуживании
чем любой из предыдущих дизайнов,
хотя они были основаны на официальных паттернах Банды четырех
но этот окончательный дизайн - нет.Иногда да, вы найдете существующий шаблон дизайна
это идеально подходит для вашей проблемы -
но если нет,
ваш дизайн может быть сильнее, если вы выйдете за их рамки.

Dodge: утверждения «если»

Я подозреваю, что приведенный выше код поразил многих читателей.
Типичному программисту на Python:
такое интенсивное использование классов может показаться полностью надуманным -
неловкое упражнение в попытке воплотить старые идеи 80-х
кажутся актуальными для современного Python.

Когда появляется новое требование к дизайну,
делает ли типичный программист Python
действительно идти писать новый класс?
Нет!
«Лучше простое, чем сложное.”
Зачем добавлять класс,
когда вместо этого будет работать оператор if ?
Один класс регистратора может постепенно наращивать условные выражения
пока он не обработает все те же дела
как в наших предыдущих примерах:

 # Каждая новая функция в виде оператора «если».

класс Logger:
    def __init __ (self, pattern = None, file = None, sock = None, priority = None):
        self.pattern = шаблон
        self.file = файл
        self.sock = носок
        self.priority = приоритет

    def log (self, message):
        если self.pattern не равен None:
            если сам.шаблон отсутствует в сообщении:
                возвращаться
        если self.file не равен None:
            self.file.write (сообщение + '\ n')
            self.file.flush ()
        если self.sock не равен None:
            self.sock.sendall ((сообщение + '\ n'). encode ('ascii'))
        если self.priority не равен None:
            syslog.syslog (собственный приоритет, сообщение)

# Работает нормально.

logger = Регистратор (шаблон = 'Ошибка', файл = sys.stdout)

logger.log ('Предупреждение: не так важно')
logger.log ('Ошибка: это важно')
 

Вы можете узнать этот пример
как более типичный для практики проектирования Python
вы сталкивались с реальными приложениями.

Метод утверждения if не лишен преимуществ.
Весь спектр возможных вариантов поведения этого класса
можно понять при однократном чтении кода сверху вниз.
Список параметров может выглядеть подробным, но,
благодаря необязательным аргументам ключевого слова Python,
для большинства вызовов класса не нужно указывать все четыре аргумента.

(Это правда, что этот класс может обрабатывать только один файл и один сокет,
но это случайное упрощение для удобства чтения.Мы могли легко изменить параметры файла и socket .
в списки с именами файлы и сокеты .)

Учитывая, что каждый программист Python изучает , если быстро,
но для понимания классов может потребоваться гораздо больше времени,
это может показаться очевидной победой
чтобы код полагался на простейший из возможных механизмов
это заставит функцию работать.
Но давайте уравновесим это искушение, объяснив, что было потеряно.
уклоняясь от состава над наследованием:

  1. Населенный пункт.
    Реорганизация кода для использования операторов if
    не было явным преимуществом для удобочитаемости.
    Если вам поручено улучшить или отладить одну конкретную функцию -
    скажем, поддержка записи в сокет -
    вы обнаружите, что не можете прочитать его код в одном месте.
    Код, стоящий за этой единственной функцией
    разбросаны по списку параметров инициализатора,
    код инициализатора,
    и сам метод log () .
  2. Возможность удаления.
    Недооцененное свойство хорошего дизайна
    в том, что это упрощает удаление функций.Возможно, только ветераны крупных и зрелых приложений Python
    достаточно сильно оценит
    важность удаления кода для здоровья проекта.
    В случае наших решений на основе классов,
    мы можем тривиально удалить такую ​​функцию, как вход в сокет
    удалив класс SocketHandler и его модульные тесты
    как только приложение больше не нуждается в нем.
    Напротив,
    удаление функции сокета из леса , если операторы
    не только требует осторожности
    чтобы избежать нарушения смежного кода,
    но возникает неудобный вопрос, что делать
    с параметром socket в инициализаторе.Его можно удалить?
    Нет, если нам нужно сохранить согласованность списка позиционных параметров -
    нам нужно будет сохранить параметр,
    но вызовите исключение, если оно когда-либо использовалось.
  3. Анализ мертвого кода.
    Относится к предыдущему пункту
    заключается в том, что когда мы используем композицию вместо наследования,
    анализаторы мертвого кода
    может тривиально обнаружить
    когда исчезнет последнее использование SocketHandler в базе кода.
    Но анализ мертвого кода часто беспомощен, чтобы сделать такое определение, как
    «Теперь вы можете удалить все атрибуты и операторы if
    связанные с выходом сокета,
    потому что не сохранился вызов инициализатора
    передает что-либо для socket , кроме Нет .”
  4. Тестирование.
    Один из самых сильных сигналов о работоспособности кода, который дают наши тесты.
    сколько строк нерелевантного кода нужно запустить
    не дойдя до тестируемой линии.
    Легко протестировать такую ​​функцию, как вход в сокет
    если тест может просто запустить экземпляр SocketHandler ,
    передайте ему активную розетку,
    и попросите его на emit () сообщение.
    Никакой код не запускается, кроме кода, относящегося к функции.
    Но тестируем логирование сокета в нашем лесу , если утверждения
    будет выполняться как минимум в три раза больше строк кода.Необходимость настройки регистратора
    с правильным сочетанием нескольких функций
    просто проверить один из них
    это важный предупреждающий знак,
    это может показаться тривиальным в этом небольшом примере
    но приобретает решающее значение по мере роста системы.
  5. Эффективность.
    Я намеренно ставлю этот вопрос в последнюю очередь,
    потому что удобочитаемость и ремонтопригодность
    обычно более важные проблемы.
    А вот проблемы дизайна с лесом если заявлений
    также свидетельствует о неэффективности этого подхода.Даже если вам нужен простой нефильтрованный журнал в один файл,
    каждое отдельное сообщение будет принудительно запускать оператор if
    против всех возможных функций, которые вы могли бы включить.
    Техника композиции, напротив,
    запускает код только для функций, которые вы составили вместе.

По всем этим причинам
Я предполагаю, что кажущаяся простота оператора if forest
это, с точки зрения разработки программного обеспечения, во многом иллюзия.
Возможность читать регистратор сверху вниз как единый фрагмент кода.
происходит за счет нескольких других концептуальных затрат
который будет резко расти вместе с размером кодовой базы.

Dodge: множественное наследование

Некоторые проекты Python
отстают от практики композиции, а не наследования
потому что они склонны уклоняться от принципа
с помощью спорной особенности языка Python:
множественное наследование.

Вернемся к примеру кода, с которого мы начали,
где FilteredLogger и SocketLogger
были два разных подкласса базового класса Logger .
На языке, поддерживающем только одиночное наследование,
а FilteredSocketLogger
пришлось бы выбрать
унаследовать от SocketLogger или FilteredLogger ,
а затем придется дублировать код из другого класса.

Но Python поддерживает множественное наследование,
так что новый FilteredSocketLogger
может указывать как SocketLogger , так и FilteredLogger как базовые классы
и наследовать от обоих:

 # Базовый класс и подклассы нашего исходного примера.

класс Logger (объект):
    def __init __ (сам, файл):
        self.file = файл

    def log (self, message):
        self.file.write (сообщение + '\ n')
        self.file.flush ()

класс SocketLogger (Регистратор):
    def __init __ (self, sock):
        себя.sock = носок

    def log (self, message):
        self.sock.sendall ((сообщение + '\ n'). encode ('ascii'))

класс FilteredLogger (Регистратор):
    def __init __ (сам, шаблон, файл):
        self.pattern = шаблон
        super () .__ init __ (файл)

    def log (self, message):
        если self.pattern в сообщении:
            super (). log (сообщение)

# Класс, полученный посредством множественного наследования.

класс FilteredSocketLogger (FilteredLogger, SocketLogger):
    def __init __ (self, pattern, sock):
        FilteredLogger.__init __ (сам, шаблон, нет)
        SocketLogger .__ init __ (сам, носок)

# Работает нормально.

logger = FilteredSocketLogger ('Ошибка', sock1)
logger.log ('Предупреждение: не так важно')
logger.log ('Ошибка: это важно')

print ('Получен сокет:% r'% sock2.recv (512))
 
 Сокет получил: b'Error: это важно \ n '
 

Имеет несколько поразительных сходств
к нашему решению Decorator Pattern.
В обоих случаях:

  • Есть класс регистратора для каждого вида вывода
    (вместо асимметрии нашего адаптера
    между записью файлов напрямую
    а не файлы через переходник).
  • Сообщение сохраняет точное значение, предоставленное вызывающим абонентом
    (вместо привычки нашего Адаптера
    замены его значением, специфичным для файла
    добавив новую строку).
  • Фильтр и логгеры симметричны
    в том, что они оба реализуют один и тот же метод log () .
    (Другие наши решения помимо Decorator
    были классы фильтров, предлагающие один метод
    а выходные классы предлагают другой).
  • Фильтр никогда не пытается произвести вывод самостоятельно, но,
    если сообщение проходит фильтрацию,
    переносит задачу вывода на другой код.

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

Если у нас есть тщательные модульные тесты как для логгера, так и для фильтра,
насколько мы уверены, что они будут работать вместе?

  1. Успех примера Decorator
    зависит только от публичного поведения каждого класса:
    что LogFilter предлагает метод log ()
    который, в свою очередь, вызывает log () для объекта, который он обертывает
    (что тест может тривиально проверить с помощью крошечного фальшивого регистратора),
    и что каждый регистратор предлагает рабочий метод log () .Пока наши модульные тесты проверяют эти два публичных поведения,
    мы не можем нарушить композицию
    не провалив наших модульных тестов.

    Множественное наследование, напротив,
    зависит от поведения, которое невозможно проверить
    просто создавая экземпляры рассматриваемых классов.
    Публичное поведение FilteredLogger
    в том, что он предлагает метод log ()
    который и фильтрует, и записывает в файл.
    Но множественное наследование зависит не только от этого публичного поведения,
    но о том, как это поведение реализовано внутри.Множественное наследование будет работать
    если метод подчиняется своему базовому классу с помощью super () ,
    но нет, если метод выполняет свой собственный write () в файл,
    даже если любая реализация удовлетворяет модульному тесту.

    Таким образом, набор тестов должен выходить за рамки модульного тестирования.
    и выполнить фактическое множественное наследование класса -
    или патч обезьяны, чтобы убедиться, что log () вызывает super (). log () -
    чтобы гарантировать, что множественное наследование продолжает работать
    как будущие разработчики работают над кодом.

  2. Множественное наследование представило новый метод __init __ ()
    потому что ни один из методов базового класса __init __ ()
    принимает достаточно аргументов для комбинированного фильтра и регистратора.
    Этот новый код нужно протестировать,
    поэтому потребуется хотя бы один тест
    для каждого нового подкласса.

    У вас может возникнуть соблазн придумать схему
    чтобы избежать нового __init __ () для каждого подкласса,
    например, принять * args , а затем передать их super ().__init __ () .
    (Если вы будете придерживаться этого подхода,
    просмотрите классическое эссе «Питон считается вредным в высшей степени опасным».
    который утверждает, что на самом деле безопасно только ** кВт .)
    Проблема с такой схемой в том, что она ухудшает читаемость -
    вы больше не можете понять, какие аргументы принимает метод __init __ ()
    просто прочитав его список параметров.
    И инструменты проверки типов
    больше не сможет гарантировать правильность.

    Но даете ли вы каждому производному классу свой собственный __init __ ()
    или объедините их в цепочку,
    ваши модульные тесты оригинального FilteredLogger и SocketLogger
    не могут сами по себе гарантировать
    что классы правильно инициализируются при объединении.

    Напротив,
    дизайн декоратора оставляет свои инициализаторы
    счастливо и строго ортогонально.
    Фильтр принимает свой образец ,
    регистратор принимает свой носок ,
    и между ними нет возможного конфликта.

  3. Наконец,
    возможно, что два класса работают нормально сами по себе,
    но иметь атрибуты класса или экземпляра с тем же именем
    что столкнется
    когда классы объединены посредством множественного наследования.

    Да,
    наши маленькие примеры здесь
    сделать вероятность столкновения слишком малой, чтобы о ней беспокоиться -
    но помните, что эти примеры просто стоят в
    для более сложных классов
    вы можете писать в реальных приложениях.

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

По любой из этих причин
модульные тесты двух базовых классов могут оставаться зелеными
даже если их способность объединяться посредством множественного наследования
сломан.Это означает, что "Банда четырех"
«Взрыв подклассов для поддержки каждой комбинации»
также огорчит ваши тесты.
Только путем тестирования
каждая комбинация базовых классов м × n в вашем приложении
можете ли вы сделать безопасным для приложения использование таких классов во время выполнения.

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

  1. Самоанализ просто в случае с Decorator.
    Просто print (my_filter.logger) или просмотреть этот атрибут в отладчике
    чтобы увидеть, какой тип выходного регистратора прикреплен.
    Однако в случае множественного наследования
    вы можете только узнать, какой фильтр и регистратор были объединены
    изучив метаданные самого класса -
    либо прочитав его __mro__
    или подвергая объект серии тестов isinstance () .
  2. В случае с Decorator все тривиально
    взять живую комбинацию фильтра и логгера
    и во время выполнения заменить другой регистратор
    через присвоение .атрибут регистратора -
    скажем, потому что пользователь только что переключил предпочтение
    в интерфейсе приложения.
    Но сделать то же самое в случае множественного наследования
    потребует более нежелательного маневра
    перезаписи класса объекта.
    При изменении класса объекта во время выполнения
    не невозможно в динамическом языке, таком как Python,
    это обычно считается симптомом
    эта разработка программного обеспечения пошла не так.
  3. Наконец,
    множественное наследование не имеет встроенного механизма
    чтобы помочь программисту правильно упорядочить базовые классы. FilteredSocketLogger
    не будет успешно записывать в сокет, если его базовые классы поменяны местами и,
    как свидетельствуют десятки вопросов о переполнении стека,
    Программистам на Python вечно трудно
    с размещением сторонних базовых классов в правильном порядке.
    Паттерн Декоратор, напротив,
    делает очевидным, каким образом составляются классы:
    фильтру __init __ () нужен объект регистратора ,
    но регистратор __init __ () не запрашивает фильтр .

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

Dodge: Миксины

The FilteredSocketLogger из предыдущего раздела
нужен собственный метод __init __ ()
потому что ему нужно было принимать аргументы для обоих своих базовых классов.
Но оказывается, что эта ответственность
можно избежать.Конечно,
в случаях, когда подкласс не требует дополнительных данных,
проблемы не возникает.
Но даже классы, требующие дополнительных данных
может доставить его другими способами.

Мы можем сделать FilteredLogger
более дружелюбен к множественному наследованию
если мы предоставим значение по умолчанию для шаблона в самом классе
а затем предложите вызывающим абонентам настроить атрибут напрямую,
внеполосная инициализация:

 # Не принимайте «шаблон» во время инициализации.класс FilteredLogger (Регистратор):
    шаблон = ''

    def log (self, message):
        если self.pattern в сообщении:
            super (). log (сообщение)

# Множественное наследование стало проще.

класс FilteredSocketLogger (FilteredLogger, SocketLogger):
    pass # Этот подкласс не требует дополнительного кода!

# Вызывающий может просто установить «шаблон» напрямую.

logger = FilteredSocketLogger (sock1)
logger.pattern = 'Ошибка'

# Работает нормально.

logger.log ('Предупреждение: не так важно')
logger.log ('Ошибка: это важно')

print ('Получен сокет:% r'% sock2.recv (512))
 
 Сокет получил: b'Error: это важно \ n '
 

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

 # Упростите фильтр, сделав его миксином.

class FilterMixin: # Нет базового класса!
    шаблон = ''

    def log (self, message):
        если сам.шаблон в сообщении:
            super (). log (сообщение)

# Множественное наследование выглядит так же, как указано выше.

класс FilteredLogger (FilterMixin, FileLogger):
    pass # Опять же, подкласс не требует дополнительного кода.

# Работает нормально.

logger = FilteredLogger (sys.stdout)
logger.pattern = 'Ошибка'
logger.log ('Предупреждение: не так важно')
logger.log ('Ошибка: это важно')
 

Миксин концептуально проще
чем отфильтрованный подкласс, который мы видели в последнем разделе:
у него нет базового класса, который мог бы усложнить порядок разрешения методов,
поэтому super () всегда будет звонить
следующий базовый класс, указанный в заявлении class .

У миксина также более простая история тестирования, чем у эквивалентного подкласса.
Тогда как для FilteredLogger потребуются тесты
что оба запускают его автономно
а также объединить его с другими классами,
FilterMixin требует только тестов, которые сочетают его с регистратором.
Поскольку миксин сам по себе неполный,
Невозможно даже написать тест, который запускал бы его автономно.

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

Dodge: динамическое построение классов

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

Множественное наследование в общем случае по-прежнему требует
«Увеличение количества классов» с м × n классовые инструкции
что каждый выглядит:

 класс FilteredSocketLogger (FilteredLogger, SocketLogger):
    ...
 

Но оказывается, что Python предлагает обходной путь.

Представьте, что наше приложение читает файл конфигурации
чтобы узнать фильтр журнала и место назначения журнала, которое он должен использовать,
файл, содержимое которого неизвестно до времени выполнения.
Вместо того, чтобы строить все м × n возможных классов раньше времени
а затем выбрав нужный,
мы можем подождать и воспользоваться этим фактом
что Python не только поддерживает оператор class
но встроенная функция типа ()
который динамически создает новые классы во время выполнения:

 # Представьте себе 2 отфильтрованных регистратора и 3 регистратора вывода.filter = {
    'шаблон': PatternFilteredLog,
    'severity': SeverityFilteredLog,
}
output = {
    'файл': FileLog,
    'сокет': SocketLog,
    'syslog': SyslogLog,
}

# Выберите два класса, которые мы хотим объединить.

с open ('config') как f:
    имя_фильтра, имя_вывода = f.read (). split ()

filter_cls = фильтры [имя_фильтра]
output_cls = выходы [output_name]

# Создаем новый производный класс (!)

name = имя_фильтра.title () + output_name.title () + 'Журнал'
cls = тип (имя, (filter_cls, output_cls), {})

# Вызовите его как обычно, чтобы создать экземпляр.logger = cls (...)
 

Кортеж классов передан в тип ()
имеет то же значение
как ряд базовых классов в заявлении class .
Вызов type () выше создает новый класс
через множественное наследование как от отфильтрованного регистратора, так и от регистратора вывода.

Прежде, чем вы спросите:
да,
это также будет работать
для построения оператора класса в виде простого текста
а затем передайте его в eval () .

Но создание классов «на лету» связано с серьезными проблемами.

  • Страдает читаемость.
    Человек, читающий приведенный выше фрагмент кода
    придется делать дополнительную работу
    чтобы определить, какой объект является экземпляром cls .
    Кроме того, многие программисты Python
    не знакомы с типом ()
    и нужно будет остановиться и ломать голову над его документацией.
    Если у них возникнут трудности с новой концепцией
    что классы могут быть определены динамически,
    они все еще могут быть сбиты с толку.
  • Если сконструированный класс, например PatternFilteredFileLog
    указан в сообщении об исключении или ошибке,
    разработчик, вероятно, будет недоволен, обнаружив
    что ничего не возникает, когда они ищут код для этого имени класса.Отладка становится сложнее
    когда вы даже не можете найти класс.
    Может быть потрачено много времени
    поиск в кодовой базе type () звонков
    и пытаемся определить, какой из них сгенерировал класс.
    Иногда разработчикам приходится прибегать
    для вызова каждого метода с плохими аргументами
    и используя номера строк в результирующих трассировках
    чтобы отследить базовые классы.
  • Самоанализ типа, в общем случае,
    сбой для классов, динамически создаваемых во время выполнения.
    Ярлыки «Перейти к классу» в редакторе
    некуда будет тебя отвезти
    когда вы выделяете экземпляр PatternFilteredFileLog
    в отладчике.И движки проверки типов, такие как
    mypy
    и пир-чек
    вряд ли предложит сильную защиту для вашего сгенерированного класса
    что они могут предоставить
    для обычных классов Python.
  • Прекрасный блокнот Jupyter с функцией автоматической перезагрузки %
    обладает почти сверхъестественными способностями
    для обнаружения и перезагрузки измененного исходного кода в живом интерпретаторе Python.
    Но этому мешает, например, множественное наследование классов.
    что matplotlib строит во время выполнения
    через type () вызывает внутри своего subplot_class_factory () .

После того, как его обязательства будут взвешены,
попытка использовать генерацию классов среды выполнения как последний маневр
спасти и без того неисправный механизм множественного наследования
стоит как reductio ad absurdum
всего проекта уклонения от композиции над наследованием
когда вам нужно поведение объекта
варьироваться по нескольким независимым осям.

Наследование или композиция: что лучше для вашего проекта JavaScript? | автор: Фернандо Доглио

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

Наследование

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

Приведенный выше код TypeScript показывает основы наследования.Оба класса Dog и Cat расширяют или наследуют от FourLeggedAnimal , что означает, что они принимают все свои свойства и методы, нам не нужно переопределять их, если, как в моем примере, мы не хотим перезаписать что они делают. Наследование позволяет нам абстрагировать общее поведение, состояние (другими словами, методы и свойства) в едином месте, откуда мы можем извлечь (родительский класс).

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

Еще одно большое преимущество наследования, в основном для строго типизированных языков, таких как JAVA, TypeScript и другие, заключается в том, что переменные, объявленные с типом вашего родительского класса, могут также содержать объекты из его дочерних классов. Другими словами:

Следуя тем же определениям классов, что и раньше, в этом примере объявляется список элементов типа FourLeggedAnimals , в то время как на самом деле он содержит Cat и Dog . Это возможно, потому что оба объекта имеют один и тот же родительский класс.то же самое касается того факта, что мы можем безопасно вызвать метод speak в строке 6. Благодаря тому факту, что этот метод уже определен в родительском классе, мы знаем, что он будет у всех наших объектов (мы можем Не знаю, какая именно реализация будет использоваться, если мы не видели код, но мы можем быть уверены, что эта строка не выдаст ошибку из-за ошибки отсутствующего метода).

А что насчет композиции?

Хотя, как вы увидите, оба инструмента могут дать очень похожий результат, композиция - это совсем другое дело от наследования.Вместо подтипов, как мы делали раньше, композиция позволяет нам моделировать с одним отношением между объектами.

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

Если мы вернемся к нашему примеру с животными, мы можем переосмыслить нашу внутреннюю архитектуру и получить что-то вроде этого:

По сути, теперь у нас есть три разных компонента, в двух из которых мы инкапсулировали поведение ( Barker и компоненты Meower ) и в AnimalProperties указано, что мы инкапсулировали наше состояние.У нас больше нет общего класса для Dog и Cat , от которого мы могли бы наследовать.

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

Теперь мы можем перейти на следующий уровень и, добавив простой интерфейс, мы можем еще больше упростить наш код.

Проверьте это, у нас снова есть только один класс животных, который содержит нашу основную логику.И чтобы уточнить, интерфейс позволяет нам только обобщать форму нашего объекта, он ничего не реализует (интерфейсы на самом деле не реализуют код, а только определяют свой API). С его помощью мы можем создать общий «тип», если хотите, который описывает форму нашего объекта, что, в свою очередь, позволяет нам определять переменную для переноса любого из них внутрь (см. Свойство актера внутри класса Animal ).

Как же тогда создать Dog или Cat ? Позвольте мне показать вам:

Перво-наперво: обратите внимание, что конечный результат такой же.Мы можем создать общий список животных, перебрать его и вызвать тот же метод со 100% уверенностью, что он будет у всех объектов. Опять же, мы не знаем, как это будет реализовано, потому что именно здесь компоненты (в данном случае) вступают в игру.

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

И если вы еще не заметили, хотя тот же эффект может быть достигнут либо наследованием, либо композицией, первый происходит во время компиляции (или интерпретации), а второй - во время выполнения.Насколько это важно? На самом деле очень важно , понимаете, при правильном наборе методов вы можете превратить Cat в Dog или даже в совершенно новое животное во время выполнения, чего вы не могли бы сделать с наследованием.

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

Теперь, когда мы понимаем, что это такое и как их можно использовать, правда в том, что они оба великолепны!

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

Случай наследования

Наследование имеет смысл, потому что мы склонны связывать концепции ООП с объектами реального мира, а затем пытаемся обобщить их поведение, обобщая их природу.

Другими словами, мы не думаем, что кошка и доктор имеют 4 ноги и набор органов, которые позволяют им либо лаять, либо мяукать.Мы думаем о них как о животных, , что переводится как наследование.

И поэтому идеальный вариант использования для перехода с наследованием - это когда 80% вашего кода является общим для двух или более классов, и в то же время конкретный код сильно отличается от . И не только это, но и уверенность в том, что ни в коем случае не потребуется менять конкретный код друг на друга. Тогда наследование - это определенно правильный путь, с ним у вас будет более простая внутренняя архитектура и меньше кода, о котором нужно думать.

Чемодан для композиции

Как мы уже видели, композиция - это очень гибкий инструмент . Вам определенно нужно немного изменить свое мышление, особенно если вас, как и меня, уже учили наследованию в прошлом и то, что это единственное решение для повторного использования кода внутри контекста ООП.

Однако теперь, когда вы увидели свет (кстати, добро пожаловать), вы знаете, что это неправда. Мало того, вы также знаете, что общий код также можно абстрагировать на различные компоненты, которые, в свою очередь, могут быть настолько сложными, насколько им нужно (при условии, что их общедоступный интерфейс остается неизменным), и что мы можем менять их местами во время выполнения, что для меня является удивительно гибким.

Еще одно большое преимущество, которое я вижу, заключается в том, что при наследовании, если вам нужно создать новый конкретный класс (например, добавить сейчас класс Lion ), вам нужно будет понять код класса FourLeggedAnimal , чтобы сделать уверен, что теперь ты получаешь от этого. И это было бы просто для того, чтобы вы могли реализовать другую версию метода speak . Однако, если вы выбрали композицию, все, что вам нужно было сделать, это создать новый класс, реализующий новую логику для метода speak , не зная ни о чем другом, и все.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *