Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 17e43f9

Browse files
Finish first draft of flyweight
1 parent 893fce8 commit 17e43f9

File tree

1 file changed

+96
-14
lines changed

1 file changed

+96
-14
lines changed

gang-of-four/flyweight/index.rst

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,17 @@ Implementing Flyweights
150150
=======================
151151

152152
The simplest flyweights are allocated ahead of time.
153-
simple system for assigning letter grades
153+
A system for assigning letter grades
154154
might use flyweights for the grades themselves:
155155

156156
.. testcode::
157157

158-
class Grade(object):
159-
def __init__(self, minimum, maximum, name):
160-
self.value = value
161-
162-
_grades = [letter + suffix for letter in 'ABCDF'
163-
for suffix in ('+', '', '-')]
158+
_grades = [letter + suffix
159+
for letter in 'ABCD'
160+
for suffix in ('+', '', '-')] + ['F']
164161

165162
def compute_grade(percent):
166-
percent = max(50, min(99, percent))
163+
percent = max(59, min(99, percent))
167164
return _grades[(99 - percent) * 3 // 10]
168165

169166
print(compute_grade(55))
@@ -179,9 +176,8 @@ might use flyweights for the grades themselves:
179176
Factories that need to build a flyweight population dynamically
180177
are more complicated:
181178
they’ll need a dynamic data structure
182-
in which to enroll the flyweights
183-
so they can find them again.
184-
A dictionary is the typical choice:
179+
in which to enroll the flyweights and find them again later.
180+
A dictionary is a typical choice:
185181

186182
.. testcode::
187183

@@ -203,8 +199,94 @@ A dictionary is the typical choice:
203199
False
204200
True
205201

206-
should you keep them all?
202+
One danger of dynamically allocated flyweights
203+
is the possibility of eventually exhausting memory,
204+
if the number of possible values is very large
205+
and callers might request a large number of unique values
206+
over a program’s runtime.
207+
In such cases you might consider using a |WeakValueDictionary|
208+
from the ``weakref`` module.
209+
210+
.. |WeakValueDictionary| replace:: ``WeakValueDictionary``
211+
.. _WeakValueDictionary: https://docs.python.org/3/library/weakref.html#weakref.WeakValueDictionary
212+
213+
Weak references wouldn’t work in the simple example given above,
214+
because ``my_intern`` uses each interned string
215+
not only as a value but also as the corresponding key.
216+
But it should work fine in the more common case
217+
where the indexes are simple values
218+
and the keys more complicated object instances.
219+
220+
The Gang of Four define the Flyweight Pattern as using a factory function,
221+
but Python provides another possibility:
222+
a class can implement the pattern right in its constructor,
223+
just like ``bool()`` and ``int()``.
224+
Rewriting the above example as a class —
225+
and, for the sake of example, allocating objects on-demand
226+
instead of building them ahead of time —
227+
would produce something like:
228+
229+
.. testcode::
230+
231+
class Grade(object):
232+
_instances = {}
207233

208-
weakref.WeakValueDictionary
234+
def __new__(cls, percent):
235+
percent = max(50, min(99, percent))
236+
letter = 'FDCBA'[(percent - 50) // 10]
237+
self = cls._instances.get(letter)
238+
if self is None:
239+
self = cls._instances[letter] = object.__new__(Grade)
240+
self.letter = letter
241+
return self
242+
243+
def __repr__(self):
244+
return 'Grade {!r}'.format(self.letter)
245+
246+
247+
print(Grade(55), Grade(85), Grade(95), Grade(100))
248+
print(len(Grade._instances))
249+
print(Grade(95) is Grade(100))
250+
print(len(Grade._instances))
251+
252+
.. testoutput::
209253

210-
either with class __new__
254+
Grade 'F' Grade 'B' Grade 'A' Grade 'A'
255+
3
256+
True
257+
3
258+
259+
Once a ``Grade`` object for A has been created,
260+
all further requests for it receive the same object;
261+
the instances dictionary does not grow any further.
262+
263+
Note that we don’t define a ``__init__()`` method
264+
in a class like this
265+
whose ``__new__()`` might return an existing object.
266+
That’s because Python always calls for initialization
267+
on the object received back from ``__new__()``
268+
(as long as the object is an instance of the class itself),
269+
which would be useful the first time we returned a new object
270+
but redundant on all of the subsequent occasions
271+
when we were simply returning it from the ``_instances`` cache.
272+
So instead we simply do the work of initialization manually
273+
right in the middle of ``__new__()``::
274+
275+
self.letter = letter
276+
277+
.. TODO mention here “for the same reason as the Singleton” once it’s written
278+
279+
Having illustrated this possibility,
280+
I recommend against it
281+
because it produces code whose behavior does not match its spelling.
282+
When a Python programmer sees ``Grade(95)``,
283+
they are going to think “new object instance”
284+
along with all of the consequences,
285+
unless they are in on the secret that ``__new__()`` has been overridden —
286+
and even in that case, they might at some point forget
287+
that the ``Grades`` class is special.
288+
289+
Whereas a factory ``get_grade_for_percent()``
290+
will be less likely to trigger assumptions
291+
like “this call always builds a new object”
292+
and in any case is simpler both to implement and debug.

0 commit comments

Comments
 (0)