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

Skip to content

Commit 7d57e10

Browse files
Updated Abstract Factory from “draft” branch
1 parent b5c5426 commit 7d57e10

File tree

1 file changed

+82
-79
lines changed

1 file changed

+82
-79
lines changed

gang-of-four/abstract-factory/index.rst

Lines changed: 82 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,30 @@ Consider a JSON string like this one:
2323

2424
text = '{"total": 9.61, "items": ["Americano", "Omelet"]}'
2525

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.
3334
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.
3838

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:
4041

4142
* In the course of performing its duties,
4243
a routine is going to need to create a number of objects
4344
on behalf of the caller.
4445

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.
4848

49-
* So instead of hard-coding those default classes
49+
* So instead of hard-coding that default class
5050
and making customization impossible,
5151
the routine wants to let the caller specify which classes
5252
it will instantiate.
@@ -55,23 +55,23 @@ First, we’ll look at the Pythonic approach to this problem.
5555
Then we’ll start placing a series of restrictions on our Python code
5656
to more closely model legacy object oriented languages,
5757
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.
5959

6060
The Pythonic approach: callable factories
6161
=========================================
6262

6363
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)``
6666
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,
6868
returned as a return value,
6969
and can even be stored in a data structure
7070
like a list or dictionary.
7171

7272
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.
7575

7676
A beginning Python programmer might expect
7777
that each time they need to supply a factory,
@@ -96,19 +96,19 @@ The number returned is a decimal instead of a float.
9696

9797
(Note my choice of a verb ``build_decimal()`` as the name of this function,
9898
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.)
102101

103-
While the above code will certainly work,
102+
While the above function will certainly work,
104103
there is an elision we can perform
105104
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,
110107
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:
112112

113113
.. testcode::
114114

@@ -122,15 +122,15 @@ There is one implementation detail that deserves mention.
122122
If you study the ``json`` module
123123
you will discover that ``load()`` is simply a wrapper
124124
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,
130128
defaulting to Python’s built-in ``float`` type if no override was specified::
131129

132130
self.parse_float = parse_float or float
133131

132+
It can then invoke it later as ``self.parse_float(…)``.
133+
134134
If you are interested in variations on this pattern —
135135
where a class uses its instance attributes
136136
to remember how it’s supposed to create a specific kind of object —
@@ -150,45 +150,42 @@ Restriction: outlaw passing callables
150150
What if Python didn’t let you pass callables as parameters?
151151

152152
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:
167158

168159
.. testcode::
169160

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+
170169
class DecimalFactory(object):
171170
@staticmethod
172171
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.
192189

193190
.. testcode::
194191

@@ -205,15 +202,17 @@ of parsing a comma-separated list of numbers:
205202

206203
[Decimal('464.80'), Decimal('993.68')]
207204

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.
211210

212211
Restriction: outlaw passing classes
213212
===================================
214213

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
217216
and passed as parameters.
218217

219218
This restriction is going to prevent us
@@ -233,7 +232,7 @@ and pass the resulting object:
233232

234233
[Decimal('464.80'), Decimal('993.68')]
235234

236-
Note again the difference
235+
Note the difference
237236
between this pattern
238237
and the :doc:`Factory Method </gang-of-four/factory-method/index>`.
239238
Here, we are neither asked nor required to subclass ``Loader`` itself
@@ -242,12 +241,12 @@ Instead, object creation is entirely parametrized
242241
by the separate factory object we choose to pass in.
243242

244243
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.
246245
Scroll back up and read the method’s code.
247246
Where does it accept as an argument, or use in its result,
248247
the object ``self`` on which it is being invoked?
249248
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.
251250
As Jack Diederich propounded in his famous talk
252251
`Stop Writing Classes <https://www.youtube.com/watch?v=o9pEzgHorH0>`_,
253252
a method that never uses ``self``
@@ -297,6 +296,9 @@ And here is an updated loader that uses this factory:
297296

298297
[Decimal('1.23'), Decimal('4.56')]
299298

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+
300302
Second, consider the behavior of languages that force you
301303
to declare ahead of time the type of each method parameter.
302304
You would overly restrict your future choices
@@ -332,7 +334,8 @@ though, the operations that take place at runtime
332334
are exactly the same as they were before.
333335
The factory’s methods are called with various arguments,
334336
which direct them to create various kinds of object,
335-
which the methods construct and return
337+
which they construct and return
336338
without the caller needing to know the details.
337339

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

Comments
 (0)