본문 바로가기
Practical Python Design Patterns

Visitor Pattern

by 자동매매 2023. 3. 29.

CHAPTER 19 Visitor Pattern

I want to believe.

X-Files

Since Python can be found in many places, you might one day want to do a little bit of home automation. Get a couple of single-board and micro computers and connect them to some hardware sensors and actuators, and soon you have a network of devices, all controlled by you. Each of these items in the network has its own functionality, and each performs different interactions like metering the light levels in the home, or checking the temperature. You could encapsulate the functionality of each device as an object

in a virtual network that matches the physical wired network, then treat the whole system like you would any other network of objects. This chapter will focus on that side of the implementation, assuming all the elements have already been wired up and are modeled as objects in the system.

The network in our simulation will have the following components:

Thermostat

Temperature regulator Front door lock

Coffee machine

Bedroom lights

Kitchen lights

Clock

Suppose each object class has the functionality to check the status of the device it is connected to. These return different values. Lights might return 1 for the lights are on and 0 for the lights are off, and an error message, -1, when the lights controller

271

' Wessel Badenhorst 2017

W. Badenhorst, Practical Python Design Patterns, https://doi.org/10.1007/978-1-4842-2680-3_18
CHAPTER VISITOR PATTERN

cannot be reached. The thermostat might return the actual temperature it is reading

and None if it is offline. The front door lock is similar to the lights, with 1 being locked, 0 unlocked, and -1 error. The coffee machine has states for error, off, on, brewing, waiting, and heating using integers from -1 to 4, respectively. The temperature regulator has

states for heating, cooling, on, off, and error. The clock either returns None if the device is disconnected or the time as a Python time value.

The classes involved with concrete instances will look something like this:

import random

class Light(object): def __init__(self):

pass

def get_status(self):

return random.choice(range(-1,2))

class Thermostat(object): def __init__(self):

pass

def get_status(self):

temp_range = [x for x in range(-10, 31)] temp_range.append(None)

return random.choice(temp_range)

class TemperatureRegulator(object):

def __init__(self):

pass

def get_status(self):

return random.choice([’heating’, ’cooling’, ’on’, ’off’, ’error’])

class DoorLock(object): def __init__(self):

pass

def get_status(self):

return random.choice(range(-1,2))

272
CHAPTER VISITOR PATTERN

class CoffeeMachine(object):

def _init_(self):

pass

def get_status(self):

return random.choice(range(-1,5))

class Clock(object): def __init__(self):

pass

def get_status(self):

return "{}:{}".format(random.randrange(24), random.randrange(60))

def main():

device_network = [

Thermostat(), TemperatureRegulator(), DoorLock(), CoffeeMachine(), Light(),

Light(),

Clock(),

]

for device in device_network: print(device.get_status())

if __name__ == "__main__":

main()

The output is somewhat messy and looks like this:

0 off -1

2

0

-1 9:26

273
CHAPTER VISITOR PATTERN

This is a lot cleaner than the types of output you will encounter in the real world, but this is a good representation of the messy nature of real-world devices. We now have a simulation of a network of devices. We can move on to the parts we are really interested in, namely doing something with these devices.

The first thing we are interested in is checking the status of the device attached to the object to determine if the device is online or not. Let s create a flat collection containing the nodes in the network and then implement an is_online method to tell us if the

device is online or not. Then, we can look at each device in turn and ask if it is online

or not. We also added a name parameter to each constructor so we can easily see what device we are currently dealing with.

import random

class Light(object):

def __init__(self, name):

self.name = name

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.get_status() != -1

class Thermostat(object):

def __init__(self, name):

self.name = name

def get_status(self):

temp_range = [x for x in range(-10, 31)] temp_range.append(None)

return random.choice(temp_range)

def is_online(self):

return self.get_status() is not None

class TemperatureRegulator(object): def __init__(self, name):

self.name = name

274
CHAPTER VISITOR PATTERN

def get_status(self):

return random.choice([’heating’, ’cooling’, ’on’, ’off’, ’error’])

def is_online(self):

return self.get_status() != ’error’

class DoorLock(object):

def __init__(self, name):

self.name = name

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.get_status() != -1

class CoffeeMachine(object): def __init__(self, name):

self.name = name

def get_status(self):

return random.choice(range(-1,5))

def is_online(self):

return self.get_status() != -1

class Clock(object):

def __init__(self, name):

self.name = name

def get_status(self):

return "{}:{}".format(random.randrange(24), random.randrange(60))

def is_online(self):

return True

275
CHAPTER VISITOR PATTERN

def main():

device_network = [

Thermostat("General Thermostat"), TemperatureRegulator("Thermal Regulator"), DoorLock("Front Door Lock"), CoffeeMachine("Coffee Machine"), Light("Bedroom Light"),

Light("Kitchen Light"),

Clock("System Clock"),

]

for device in device_network:

print("{} is online: \t{}".format(device.name, device.is_online()))

if __name__ == "__main__":

main()

Since the simulated response is randomly generated, you should not expect your output to be an exact match of the one given here, but the general shape of the output should hold.

General Thermostat is online: True Thermal Regulator is online: True Front Door Lock is online: True Coffee Machine is online: False Bedroom Light is online: False Kitchen Light is online: True System Clock is online: True

We now want to add a boot sequence to turn on all the devices and get them into their initial state, like setting the clock to 00:00, starting the coffee machine, turning off all the lights, and turning on but not setting any settings on the temperature system. We will leave the front door in the state that it is in.

Since we are now becoming interested in the actual state of the system, we will remove the get_status method and instead set a status attribute to a random value in the __init__() method.

276
CHAPTER VISITOR PATTERN

One other thing that you should note is that we now import the TestCase class from the unittest library, which allows us to write tests to make sure that, after applying the boot sequence to a device, that device is indeed in the state we expect it to be. This is not an in-depth tutorial on unit testing, but will be good for you to see so you gain a deeper level of comfort with the testing tools available in Python.

import random import unittest

class Light(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.status != -1

def boot_up(self):

self.status = 0

class Thermostat(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

temp_range = [x for x in range(-10, 31)] temp_range.append(None)

return random.choice(temp_range)

def is_online(self):

return self.status is not None

def boot_up(self):

pass

277
CHAPTER 19 VISITOR PATTERN

class TemperatureRegulator(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):s

return random.choice([’heating’, ’cooling’, ’on’, ’off’, ’error’])

def is_online(self):

return self.status != ’error’

def boot_up(self):

self.status = ’on’

class DoorLock(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.status != -1

def boot_up(self):

pass

class CoffeeMachine(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,5))

def is_online(self):

return self.status != -1

def boot_up(self):

self.status = 1

278
CHAPTER 19 VISITOR PATTERN

class Clock(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return "{}:{}".format(random.randrange(24), random.randrange(60))

def is_online(self):

return True

def boot_up(self):

self.status = "00:00"

class HomeAutomationBootTests(unittest.TestCase):

def setUp(self):

self.thermostat = Thermostat("General Thermostat") self.thermal_regulator = TemperatureRegulator("Thermal Regulator") self.front_door_lock = DoorLock("Front Door Lock") self.coffee_machine = CoffeeMachine("Coffee Machine") self.bedroom_light = Light("Bedroom Light")

self.system_clock = Clock("System Clock")

def test_boot_thermostat_does_nothing_to_state(self):

state_before = self.thermostat.status self.thermostat.boot_up() self.assertEqual(state_before, self.thermostat.status)

def test_boot_thermal_regulator_turns_it_on(self):

self.thermal_regulator.boot_up() self.assertEqual(self.thermal_regulator.status, ’on’)

def test_boot_front_door_lock_does_nothing_to_state(self):

state_before = self.front_door_lock.status self.front_door_lock.boot_up() self.assertEqual(state_before, self.front_door_lock.status)

def test_boot_coffee_machine_turns_it_on(self):

self.coffee_machine.boot_up() self.assertEqual(self.coffee_machine.status, 1)

279
CHAPTER VISITOR PATTERN

def test_boot_light_turns_it_off(self):

self.bedroom_light.boot_up() self.assertEqual(self.bedroom_light.status, 0)

def test_boot_system_clock_zeros_it(self):

self.system_clock.boot_up() self.assertEqual(self.system_clock.status, "00:00")

if __name__ == "__main__":

unittest.main()

Setting the execution function for when the program is run from the command line to unittest.main() tells Python to look for instances of the unittest.TestCase class and then to run the tests in that class. We set up six tests, and the status after running the tests looks something like this:

...... ---------------------------------------------------------------------- Ran 6 tests in 0.001s

OK

Each of these had us implement functions we would expect of each function, but how would the system look if we wanted to implement different profiles? Say person1 and person2 share the house, and they have different preferences, such as time to get up, time to go to bed, and what should happen in the morning or evening. They also have different temperatures that they feel most comfortable with. Since they are friendly people, they have agreed on a middle ground for when both of them are home, but when one or the other is home they would like to have the system settings set to their perfect configuration.

A simple extension of the system we saw up to now would see an implementation like this:

import random import unittest

class Light(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

280
CHAPTER VISITOR PATTERN

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.status != -1

def boot_up(self):

self.status = 0

def update_status(self, person_1_home, person_2_home):

if person_1_home:

if person_2_home:

self.status = 1

else:

self.status = 0

elif person_2_home:

self.status = 1

else:

self.status = 0

class Thermostat(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

temp_range = [x for x in range(-10, 31)] temp_range.append(None)

return random.choice(temp_range)

def is_online(self):

return self.status is not None

def boot_up(self):

pass

def update_status(self, person_1_home, person_2_home):

pass

281
CHAPTER VISITOR PATTERN

class TemperatureRegulator(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice([’heating’, ’cooling’, ’on’, ’off’, ’error’])

def is_online(self):

return self.status != ’error’

def boot_up(self):

self.status = ’on’

def update_status(self, person_1_home, person_2_home):

if person_1_home:

if person_2_home:

self.status = ’on’

else:

self.status = ’heating’

elif person_2_home:

self.status = ’cooling’

else:

self.status = ’off’

class DoorLock(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.status != -1

def boot_up(self):

pass

282
CHAPTER VISITOR PATTERN

def update_status(self, person_1_home, person_2_home):

if person_1_home:

self.status = 0

elif person_2_home:

self.status = 1

else:

self.status = 1

class CoffeeMachine(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,5))

def is_online(self):

return self.status != -1

def boot_up(self):

self.status = 1

def update_status(self, person_1_home, person_2_home):

if person_1_home:

if person_2_home:

self.status = 2

else:

self.status = 3

elif person_2_home:

self.status = 4

else:

self.status = 0

class Clock(object):

def __init__(self, name):

self.name = name self.status = self.get_status()

283
CHAPTER VISITOR PATTERN

def get_status(self):

return "{}:{}".format(random.randrange(24), random.randrange(60))

def is_online(self):

return True

def boot_up(self):

self.status = "00:00"

def update_status(self, person_1_home, person_2_home):

if person_1_home:

if person_2_home:

pass

else:

"00:01"

elif person_2_home:

"20:22"

else:

pass

As an exercise, write tests for these states where person1 and person2 are either there or not. Make sure you cover all the possible options in these tests.

This is an already messy implementation, and you know we are going to do some cleanup. We could implement the system using a state machine, but implementing a state machine for each device for each state of the inhabitancy of the house just seems wrong; it is also a lot of work without a lot of value. There has to be a better way.

To get us there, let s consider what we want to do.

The Visitor Pattern

Once again, we are going to tease apart a complex piece of functionality into more discrete parts, then abstract these parts in such a way that the one does not need to be intimately familiar with the other. This pattern of separation and isolation is one you will see over and over again as you spend time optimizing, extending, and cleaning up existing systems.

284
CHAPTER VISITOR PATTERN

It is necessary to mention Martin Fowler s take on developing micro service based architectures. Fowler posits that one first has to develop the monolith because at the

outset you do not know which elements will coalesce to form good micro services and which can be kept separate. As you work on and grow a system, a single object becomes

a network of objects. When these networks become too complex, you refactor, separate, and clean up the code in ways that would not make any sense at a smaller scale.

This is another high-level instance of the You Ain t Gonna Need It principle (YAGNI), which simply implores the developer to not build functionality or structures that

will not be used. This is much like the young developer who upon creating a new

object immediately proceeds to add Create, Read, Update, and Delete functionality

even though the object can never be updated (or whatever business rule applies). Immediately abstracting any function or idea into some sort of meta class will leave

a lot of dead code in your system, code that needs to be maintained, reasoned about, debugged, and tested, but never actually used. New team members will need to slog through this code only to realize after the whole system is analyzed that it was a waste

of time and effort. If this type of code proliferates the system, the push for a complete rewrite will become stronger, and more time will be wasted. If dead code is again implemented, the new developers will head straight into the same problem. In short, do

not write code you are not going to use.

A good rule of thumb is to wait until you have to do the same thing (or very similar thing) the third time before you abstract it. The flip side of this rule is that as soon as you come across the same requirement or problem in your project the third time, you should

not proceed without abstracting the solution, so that whenever you next come across

this situation, you will have a solution ready to go. You have now found a balance.

With this in mind, imagine that you have needed to alter objects twice to allow some function to be performed over the entire collection of objects, and now you have a third algorithm you want to implement specific to the structure. Clearly, you want to abstract

the algorithms being implemented in terms of the data structure. Ideally, you want to be able to dynamically add new algorithms and have them be executed relative to the same data structure without any alterations needing to be made to the classes that make up

the elements of said data structure.

285
CHAPTER VISITOR PATTERN

Look at the generic implementation of the visitor pattern and get a feel for the code. We will dig into the details after this code snippet.

import abc

class Visitable(object):

def accept(self, visitor):

visitor.visit(self)

class CompositeVisitable(Visitable): def __init__(self, iterable): self.iterable = iterable

def accept(self, visitor):

for element in self.iterable: element.accept(visitor)

visitor.visit(self)

class AbstractVisitor(object):

__metaclass__ = abc.ABCMeta

@abc.abstractmethod

def visit(self, element):

raise NotImplementedError("A visitor needs to define a visit method")

class ConcreteVisitable(Visitable):

def __init__(self):

pass

class ConcreteVisitor(AbstractVisitor):

def visit(self, element):

pass

We can now return to the example of our friends who are sharing a house. Let s abstract the setting up of the system for each of them using the visitor pattern. We will do this by creating a visitor for each of the three potential configurations of people inside the house, and then we check who is home before visiting each device and setting it accordingly.

286
CHAPTER VISITOR PATTERN

import abc import random import unittest

class Visitable(object):

def accept(self, visitor):

visitor.visit(self)

class CompositeVisitable(Visitable): def __init__(self, iterable): self.iterable = iterable

def accept(self, visitor):

for element in self.iterable: element.accept(visitor)

visitor.visit(self)

class AbstractVisitor(object):

__metaclass__ = abc.ABCMeta

@abc.abstractmethod

def visit(self, element):

raise NotImplementedError("A visitor need to define a visit method")

class Light(Visitable):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,2))

def is_online(self):

return self.status != -1

def boot_up(self):

self.status = 0

287
CHAPTER VISITOR PATTERN

class LightStatusUpdateVisitor(AbstractVisitor):

def __init__(self, person_1_home, person_2_home):

self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

if self.person_1_home:

if self.person_2_home:

element.status = 1 else:

element.status = 0 elif self.person_2_home:

element.status = 1 else:

element.status = 0

class Thermostat(Visitable):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

temp_range = [x for x in range(-10, 31)] temp_range.append(None)

return random.choice(temp_range)

def is_online(self):

return self.status is not None

def boot_up(self):

pass

class ThermostatStatusUpdateVisitor(AbstractVisitor): def __init__(self, person_1_home, person_2_home):

self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

pass

288
CHAPTER VISITOR PATTERN

class TemperatureRegulator(Visitable): def __init__(self, name):

self.name = name

self.status = self.get_status()

def get_status(self):

return random.choice([’heating’, ’cooling’, ’on’, ’off’, ’error’])

def is_online(self):

return self.status != ’error’

def boot_up(self):

self.status = ’on’

class TemperatureRegulatorStatusUpdateVisitor(AbstractVisitor): def __init__(self, person_1_home, person_2_home): self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

if self.person_1_home:

if self.person_2_home:

element.status = ’on’ else:

element.status = ’heating’ elif self.person_2_home:

element.status = ’cooling’ else:

element.status = ’off’

class DoorLock(Visitable):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,2))

289
CHAPTER VISITOR PATTERN

def is_online(self):

return self.status != -1

def boot_up(self):

pass

class DoorLockStatusUpdateVisitor(AbstractVisitor): def __init__(self, person_1_home, person_2_home):

self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

if self.person_1_home:

element.status = 0 elif self.person_2_home:

element.status = 1 else:

element.status = 1

class CoffeeMachine(Visitable):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return random.choice(range(-1,5))

def is_online(self):

return self.status != -1

def boot_up(self):

self.status = 1

class CoffeeMachineStatusUpdateVisitor(AbstractVisitor): def __init__(self, person_1_home, person_2_home): self.person_1_home = person_1_home self.person_2_home = person_2_home

290
CHAPTER VISITOR PATTERN

def visit(self, element):

if self.person_1_home:

if self.person_2_home:

element.status = 2 else:

element.status = 3 elif self.person_2_home:

element.status = 4 else:

element.status = 0

class Clock(Visitable):

def __init__(self, name):

self.name = name self.status = self.get_status()

def get_status(self):

return "{}:{}".format(random.randrange(24), random.randrange(60))

def is_online(self):

return True

def boot_up(self):

self.status = "00:00"

class ClockStatusUpdateVisitor(AbstractVisitor):

def __init__(self, person_1_home, person_2_home):

self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

if self.person_1_home:

if self.person_2_home:

pass

else:

291
CHAPTER VISITOR PATTERN

element.status = "00:01" elif self.person_2_home:

element.status = "20:22" else:

pass

class CompositeVisitor(AbstractVisitor):

def __init__(self, person_1_home, person_2_home):

self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

try:

c = eval("{}StatusUpdateVisitor".format (element.__class__.__name__))

except:

print("Visitor for {} not found".format (element.__class__.__name__))

else:

visitor = c(self.person_1_home, self.person_2_home) visitor.visit(element)

class MyHomeSystem(CompositeVisitable):

pass

class MyHomeSystemStatusUpdateVisitor(AbstractVisitor): def __init__(self, person_1_home, person_2_home): self.person_1_home = person_1_home self.person_2_home = person_2_home

def visit(self, element):

pass

class HomeAutomationBootTests(unittest.TestCase):

def setUp(self):

self.my_home_system = MyHomeSystem([

Thermostat("General Thermostat"), TemperatureRegulator("Thermal Regulator"),

292
CHAPTER VISITOR PATTERN

DoorLock("Front Door Lock"), CoffeeMachine("Coffee Machine"), Light("Bedroom Light"), Clock("System Clock"),

])

def test_person_1_not_home_person_2_not_home(self):

expected_state = map(

str,

[

self.my_home_system.iterable[0].status,

’off’,

1,

0,

0,

self.my_home_system.iterable[5].status

]

)

self.visitor = CompositeVisitor(False, False) self.my_home_system.accept(self.visitor) retrieved_state = sorted([str(x.status) for x in

self.my_home_system.iterable]) self.assertEqual(retrieved_state, sorted(expected_state))

def test_person_1_home_person_2_not(self):

expected_state = map(

str,

[

self.my_home_system.iterable[0].status, ’heating’,

0,

3,

0,

"00:01"

]

)

293
CHAPTER VISITOR PATTERN

self.visitor = CompositeVisitor(True, False) self.my_home_system.accept(self.visitor) retrieved_state = sorted([str(x.status) for x in

self.my_home_system.iterable]) self.assertEqual(retrieved_state, sorted(expected_state))

def test_person_1_not_home_person_2_home(self):

expected_state = map(

str,

[

self.my_home_system.iterable[0].status, ’cooling’,

1,

4,

1,

"20:22"

]

)

self.visitor = CompositeVisitor(False, True) self.my_home_system.accept(self.visitor) retrieved_state = sorted([str(x.status) for x in

self.my_home_system.iterable]) self.assertEqual(retrieved_state, sorted(expected_state))

def test_person_1_home_person_2_home(self):

expected_state = map(

str,

[

self.my_home_system.iterable[0].status, ’on’,

0,

2,

1,

self.my_home_system.iterable[5].status ]

)

294
CHAPTER 19 VISITOR PATTERN

self.visitor = CompositeVisitor(True, True) self.my_home_system.accept(self.visitor) retrieved_state = sorted([str(x.status) for x in

self.my_home_system.iterable]) self.assertEqual(retrieved_state, sorted(expected_state))

if __name__ == "__main__":

unittest.main()

I have just a couple of notes on the preceding code. First, if you are taking input from a user, never, ever use the eval function, as Python blindly executes whatever is in the string passed to eval. Second, we are choosing to balance the general nature of eval by using the name of the class to be visited, which is not the best way in Python, as now you rely on naming conventions and magic, which is not explicit. Still, you could implement the pattern.

If you were thinking a state machine might help the situation, you are not wrong. This brings us to something that would seem obvious once you read it, but might not be so obvious from the get-go, and that is the fact that you can use more than one design pattern in a single implementation. In this case, it should be clear that the state of the house resembles a state machine. We have four states:

Person1 is not home, Person2 is not home Person1 is not home, Person2 is home

Person1 is home, Person2 is not home

Person1 is home, Person2 is home

Whenever a person either leaves or arrives at home the state of the system changes. So, implementing a state machine for the visitor seems like a good idea.

Try implementing the state pattern on the previous code snippet as an exercise.

When you separate the data structures in the system from the algorithms that operate on them, you gain an added benefit. You can adhere to the open-closed principle much more easily. This principle sees you write code where your objects are open to extension (using inheritance) but closed to alteration (by setting or changing values in an object or altering the methods on an object). Like the DRY and YAGNI principles, the open-closed principle serves as a guiding light as you plan and develop a system. Violations of these principles also serve as an early-warning system that you are writing unmaintainable code that will come back and hurt you down the line.

295
CHAPTER VISITOR PATTERN

Parting Shots

Sometimes you just need to write code, so type out the example line by line to get a feel for the idea expressed. Whenever I find descriptions of an algorithm or idea that I do not quite understand from the way it is described, I reach for this tool. There is something different that happens when you get down and write out the code rather than just

reading it. Often, nuances that were not clear before get exposed when working your way through the code.

With design patterns that you find complicated or high-level algorithms, I suggest typing out the generic version, then typing out some specific implementation, and finally altering some details to fit your own example beyond those in the text you are looking at. Finally, implement the pattern or algorithm from scratch for a specific instance.

This type of work is usually not the fun part of programming, but it is the deliberate practice that you need to do in order to become a better programmer.

There is a whole world of ideas to explore once you get past the simple elements

of syntax and the more general outline of writing idiomatic Python. That world is

where you move from being able to write words in the language, or being able to string sentences together, to expressing ideas in code. In the long run, that is what you want

to get better at the process of expressing your thoughts and reasoning in Python, our language of choice for this book. As with all forms of expression, you will get better the more you do it, but only if you challenge yourself.

You can even use this process to explore new libraries where the documentation

is lacking or opaque. You could begin by exploring what tests are included in the repository, and, failing to make enough headway through that, you could type your way through some of the key methods and classes in the library. Not only will you get a better idea of what is going on and how the library should be used, but you might also pick up a couple of interesting ideas that you could explore further.

In the end, as with all ideas, try them on and see where they work and where they let you down. Keep what fits your brain and discard what trips you up, as long as you only discard an idea after you have gained clarity, and not while you still find it hard.

296
CHAPTER VISITOR PATTERN

Exercises

Use the process from the Parting Shots section and code your way

to a deeper understanding of the visitor pattern, if you have not been doing so throughout the book.

Find an interesting Python package in the standard library and code

through that, seeing how these developers do things differently from you.

Write the tests for these states where person1 and person2 are

either there or not. Make sure you cover all the possible options in these tests.

Extend the code in each instance to accommodate friends coming

over for a social gathering.

ink about the other design patterns in this book; are there other

ones that would make the code better? If so, implement the solution using this design pattern.

In order to crystallize the reason why the visitor pattern makes

adhering to the open-closed principle easier, you are going to add another device to our system a streaming media entertainment center. is device comes with Python bindings, but we do not have access to the inner workings of the device for some reason. Add this object to our network and define a dummy interface to mimic the interface provided. en, add custom settings for each of the states in the home.

Add the state pattern to the final piece of code in this chapter to allow you to handle the four scenarios we discussed. How do you need

to think differently about the code we wrote in the chapter? en, decide if implementing the state pattern, in this case, makes the solution better or worse. ink specifically about maintenance and

the principles we talked about thus far.

297

'Practical Python Design Patterns' 카테고리의 다른 글

PUBLISH–SUBSCRIBE PATTERN  (0) 2023.03.29
Model-View-Controller Pattern  (0) 2023.03.29
Template Method Pattern  (0) 2023.03.29
Strategy Pattern  (0) 2023.03.29
State Pattern  (0) 2023.03.29

댓글