@@ -23,30 +23,30 @@ Consider a JSON string like this one:
23
23
24
24
text = '{"total": 9.61, "items": ["Americano", "Omelet"]}'
25
25
26
- By default, the ``json `` module
27
- will create ``unicode `` objects for strings like ``"Americano" ``,
28
- a ``float `` for ``9.61 ``,
29
- a ``list `` for the sequence of items,
30
- and a ``dict `` for the main object’s keys and values,
31
-
32
- But some users aren’t content with these defaults.
26
+ By default, the ``json `` module’s ``loads() `` function
27
+ will create ``unicode `` objects
28
+ for the strings ``"Americano" `` and ``"Omelet" ``,
29
+ a ``list `` to hold them,
30
+ a Python ``float `` for ``9.61 ``,
31
+ and a ``dict `` with ``unicode `` keys for the top-level JSON object.
32
+
33
+ But some users will not be content with these defaults.
33
34
For example, an accountant would probably be unhappy
34
- with the ``json `` module’s
35
- representing an exact amount like “9 dollars 61 cents”
36
- with an approximate floating point number,
37
- and would prefer to use a ``Decimal `` instance instead.
35
+ with the choice of a ``float ``
36
+ to represent an exact amount like “9 dollars 61 cents”
37
+ and would prefer an exact ``Decimal `` instead.
38
38
39
- This is a specific instance of a general problem:
39
+ The need to control what kind of numeric object the ``json `` module creates
40
+ is a specific instance of a general problem:
40
41
41
42
* In the course of performing its duties,
42
43
a routine is going to need to create a number of objects
43
44
on behalf of the caller.
44
45
45
- * A reasonable default might exist
46
- for which class to use for each object,
47
- but the default does not cover all possible cases.
46
+ * But the class that the routine would instantiate by default
47
+ does not cover all possible cases.
48
48
49
- * So instead of hard-coding those default classes
49
+ * So instead of hard-coding that default class
50
50
and making customization impossible,
51
51
the routine wants to let the caller specify which classes
52
52
it will instantiate.
@@ -55,23 +55,23 @@ First, we’ll look at the Pythonic approach to this problem.
55
55
Then we’ll start placing a series of restrictions on our Python code
56
56
to more closely model legacy object oriented languages,
57
57
until the Abstract Factory pattern emerges
58
- as an elegant solution within the bounds set by those limitations.
58
+ as the best solution within those limitations.
59
59
60
60
The Pythonic approach: callable factories
61
61
=========================================
62
62
63
63
In Python, a “callable” —
64
- any routine ``f `` that can be invoked using the syntax `` f(a, b, c) ``
65
- to run its code with the arguments listed in the parentheses —
64
+ any object ``f `` that executes code
65
+ when invoked using the syntax `` f(a, b, c) `` —
66
66
is a first-class object.
67
- This means that a callable can be passed as a parameter,
67
+ To be “first class” means that a callable can be passed as a parameter,
68
68
returned as a return value,
69
69
and can even be stored in a data structure
70
70
like a list or dictionary.
71
71
72
72
First-class callables offer a powerful mechanism
73
- for implementing object “factories” —
74
- a fancy term for routines that build and return new objects.
73
+ for implementing object “factories”,
74
+ a fancy term for “ routines that build and return new objects.”
75
75
76
76
A beginning Python programmer might expect
77
77
that each time they need to supply a factory,
@@ -96,19 +96,19 @@ The number returned is a decimal instead of a float.
96
96
97
97
(Note my choice of a verb ``build_decimal() `` as the name of this function,
98
98
instead of a noun like ``decimal_factory() `` —
99
- I almost always find functions more readable
100
- if their name tells me what they *do *
101
- instead of what they think they *are *.)
99
+ I find a function name easier to read when it tells me what the function *does *
100
+ instead of telling me what *kind * of function it is.)
102
101
103
- While the above code will certainly work,
102
+ While the above function will certainly work,
104
103
there is an elision we can perform
105
104
thanks to the fact that Python types are themselves callables.
106
- The fact that ``Decimal `` is itself a callable taking a string argument
107
- and returning a decimal object instance
108
- means that, unless we need to edit the string first —
109
- for example, to removing a leading currency symbol —
105
+ Because ``Decimal `` is a callable taking a string argument
106
+ and returning a decimal object instance,
110
107
we can dispense with our own factory
111
- and simply pass the ``Decimal `` type directly to the JSON loader!
108
+ and pass the ``Decimal `` type directly to the JSON loader!
109
+ Unless we need to edit the string first,
110
+ like by removing a leading currency symbol,
111
+ ``Decimal `` can completely replace our little factory:
112
112
113
113
.. testcode ::
114
114
@@ -122,15 +122,15 @@ There is one implementation detail that deserves mention.
122
122
If you study the ``json `` module
123
123
you will discover that ``load() `` is simply a wrapper
124
124
around the ``JSONDecoder `` class.
125
- How is the decoder instance itself customized
126
- when we provide this alternative factory?
127
- The answer is that its initialization method
128
- stores its ``parse_float `` argument
129
- on the class as an instance attribute,
125
+ How does the decoder instance itself support an alternative factory?
126
+ Its initialization method stores the ``parse_float `` argument
127
+ as an instance attribute,
130
128
defaulting to Python’s built-in ``float `` type if no override was specified::
131
129
132
130
self.parse_float = parse_float or float
133
131
132
+ It can then invoke it later as ``self.parse_float(…) ``.
133
+
134
134
If you are interested in variations on this pattern —
135
135
where a class uses its instance attributes
136
136
to remember how it’s supposed to create a specific kind of object —
@@ -150,45 +150,42 @@ Restriction: outlaw passing callables
150
150
What if Python didn’t let you pass callables as parameters?
151
151
152
152
That restriction would remove an entire dimension from Python’s flexibility.
153
- Python normally lets your programs work with both nouns —
154
- objects that are interesting because of the attributes and methods
155
- they offer —
156
- and verbs, callables that perform an action.
157
-
158
- If we prohibit our Python code from passing callables,
159
- then we eliminate verbs from the arguments we can pass.
160
- Instead we will always pass nouns,
161
- and any verb we want to accomplish
162
- will have to dangle off of a noun as a method.
163
- Instead of a simple function,
164
- we’ll need to indent our code an extra level
165
- and wrap it up inside a class.
166
- One approach would be:
153
+ Instead of supporting both “nouns” and “verbs” as arguments —
154
+ both class instances and callable functions —
155
+ some legacy languages only support passing class instances.
156
+ Under that restriction,
157
+ every simple factory would need to pivot from a function to a method:
167
158
168
159
.. testcode ::
169
160
161
+ # In Python: a factory function.
162
+
163
+ def build_decimal(string):
164
+ return Decimal(string.lstrip('$'))
165
+
166
+ # In some legacy languages: the code must
167
+ # move inside a class method instead.
168
+
170
169
class DecimalFactory(object):
171
170
@staticmethod
172
171
def build(string):
173
- return Decimal(string)
174
-
175
- This restriction that some languages impose
176
- against passing simple callables
177
- is why words like “factory” had to be imported
178
- into the practice of programming in the first place.
179
- When a verb would have been fine,
180
- but a language requires each verb to be attached to a useless noun,
181
- the programmer’s imagination has to search for a vague abstraction
182
- to fill the gap.
183
- And a “factory” in the real world is,
184
- indeed, a place where objects are manufactured.
185
-
186
- The code using our factory
187
- must now switch to invoking the factory’s method.
188
- Instead of implementing our own JSON parser as our example,
189
- let’s keep our attention on the pattern
190
- by switching to the simpler task
191
- of parsing a comma-separated list of numbers:
172
+ return Decimal(string.lstrip('$'))
173
+
174
+ In traditional Object Oriented programming,
175
+ the word “factory” is the name of this kind of class —
176
+ a class that offers a method that builds an object.
177
+ In naming the equivalent Python function ``build_decimal() ``,
178
+ therefore,
179
+ I’m not only indulging in my own preference
180
+ for giving functions verb-names rather than noun-names,
181
+ but being as precise as possible in naming:
182
+ the “factory” is not the callable,
183
+ but the class that holds it.
184
+
185
+ Instead of continuing our earlier example of JSON parsing,
186
+ let’s switch to a simpler task that can fit in a couple of lines of code:
187
+ parsing a comma-separated list of numbers.
188
+ Here’s how the parser would invoke the builder method on our factory class.
192
189
193
190
.. testcode ::
194
191
@@ -205,15 +202,17 @@ of parsing a comma-separated list of numbers:
205
202
206
203
[Decimal('464.80'), Decimal('993.68')]
207
204
208
- After the labor of moving all of our code into methods,
209
- we are still able to use our factory
210
- to take control of how the parsing logic builds objects.
205
+ Note that,
206
+ thanks to the fact that Python classes offer static and class methods
207
+ that can be invoked without an instance,
208
+ we have not yet been reduced to needing to instantiate the factory class —
209
+ we are simply passing the Python class in as a first-class object.
211
210
212
211
Restriction: outlaw passing classes
213
212
===================================
214
213
215
- Next, let’s also pretend that a Python class cannot be passed as a value;
216
- only object instances can be assigned to names
214
+ Next, let’s also pretend that a Python class cannot be passed as a value,
215
+ but that only object instances can be assigned to names
217
216
and passed as parameters.
218
217
219
218
This restriction is going to prevent us
@@ -233,7 +232,7 @@ and pass the resulting object:
233
232
234
233
[Decimal('464.80'), Decimal('993.68')]
235
234
236
- Note again the difference
235
+ Note the difference
237
236
between this pattern
238
237
and the :doc: `Factory Method </gang-of-four/factory-method/index >`.
239
238
Here, we are neither asked nor required to subclass ``Loader `` itself
@@ -242,12 +241,12 @@ Instead, object creation is entirely parametrized
242
241
by the separate factory object we choose to pass in.
243
242
244
243
Note also the clear warning sign in the factory’s own code
245
- that ``build() `` should not really be the method of an object.
244
+ that ``build() `` should, in Python, not really be the method of an object.
246
245
Scroll back up and read the method’s code.
247
246
Where does it accept as an argument, or use in its result,
248
247
the object ``self `` on which it is being invoked?
249
248
It makes no use of it at all!
250
- The method never even mentions ``self `` in its code.
249
+ The method never mentions ``self `` in its code.
251
250
As Jack Diederich propounded in his famous talk
252
251
`Stop Writing Classes <https://www.youtube.com/watch?v=o9pEzgHorH0 >`_,
253
252
a method that never uses ``self ``
@@ -297,6 +296,9 @@ And here is an updated loader that uses this factory:
297
296
298
297
[Decimal('1.23'), Decimal('4.56')]
299
298
299
+ Every choice it needs to make about object instantiation
300
+ is deferred to the factory instead of taking place in the parser itself.
301
+
300
302
Second, consider the behavior of languages that force you
301
303
to declare ahead of time the type of each method parameter.
302
304
You would overly restrict your future choices
@@ -332,7 +334,8 @@ though, the operations that take place at runtime
332
334
are exactly the same as they were before.
333
335
The factory’s methods are called with various arguments,
334
336
which direct them to create various kinds of object,
335
- which the methods construct and return
337
+ which they construct and return
336
338
without the caller needing to know the details.
337
339
338
- It’s like something you might do in Python, but done more complicated.
340
+ It’s like something you might do in Python, but made overly complicated.
341
+ So avoid the Abstract Factory and use callables as factories instead.
0 commit comments