Created
January 30, 2014 10:32
-
-
Save mlashcorp/8706002 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
###################################################################### | |
## | |
## Feature Broker | |
## | |
###################################################################### | |
class FeatureBroker: | |
def __init__(self, allowReplace=False): | |
self.providers = {} | |
self.allowReplace = allowReplace | |
def Provide(self, feature, provider, *args, **kwargs): | |
if not self.allowReplace: | |
assert not self.providers.has_key(feature), "Duplicate feature: %r" % feature | |
if callable(provider): | |
def call(): return provider(*args, **kwargs) | |
else: | |
def call(): return provider | |
self.providers[feature] = call | |
def __getitem__(self, feature): | |
try: | |
provider = self.providers[feature] | |
except KeyError: | |
raise KeyError, "Unknown feature named %r" % feature | |
return provider() | |
features = FeatureBroker() | |
###################################################################### | |
## | |
## Representation of Required Features and Feature Assertions | |
## | |
###################################################################### | |
# | |
# Some basic assertions to test the suitability of injected features | |
# | |
def NoAssertion(obj): return True | |
def IsInstanceOf(*classes): | |
def test(obj): return isinstance(obj, classes) | |
return test | |
def HasAttributes(*attributes): | |
def test(obj): | |
for each in attributes: | |
if not hasattr(obj, each): return False | |
return True | |
return test | |
def HasMethods(*methods): | |
def test(obj): | |
for each in methods: | |
try: | |
attr = getattr(obj, each) | |
except AttributeError: | |
return False | |
if not callable(attr): return False | |
return True | |
return test | |
# | |
# An attribute descriptor to "declare" required features | |
# | |
class RequiredFeature(object): | |
def __init__(self, feature, assertion=NoAssertion): | |
self.feature = feature | |
self.assertion = assertion | |
def __get__(self, obj, T): | |
return self.result # <-- will request the feature upon first call | |
def __getattr__(self, name): | |
assert name == 'result', "Unexpected attribute request other then 'result'" | |
self.result = self.Request() | |
return self.result | |
def Request(self): | |
obj = features[self.feature] | |
assert self.assertion(obj), \ | |
"The value %r of %r does not match the specified criteria" \ | |
% (obj, self.feature) | |
return obj | |
class Component(object): | |
"Symbolic base class for components" | |
###################################################################### | |
## | |
## DEMO | |
## | |
###################################################################### | |
# --------------------------------------------------------------------------------- | |
# Some python module defines a Bar component and states the dependencies | |
# We will assume that | |
# - Console denotes an object with a method WriteLine(string) | |
# - AppTitle denotes a string that represents the current application name | |
# - CurrentUser denotes a string that represents the current user name | |
# | |
class Bar(Component): | |
con = RequiredFeature('Console', HasMethods('WriteLine')) | |
title = RequiredFeature('AppTitle', IsInstanceOf(str)) | |
user = RequiredFeature('CurrentUser', IsInstanceOf(str)) | |
def __init__(self): | |
self.X = 0 | |
def PrintYourself(self): | |
self.con.WriteLine('-- Bar instance --') | |
self.con.WriteLine('Title: %s' % self.title) | |
self.con.WriteLine('User: %s' % self.user) | |
self.con.WriteLine('X: %d' % self.X) | |
# --------------------------------------------------------------------------------- | |
# Some other python module defines a basic Console component | |
# | |
class SimpleConsole(Component): | |
def WriteLine(self, s): | |
print s | |
# --------------------------------------------------------------------------------- | |
# Yet another python module defines a better Console component | |
# | |
class BetterConsole(Component): | |
def __init__(self, prefix=''): | |
self.prefix = prefix | |
def WriteLine(self, s): | |
lines = s.split('\n') | |
for line in lines: | |
if line: | |
print self.prefix, line | |
else: | |
# --------------------------------------------------------------------------------- | |
# Some third python module knows how to discover the current user's name | |
# | |
def GetCurrentUser(): | |
return os.getenv('USERNAME') or 'Some User' # USERNAME is platform-specific | |
# --------------------------------------------------------------------------------- | |
# Finally, the main python script specifies the application name, | |
# decides which components/values to use for what feature, | |
# and creates an instance of Bar to work with | |
# | |
if __name__ == '__main__': | |
print '\n*** IoC Demo ***' | |
features.Provide('AppTitle', 'Inversion of Control ...\n\n... The Python Way') | |
features.Provide('CurrentUser', GetCurrentUser) | |
features.Provide('Console', BetterConsole, prefix='-->') # <-- transient lifestyle | |
##features.Provide('Console', BetterConsole(prefix='-->')) # <-- singleton lifestyle | |
bar = Bar() | |
bar.PrintYourself() | |
# | |
# Evidently, none of the used components needed to know about each other | |
# => Loose coupling goal achieved | |
# --------------------------------------------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment