Обзор
Это вторая статья из короткой серии, посвященной шаблонам проектирования в Python .
Структурные шаблоны проектирования
Структурные шаблоны проектирования используются для сборки нескольких классов в более крупные рабочие структуры.
Иногда интерфейсы для работы с несколькими объектами просто не подходят, или вы работаете с устаревшим кодом, который не можете изменить, но нуждаетесь в новой функциональности, или вы просто начинаете замечать, что ваши структуры кажутся неопрятными и чрезмерными, но все элементы кажутся необходимыми .
Они очень полезны для создания читаемого, поддерживаемого, многоуровневого кода, особенно при работе с внешними библиотеками, устаревшим кодом, взаимозависимыми классами или многочисленными объектами.
В этой статье рассматриваются следующие шаблоны проектирования:
Адаптер
В реальном мире вы можете использовать адаптер для подключения зарядных устройств к разным розеткам во время поездок в другие страны или к другим моделям телефонов. Вы можете использовать их для подключения старого монитора VGA к разъему HDMI на вашем новом ПК.
Шаблон проектирования получил свое название, потому что его цель одна и та же - адаптация одного входа к другому заранее определенному выходу.
Проблема
Допустим, вы работаете над программным обеспечением для отображения
изображений, и пока ваши клиенты хотели отображать только растровые
изображения . У вас есть
полная реализация для вывода, скажем, .png
на экран.
Для простоты функциональность выглядит так:
from abc import ABC, abstractmethod
class PngInterface(ABC):
@abstractmethod
def draw(self):
pass
class PngImage(PngInterface):
def __init__(self, png):
self.png = png
self.format = "raster"
def draw(self):
print("drawing " + self.get_image())
def get_image(self):
return "png"
Но вы хотите расширить свою целевую аудиторию, предлагая больше функций, поэтому вы решили заставить свою программу работать и с векторной графикой .
Как оказалось, есть библиотека для работы с векторной графикой, которую
вы можете использовать вместо того, чтобы самостоятельно реализовывать
все эти совершенно новые функции. Однако классы не соответствуют вашему
интерфейсу (они не реализуют метод draw()
):
class SvgImage:
def __init__(self, svg):
self.svg = svg
self.format = "vector"
def get_image(self):
return "svg"
Вы не хотите проверять тип каждого объекта, прежде чем что-либо с ним делать, вы действительно хотите использовать единый интерфейс - тот, который у вас уже есть.
Решение
Чтобы решить эту проблему, мы реализуем класс адаптера. Как и в случае
с реальными адаптерами, наш класс принимает доступный извне ресурс (
SvgImage
) и преобразует его в результат, который нам подходит.
В этом случае мы делаем это путем растрирования векторного изображения, чтобы мы могли нарисовать его, используя те же функции, которые мы уже реализовали.
Опять же, для простоты, мы просто распечатаем "png"
, хотя эта функция
будет рисовать изображение в реальной жизни.
Адаптер объекта
Адаптер объекта просто обертывает внешний (служебный) класс, предлагая интерфейс, соответствующий нашему собственному (клиентскому) классу. В данном случае сервис предоставляет нам векторную графику, а наш адаптер выполняет растеризацию и рисует получившееся изображение:
class SvgAdapter(png_interface):
def __init__(self, svg):
self.svg = svg
def rasterize(self):
return "rasterized " + self.svg.get_image()
def draw(self):
img = self.rasterize()
print("drawing " + img)
Итак, давайте проверим, как работает наш адаптер:
regular_png = PngImage("some data")
regular_png.draw()
example_svg = SvgImage("some data")
example_adapter = SvgAdapter(example_svg)
example_adapter.draw()
Передача regular_png
отлично работает для нашей функции
graphic_draw()
. Однако передача regular_svg
не работает. После
адаптации regular_svg
мы можем использовать его адаптированную форму
так же, как мы использовали бы изображение .png
drawing png
drawing rasterized svg
В нашей функции graphic_draw()
не нужно ничего менять. Он работает так
же, как и раньше. Мы просто адаптировали ввод для уже существующей
функции.
Адаптер класса
Адаптеры классов могут быть реализованы только на языках, поддерживающих множественное наследование . Они наследуют как наш класс, так и внешний класс, тем самым наследуя все их функции. По этой причине экземпляр адаптера может заменить либо наш класс, либо внешний класс в рамках единого интерфейса.
Чтобы мы могли это сделать, нам нужно каким-то образом проверить, нужно ли нам выполнять преобразование или нет. Чтобы проверить это, мы вводим исключение:
class ConvertingNonVector(Exception):
# An exception used by class_adapter to check
# whether an image can be rasterized
pass
И с этим мы можем сделать адаптер класса:
class ClassAdapter(png_image, svg_image):
def __init__(self, image):
self.image = image
def rasterize(self):
if(self.image.format == "vector"):
return "rasterized " + self.image.get_image()
else:
raise ConvertingNonVector
def draw(self):
try:
img = self.rasterize()
print("drawing " + img)
except ConvertingNonVector as e:
print("drawing " + self.image.get_image())
Чтобы проверить, хорошо ли он работает, давайте проверим его на
изображениях .png
и .svg
example_png = PngImage("some data")
regular_png = ClassAdapter(example_png)
regular_png.draw()
example_svg = SvgImage("some data")
example_adapter = ClassAdapter(example_svg)
example_adapter.draw()
Выполнение этого кода приводит к:
drawing png
drawing rasterized svg
Адаптер объекта или класса?
В общем, вы должны предпочесть использовать объектные адаптеры . Есть две основные причины, чтобы предпочесть его классовой версии, а именно:
- Принцип композиции над
наследованием,
обеспечивающий слабую связь. В приведенном выше примере поле
предполагаемого
format
не обязательно должно существовать для работы адаптера объекта, в то время как оно необходимо для адаптера класса. - Дополнительная сложность, которая может привести к проблемам, сопровождающим множественное наследование.
Мост
Проблема
Большой класс может нарушать принцип единой ответственности, и может потребоваться разделение на отдельные классы с отдельными иерархиями. Это может быть расширено до большой иерархии классов, которую необходимо разделить на две отдельные, но взаимозависимые иерархии.
Например, представьте, что у нас есть классовая структура, включающая
средневековые здания. У нас есть wall
, tower
, stable
, mill
,
house
, armory
и т. Д. Теперь мы хотели различать их в зависимости
от того, из каких материалов они сделаны. Мы могли бы унаследовать
каждый класс и создать straw_wall
, log_wall
, cobblestone_wall
стену, limestone_watchtower
башню и т. Д.
Кроме того, tower
может быть расширена в watchtower
, lighthouse
и
castle_tower
.
{.ezlazyload}
Но это привело бы к экспоненциальному росту количества классов, если бы мы продолжали добавлять атрибуты аналогичным образом. Кроме того, у этих классов будет много повторяющегося кода.
Более того, будет ли limestone_watchtower
башня расширять
limestone_tower
башню и добавлять особенности сторожевой башни или
расширять watchtower
и добавлять особенности материала?
Решение
Чтобы этого избежать, мы возьмем основную информацию и сделаем ее общей,
на которой мы будем строить вариации. В нашем случае мы разделим
иерархию классов для Building
и Material
.
Нам нужно создать мост между всеми Building
и всеми Material
чтобы
мы могли генерировать их вариации без необходимости определять их как
отдельные классы. Поскольку материал можно использовать во многих вещах,
Building
будет содержать Material
качестве одного из полей:
from abc import ABC, abstractmethod
class Material(ABC):
@abstractmethod
def __str__(self):
pass
class Cobblestone(Material):
def __init__(self):
pass
def __str__(self):
return 'cobblestone'
class Wood(Material):
def __init__(self):
pass
def __str__(self):
return 'wood'
Итак, давайте Building
класс Building:
from abc import ABC, abstractmethod
class Building(ABC):
@abstractmethod
def print_name(self):
pass
class Tower(Building):
def __init__(self, name, material):
self.name = name
self.material = material
def print_name(self):
print(str(self.material) + ' tower ' + self.name)
class Mill(Building):
def __init__(self, name, material):
self.name = name
self.material = material
def print_name(self):
print(str(self.material) + ' mill ' + self.name)
Теперь, когда мы хотим создать булыжниковую мельницу или деревянную
башню, нам не нужны классы CobblestoneMill
или WoodenTower
Вместо
этого мы можем создать экземпляр Mill
или Tower
и назначить ему
любой материал, который нам нужен:
cobb = Cobblestone()
local_mill = Mill('Hilltop Mill', cobb)
local_mill.print_name()
wooden = Wood()
watchtower = Tower('Abandoned Sentry', wooden)
watchtower.print_name()
Выполнение этого кода даст:
cobblestone mill Hilltop Mill
wooden tower Abandoned Sentry
Композитный
Проблема
Представьте, что вы управляете службой доставки, а поставщики отправляют через вашу компанию большие коробки с товарами. Вы захотите узнать стоимость предметов внутри, потому что вы взимаете плату за дорогостоящие посылки. Конечно, это делается автоматически, потому что нужно все распаковывать - хлопотно.
Это не так просто, как просто запустить цикл, потому что структура каждого блока нерегулярна. Конечно, вы можете перебирать элементы внутри, но что произойдет, если в коробке есть другая коробка с элементами внутри? Как ваша петля может с этим справиться?
Конечно, вы можете проверить класс каждого зацикленного элемента, но это только усложняет задачу. Чем больше у вас классов, тем больше крайних случаев, что приводит к немасштабируемой системе.
Решение
Что примечательно в подобных задачах, так это то, что они имеют древовидную иерархическую структуру. У вас самая большая коробка наверху. И тогда у вас есть более мелкие предметы или внутри коробки. Хороший способ справиться с такой структурой - иметь объект непосредственно над ним, контролируя поведение тех, кто находится под ним.
Шаблон проектирования Composite используется для создания древовидных структур и обработки коллекций объектов аналогичным образом.
В нашем примере мы могли бы сделать каждый ящик содержащим его
содержимое, и убедиться, что все ящики и элементы имеют функцию -
return_price()
. Если вы вызываете return_price()
для ящика, он
просматривает его содержимое и складывает их цены (также рассчитанные
путем вызова их return_price()
), а если у вас есть элемент, он просто
возвращает его цену.
Мы создали ситуацию, похожую на рекурсию, в которой мы решаем большую проблему, разделяя ее на более мелкие и выполняя над ними одну и ту же операцию. В некотором смысле мы проводим поиск в глубину иерархии объектов.
Мы определим абстрактный item
класса, что все наши конкретные пункты
наследуют от:
from abc import ABC, abstractmethod
class Item(ABC):
@abstractmethod
def return_price(self):
pass
Теперь давайте определим некоторые продукты, которые наши поставщики могут отправлять через нашу компанию:
class Box(Item):
def __init__(self, contents):
self.contents = contents
def return_price(self):
price = 0
for item in self.contents:
price = price + item.return_price()
return price
class Phone(Item):
def __init__(self, price):
self.price = price
def return_price(self):
return self.price
class Charger(Item):
def __init__(self, price):
self.price = price
def return_price(self):
return self.price
class Earphones(Item):
def __init__(self, price):
self.price = price
def return_price(self):
return self.price
Сам Box
также является Item
и мы можем добавить экземпляр Box
внутри экземпляра Box
Давайте создадим экземпляры нескольких элементов
и поместим их в коробку, прежде чем получить его значение:
phone_case_contents = []
phone_case_contents.append(Phone(200))
phone_case_box = Box(phone_case_contents)
big_box_contents = []
big_box_contents.append(phone_case_box)
big_box_contents.append(Charger(10))
big_box_contents.append(Earphones(10))
big_box = Box(big_box_contents)
print("Total price: " + str(big_box.return_price()))
Выполнение этого кода приведет к:
Total price: 220
Декоратор
Проблема
Представьте, что вы делаете видеоигру. Основная механика вашей игры заключается в том, что игрок может добавлять различные бонусы в середине битвы из случайного пула.
Эти способности на самом деле нельзя упростить и поместить в список, который вы можете перебирать, некоторые из них фундаментально перезаписывают то, как персонаж игрока движется или стремится, некоторые просто добавляют эффекты к своим способностям, некоторые добавляют совершенно новые функции, если вы что-то нажимаете и т. Д. .
Сначала вы можете подумать об использовании наследования для решения
этой проблемы. В конце концов, если у вас есть basic_player
, вы
можете унаследовать от него blazing_player
, bouncy_player
и
bowman_player
.
Но как насчет blazing_bouncy_player
, bouncy_bowman_player
,
blazing_bowman_player
и blazing_bouncy_bowman_player
?
По мере того, как мы добавляем новые возможности, структура становится все более сложной, нам приходится использовать множественное наследование или повторять код, и каждый раз, когда мы добавляем что-то в игру, требуется много работы, чтобы заставить это работать со всем остальным.
Решение
Шаблон декоратора используется для добавления функциональности классу без изменения самого класса. Идея состоит в том, чтобы создать оболочку, которая соответствует тому же интерфейсу, что и класс, который мы обертываем, но переопределяет его методы.
Он может вызвать метод из объекта-члена, а затем просто добавить некоторые свои собственные функции поверх него или полностью переопределить его. Затем декоратор (оболочка) можно обернуть другим декоратором, который работает точно так же.
Таким образом, мы можем украшать объект столько раз, сколько захотим, не
меняя ни единого бита исходного класса. Давайте продолжим и определим
PlayerDecorator
:
from abc import ABC, abstractmethod
class PlayerDecorator(ABC):
@abstractmethod
def handle_input(self, c):
pass
А теперь давайте определим BasePlayer
с некоторым поведением по
умолчанию и его подклассы, указав другое поведение:
class BasePlayer:
def __init__(self):
pass
def handle_input(self, c):
if c=='w':
print('moving forward')
elif c == 'a':
print('moving left')
elif c == 's':
print('moving back')
elif c == 'd':
print('moving right')
elif c == 'e':
print('attacking ')
elif c == ' ':
print('jumping')
else:
print('undefined command')
class BlazingPlayer(PlayerDecorator):
def __init__(self, wrapee):
self.wrapee = wrapee
def handle_input(self, c):
if c == 'e':
print('using fire ', end='')
self.wrapee.handle_input(c)
class BowmanPlayer(PlayerDecorator):
def __init__(self, wrapee):
self.wrapee = wrapee
def handle_input(self, c):
if c == 'e':
print('with arrows ', end='')
self.wrapee.handle_input(c)
class BouncyPlayer(PlayerDecorator):
def __init__(self, wrapee):
self.wrapee = wrapee
def handle_input(self, c):
if c == ' ':
print('double jump')
else:
self.wrapee.handle_input(c)
BasePlayer
их один за другим, начав с BasePlayer:
player = BasePlayer()
player.handle_input('e')
player.handle_input(' ')
Выполнение этого кода вернет:
attacking
jumping
Теперь давайте обернем его другим классом, который по-другому обрабатывает эти команды:
player = BlazingPlayer(player)
player.handle_input('e')
player.handle_input(' ')
Это вернет:
using fire attacking
jumping
Теперь добавим характеристики BouncyPlayer
player = BouncyPlayer(player)
player.handle_input('e')
player.handle_input(' ')
using fire attacking
double jump
Стоит отметить, что player
использует огненную атаку, а также
двойной прыжок. Украшаем player
разными классами. Украсим еще немного:
player = BowmanPlayer(player)
player.handle_input('e')
player.handle_input(' ')
Это возвращает:
with arrows using fire attacking
double jump
Фасад
Проблема
Допустим, вы моделируете явление, возможно, эволюционную концепцию, например, равновесие между различными стратегиями. Вы отвечаете за серверную часть и должны программировать, что делают образцы, когда они взаимодействуют, каковы их свойства, как работают их стратегии, как они приходят к взаимодействию друг с другом, какие условия заставляют их умирать или воспроизводиться, и т.п.
Ваш коллега работает над графическим изображением всего этого. Им не важна основная логика вашей программы, различные функции, которые проверяют, с кем имеет дело образец, сохраняют информацию о предыдущих взаимодействиях и т. Д.
Ваша сложная базовая структура не очень важна для вашего коллеги, он просто хочет знать, где находится каждый образец и как они должны выглядеть.
Итак, как сделать вашу сложную систему доступной для тех, кто мало знает теорию игр и меньше - о вашей конкретной реализации какой-либо проблемы?
Решение
Паттерн Фасад требует фасада вашей реализации. Людям не нужно знать все о базовой реализации. Вы можете создать большой класс, который будет полностью управлять вашей сложной подсистемой и просто предоставлять функциональные возможности, которые могут понадобиться вашему пользователю.
В случае вашего коллеги, он, вероятно, захочет иметь возможность перейти к следующей итерации моделирования и получить информацию о координатах объекта и соответствующей графике для их представления.
Допустим, следующий фрагмент кода - наша «сложная система». Вы, естественно, можете пропустить его чтение, поскольку дело в том, что вам не нужно знать подробности, чтобы использовать его:
class Hawk:
def __init__(self):
self.asset = '(`A´)'
self.alive = True
self.reproducing = False
def move(self):
return 'deflect'
def reproduce(self):
return hawk()
def __str__(self):
return self.asset
class Dove:
def __init__(self):
self.asset = '(๑•́ω•̀)'
self.alive = True
self.reproducing = False
def move(self):
return 'cooperate'
def reproduce(self):
return dove()
def __str__(self):
return self.asset
def iteration(specimen):
half = len(specimen)//2
spec1 = specimen[:half]
spec2 = specimen[half:]
for s1, s2 in zip(spec1, spec2):
move1 = s1.move()
move2 = s2.move()
if move1 == 'cooperate':
# both survive, neither reproduce
if move2 == 'cooperate':
pass
# s1 dies, s2 reproduces
elif move2 == 'deflect':
s1.alive = False
s2.reproducing = True
elif move1 == 'deflect':
# s2 dies, s1 reproduces
if move2 == 'cooperate':
s2.alive = False
s1.reproducing = True
# both die
elif move2 == 'deflect':
s1.alive = False
s2.alive = False
s = spec1 + spec2
s = [x for x in s if x.alive == True]
for spec in s:
if spec.reproducing == True:
s.append(spec.reproduce())
spec.reproducing = False
return s
Теперь, когда мы передадим этот код нашему коллеге, он потребует от него ознакомиться с внутренним устройством, прежде чем пытаться визуализировать животных. Вместо этого давайте закрасим его фасадом и дадим им несколько удобных функций для итерации популяции и доступа к отдельным животным из нее:
import random
class Simulation:
def __init__(self, hawk_number, dove_number):
self.population = []
for _ in range(hawk_number):
self.population.append(hawk())
for _ in range(dove_number):
self.population.append(dove())
random.shuffle(self.population)
def iterate(self):
self.population = iteration(self.population)
random.shuffle(self.population)
def get_assets(self):
return [str(x) for x in population]
Любопытный читатель может поиграться с вызовом iterate()
и посмотреть,
что происходит с населением.
Наилегчайший вес
Проблема
Вы работаете над видеоигрой. В вашей игре много пуль, и каждая пуля - это отдельный объект. У ваших пуль есть некоторая уникальная информация, такая как их координаты и скорость, но они также имеют некоторую общую информацию, такую как форма и текстура.
class Bullet:
def __init__(self, x, y, z, velocity):
self.x = x
self.y = y
self.z = z
self.velocity = velocity
self.asset = '■■►'
Это заняло бы много памяти, особенно если в воздухе одновременно летит много пуль (и мы не будем сохранять смайлик Unicode вместо ресурсов в реальной жизни).
Определенно было бы предпочтительнее просто получить текстуру из памяти один раз, сохранить ее в кеше и сделать так, чтобы все маркеры совместно использовали эту единственную текстуру, вместо того, чтобы копировать ее десятки или сотни раз.
Если была выпущена пуля другого типа, с другой текстурой - мы создали бы оба экземпляра и вернули бы их. Хотя, если мы имеем дело с повторяющимися значениями - мы можем сохранить исходное значение в пуле / кеше и просто извлечь оттуда.
Решение
Шаблон « Легковес» требует общего пула, когда может существовать множество экземпляров объекта с одинаковым значением. Известной его реализацией является Java String Pool - где, если вы пытаетесь создать экземпляры двух разных строк с одинаковым значением, создается только одна, а другая просто ссылается на первую.
Некоторые части наших данных уникальны для каждой отдельной пули. Это называются внешними чертами . С другой стороны, данные, общие для всех маркеров, такие как вышеупомянутые текстура и форма, называются внутренними характеристиками .
Что мы можем сделать, так это разделить эти черты, чтобы все внутренние черты хранились в одном экземпляре - классе Flyweight . Внешние признаки находятся в отдельных экземплярах, называемых классами контекста . Класс Flyweight обычно содержит все методы исходного класса и работает, передавая им экземпляр класса Context .
Чтобы программа работала должным образом, класс Flyweight должен быть неизменным. Таким образом, если он вызывается из разных контекстов, неожиданного поведения не будет.
Для практического использования часто используется фабрика наилегчайшего веса. Это класс, который при передаче внутреннего состояния проверяет, существует ли уже объект с таким состоянием, и возвращает его, если он существует. Если это не так, он создает новый объект и возвращает его:
class BulletContext:
def __init__(self, x, y, z, velocity):
self.x = x
self.y = y
self.z = z
self.velocity = velocity
class BulletFlyweight:
def __init__(self):
self.asset = '■■►'
self.bullets = []
def bullet_factory(self, x, y, z, velocity):
bull = [b for b in self.bullets if bx==x and by==y and bz==z and b.velocity==velocity]
if not bull:
bull = bullet(x,y,z,velocity)
self.bullets.append(bull)
else:
bull = bull[0]
return bull
def print_bullets(self):
print('Bullets:')
for bullet in self.bullets:
print(str(bullet.x)+' '+str(bullet.y)+' '+str(bullet.z)+' '+str(bullet.velocity))
Мы сделали наши контексты и наилегчайший вес. Каждый раз, когда мы
пытаемся добавить новый контекст (маркер) с помощью функции
bullet_factory()
она генерирует список существующих маркеров, которые
по сути являются одними и теми же маркерами. Если мы найдем такую пулю,
мы можем просто вернуть ее. Если мы этого не сделаем, мы создадим новый.
Имея это в виду, давайте воспользуемся bullet_factory()
чтобы создать
несколько маркеров и распечатать их значения:
bf = BulletFlyweight()
# adding bullets
bf.bullet_factory(1,1,1,1)
bf.bullet_factory(1,2,5,1)
bf.print_bullets()
Это приводит к:
Bullets:
1 1 1 1
1 2 5 1
Теперь давайте попробуем добавить больше маркеров через фабрику, которая уже существует:
# trying to add an existing bullet again
bf.bullet_factory(1,1,1,1)
bf.print_bullets()
Это приводит к:
Bullets:
1 1 1 1
1 2 5 1
Прокси
Проблема
Больница использует часть программного обеспечения с
PatientFileManager
для сохранения данных о своих пациентах. Однако, в
зависимости от вашего уровня доступа, вы не сможете просматривать файлы
некоторых пациентов. В конце концов, право на неприкосновенность частной
жизни запрещает больнице распространять эту информацию дальше, чем
необходимо для оказания им услуг.
Это всего лишь один пример - шаблон прокси на самом деле можно использовать в самых разных обстоятельствах, в том числе:
- Обработка доступа к дорогостоящему объекту, например удаленному серверу или базе данных.
- Замещение объектов, инициализация которых может быть дорогостоящей до тех пор, пока они действительно не понадобятся программе, например текстуры, которые занимают много места в ОЗУ или большую базу данных.
- Управление доступом в целях безопасности
Решение
В нашем примере с больницей вы можете создать другой класс, например
AccessManager
, который определяет, какие пользователи могут или не
могут взаимодействовать с определенными функциями PatientFileManager
.
AccessManager
- это прокси- класс, и через него пользователь
взаимодействует с базовым классом.
PatientFileManager
класс PatientFileManager:
class PatientFileManager:
def __init__(self):
self.__patients = {}
def _add_patient(self, patient_id, data):
self.__patients[patient_id] = data
def _get_patient(self, patient_id):
return self.__patients[patient_id]
Теперь давайте сделаем для этого прокси:
class AccessManager(PatientFileManager):
def __init__(self, fm):
self.fm = fm
def add_patient(self, patient_id, data, password):
if password == 'sudo':
self.fm._add_patient(patient_id, data)
else:
print("Wrong password.")
def get_patient(self, patient_id, password):
if password == 'totallytheirdoctor' or password == 'sudo':
return self.fm._get_patient(patient_id)
else:
print("Only their doctor can access this patients data.")
Вот у нас есть пара чеков. Если для прокси-сервера предоставлен
правильный AccessManager
экземпляр AccessManager может добавлять или
получать информацию о пациенте. Если пароль неправильный - не может.
Теперь давайте AccessManager
экземпляр AccessManager и добавим
пациента:
am = AccessManager(PatientFileManager())
am.add_patient('Jessica', ['pneumonia 2020-23-03', 'shortsighted'], 'sudo')
print(am.get_patient('Jessica', 'totallytheirdoctor'))
Это приводит к:
['pneumonia 2020-23-03', 'shortsighted']
Здесь важно отметить, что в Python нет настоящих частных переменных - подчеркивания - это просто указание другим программистам не трогать вещи. Таким образом, в этом случае реализация прокси будет больше служить сигналом о вашем намерении по управлению доступом, а не реальным управлением доступом.
Заключение
При этом все шаблоны структурного проектирования в Python полностью покрыты с рабочими примерами.
Многие программисты начинают использовать их как решения, основанные на здравом смысле, но зная мотивацию и вид проблемы для использования некоторых из них, вы, надеюсь, сможете начать распознавать ситуации, в которых они могут быть полезны, и иметь готовый подход к решению проблемы. .