Applying Strategy Pattern
Having chosen Strategy over Mixin, we’ll get down to doing it for real.
I’d like to have some way to test what we do here. As things stand, I don’t see a useful approach. Maybe one will come to mind.
I think the thing will be to build a new content class, fully configurable, to replace all the old ones. I’ll do that in a test file even if we don’t have any decent tests, and maybe we can test the maim operations.
We’ll see what happens. To begin with:
I’ve made four or five attempts at this and none of them feel right. I’ll spare you the pastes that didn’t work
I’ll begin again, with a ContentItem that, when we’re done, will have all the methods needed, draw, placed. interact_with_player. Then I’ll make it work.
class TestContent:
def test_initial_placed(self):
dungeon = None
content = ContentItem()
result = content.placed(dungeon)
assert result == 'ignored'
class ContentItem:
def __init__(self):
self.placement_strategy = IgnorePlacedStrategy()
def placed(self, dungeon):
return self.placement_strategy.placed(dungeon)
class IgnorePlacedStrategy():
def placed(self, *args, **kwargs):
return 'ignored'
That’s green. Commit: creating pluggable ContentItem class.
Now some behavior. The only placed behavior that we actually use is this one:
class SecretKey:
def placed(self, dungeon):
self.visible = False
def callback(event):
dungeon.announce('A key has appeared!')
self.visible = True
dungeon.subscribe_once('button_pressed', callback)
Let’s try to make that work.
class SecretPlacedStrategy():
def __init__(self):
self.visible = False
def placed(self, dungeon):
self.visible = True
def callback(event):
dungeon.announce('A key has appeared!')
self.visible = False
dungeon.subscribe_once('button_pressed', callback)
That fails miserably but as expected, for lack of a dungeon object. We need a FakeDungeon, thus:
No, it’s more tricky than that. This would be easier to refactor in than to test. Push on.
After a bit more effort, it’s working as intended:
class TestContent:
def test_initial_placed(self):
dungeon = None
content = ContentItem()
result = content.placed(dungeon)
assert result == 'ignored'
def test_secret_key_placed(self):
results = dict(announced='No Message', subscribed=False)
dungeon = FakeDungeon(results)
content = ContentItem()
content.placement_strategy = SecretPlacedStrategy()
content.placed(dungeon)
assert results['subscribed'] is True
assert results['announced'] == 'A key has appeared!'
I needed to provide a receptacle for making sure things got called, decided on a dictionary in this FakeDungeon:
class FakeDungeon:
def __init__(self, results):
self.results = results
def announce(self, string):
self.results['announced'] = string
def subscribe_once(self, string, callback):
self.results['subscribed'] = True
callback(string)
It just calls the callback immediately upon subscription. Now with this code, it all works:
class IgnorePlacedStrategy():
def placed(self, *args, **kwargs):
return 'ignored'
class SecretPlacedStrategy():
def __init__(self):
self.visible = False
def placed(self, dungeon):
self.visible = True
def callback(event):
dungeon.announce('A key has appeared!')
self.visible = False
dungeon.subscribe_once('button_pressed', callback)
class ContentItem:
def __init__(self):
self.placement_strategy = IgnorePlacedStrategy()
def placed(self, dungeon):
return self.placement_strategy.placed(dungeon)
I think in the fullness of time, we’ll be rid of that visible flag. But there may be other situations where a Strategy requires some memory. I guess that’s OK. We’ll have to wait and see how it goes.
So far so good. I had a lot of trouble getting it set up and working, but it was more the setting up of the test than the actual doing. This is a tricky thing to test as there’s all the plugging going on.
I think we’ll probably either make the different strategy slots into properties or provide explicit setters. We’ll see how it goes when we start making constructors. Maybe next time. Maybe time after that.
So far so good. It works, and I think it’s nearly right.
This is probably progress. Committed, still all in the test file, so it’s safe. See you next time!