A base for abstract factory in Python
Project description
Abstract Factory Broker
Introduction
Abstract Factory Broker (afb
) is a library that facilitates abstract factory management. It introduces a mechanism for transforming configuration files into Python objects through a network of abstract factories, allowing flexible specification of execution behavior.
Installation
This library supports Python 3.3+.
$ pip install afb
Mechanism
This package consists of two classes:
Manufacturer
:- Collection of factories of the same class.
Broker
:- Collection of
Manufacturer
s.
- Collection of
Manufacturer
A Manufacturer
is a collection of factories of a class. It is responsible for delegating the object creation requests to the specified factories. The process go as follows:
- Retrieves the factory, along with other information, according to the given key.
- Prepares the arguments required by the factory, according to its signature.
- Calls the factory and returns the result.
Each request is done by a call to method make
with parameters:
method
: The key of the factory.params
: Adict
of keyword argument values.
It is not uncommon that a factory depends on objects that are not directly representable in text, such as string or numbers. In this case, the request for the required object could be nested in the current request. The Manufacturer
would first prepare the arguments by passing the sub-requests to the Manufacturer
of the required classes through the network established by Broker
(see below). After that, the factory is called to return the desired result.
Broker
A Broker
is a collection of Manufacturer
s. It defines a network of Manufacturer
s where they are able to pass object creation sub-requests to each other. It consists of a Class-to-Manufacturer mapping to pass the requests to their responsible Manufacturer
to process.
It also accepts object creation requests through its make
method, where an additional cls
parameter is required to determine the right Manufacturer
to use.
Let's take a look at an example. Suppose there are two classes, A
and B
. A
has a factory fa
that depends on a B
object along with a float
:
def fa(b, x):
"""Creates an `A` object.
Args:
b: An instance of `B` object.
x: A float.
"""
...
return A(...)
And B
has a factory fb
that depends on a string:
def fb(s):
"""Creates a `B` object.
Args:
s: String.
"""
...
return B(...)
An instance of A
can be created by first creating a B
object, and passes it to fa
with a float
.
# Create a `B` object
s = "some string"
b = fb(s)
# Create an `A` object
x = 3.14
a = fa(b, x)
Suppose the above factories are registered in their respective Manufacturer
s (mfr_a
, mfr_b
) in a network defined by a Broker
(bkr
), a
can be created by the following call:
params = {
"fa": {
"b": {
"fb": {"s": "some string"}
},
"x": 3.14}}
a = bkr.make(cls=A, params=params)
This allows us to export the object specification to an external configuration file provided by at execution time (or even dynamically generated), giving the program a notable configurability.
Base Usage
It is best to illustrate how it works with an example. Suppose we have the following classes:
# Class definitions
class A(object):
...
class B(object):
...
class C(object):
...
Each class has a factory, say:
# Factories
def fa(x: int, y: float) -> A:
return A(...)
def fb(z: str, a: A) -> B:
return B(...)
def fc(x: float, b: B) -> C:
return C(...)
Note: Here we are using the type hints available from Python 3.5 for easy illustration. In the actual code they are NOT required.
Now we have three classes, with a factory for each of them. The first thing we need to do is to create a Manufacturer
for each class, and register their factories.
# Manufacturer for class B
# ---------------------------
# 1.1 Create Manufacturer
mfr_b = Manufacturer(B)
# 1.2 Register `fb` into `mfr_b`
# 1.2.1 Provide descriptions for `fa`.
descriptions = {
"short": "Creates B from z, a."
"long":
"""Creates B from z, a, where ..."""
}
# 1.2.2 Provide signature, with descriptions
sig_b = {
"z": {
"type": str,
"description": "Input mode of ..."
},
"a": {
"type": A,
"description": "Logic block A which ..."
}
}
# 1.2.3 Register factory.
# We use the key "fact_b" to refer to the factory `fb`
mfr_a.register("fact_b", fb, sig_b, descriptions=descriptions)
Finally, register all the Manufacturer
s to a Broker
:
# Create a Broker
broker = Broker()
# Register Manufacturers
broker.register(mfr_a)
broker.register(mfr_b)
broker.register(mfr_c)
# Or one can make a single function call:
# `broker.register_all([mfr_a, mfr_b, mfr_c])
From this point, broker
, as well as the registered Manufacturer
s, can be used to make objects. For example, an instance of A
can be created in the following ways:
# 1. Create by `Manufacturer.make` call
params = {
"x": -2,
"y": 3.14
}
a = mfr_a.make("fact_a", params)
# 2. Create by `Broker.make` call
spec = {
"fact_a": params
}
a = broker.make(A, spec)
The Broker.make
method accepts two arguments:
- Target class
- Object specification
The target class (cls
) is for the Broker
to retrieve the right Manufacturer
, while the object speicifcation is a singleton dict
that specifies:
- The factory to use as the key
- The parameters to pass to the factory as the value
The object specification can be nested. Consider making an object C
, which uses fb
for the required B
in fc
, and fa
for the required A
in fb
:
spec = {
"fact_c": {
"x": 2.7183
"b": {
"fact_b": {
"z": "Some mode",
"a": {
"fact_a": {
"x": -2,
"y": 3.1416
}
}
}
}
}
}
c = broker.make(C, spec)
The execution goes as follows:
Broker
retrievesManufacturer
ofC
,mfr_c
, and callsmfr_c.make
mfr_c
retrieves factory keyed by"fact_c"
,fc
, and prepares the argumentsx
andb
for it.- As
fc
requiresx
as afloat
, and the given value is itself afloat
, it proceeds to the next parameter. fc
requiresb
as aB
, and the given value is a singletondict
,spec_b
, it is interpreted as an object specification.mfr_c
then makes aBroker.make
call for object instantiation.Broker
retrievesManufacturer
ofB
,mfr_b
, and callsmfr_b.make
withspec_b
.mfr_b
retrieves factory keyed by"fact_b"
,fb
, and prepares the argumentsz
anda
for it.- As
fb
requiresz
as astr
, and the given value is itself astr
, it proceeds to the next parameter. fb
requiresa
as anA
, and the given value is a singletondict
,spec_a
, it is interpreted as an object specification.mfr_b
then makes aBroker.make
call for object instantiation.Broker
retrievesManufacturer
ofA
,mfr_a
, and callsmfr_a.make
withspec_a
.mfr_a
retrieves factory keyed by"fact_a"
,fa
, and prepares the argumentsx
andy
for it.- As
fa
requiresx
as anint
, and the given value is itself anint
, it proceeds to the next parameter. fa
requiresy
as afloat
, and the given value is itself afloat
, parameter preparation is done forfa
.mfr_a
callsfa
with the preparedx
andy
, and returns the resultobj_a
fromfa
to the caller.Broker
returns theobj_a
to the caller.mfr_b
completes its parameter preparation. It callsfb
with the preparedz
anda
, and returns the resultobj_b
to the caller.Broker
returns theobj_b
to the caller.mfr_c
completes its parameter preparation. It callsfc
with the preparedx
andb
, and returns the resultobj_c
to the caller.Broker
returns theobj_c
to the caller.
Note: The above is only for illustration purpose. In real applications, the specifications are usually loaded from configuration files, and the classes and factories are defined in dedicated packages and modules with a registry for management.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.