I have started the implementation of a tool in Python that gathers several system metrics (eg cpu utilisation, cpu saturation, memory errors etc.) and presents them to the end-user. This tool should ideally support as many platforms as possible (Linux, FreeBSD, Windows etc.).
I have completed the implementation of this tool for a Linux system for a few metrics I consider important and I have just started to implement the same metrics for a FreeBSD system. This tool must be designed in a way that allows support for more system metrics and for more platforms in the future. Moreover, a web-interface will soon be added and will receive data from my tool.
For the above reasons, I have decided to implement the tool in Python (convenient to read data from different sources on many systems and ,well, I am somewhat more familiar with it :)) and I am following a class structure for each system metric (inheritance is important as some systems share features so there is no need to rewrite code). Moreover, I have decided that I have a valid use case for using a factory method .
Here is an example class diagram for CPU Metrics (simplified for the sake of the question):
CpuMetrics (Abstract Base Class)
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
LinuxCpuMetrics FreeBSDCpuMetrics WindowsCPUMetrics (per OS)
/ \
/ \
/ \
/ \
/ \
/ \
/ \
ArchLinuxCpuMetrics DebianLinuxCpuMetrics (sometimes important per Distro or Version)
In the Abstract Base Class called CpuMetrics there are some abstract methods defined that should be implemented by inheriting classes and a factory method called get_impl(). I have done some research on when I should use a Factory Method (for example, answers such as this ) and I believe it is valid to use one in my case.
For example, I want a client (eg my web interface) to call my tool to get CPU Utilisation metrics like this:
cpu_metrics = CpuMetrics.get_impl() # factory method as an alternative constructor
cpu_metrics.get_cpu_util() # It is completely transparent for the client which get_cpu_util() is returned.
Following the above analyzed design, it is very important for my factory method to be informed about which system are we on now ("is this Linux, Windows? Which implementation should I bring now?"). Therefore, I have to heavily rely on functions such as platform.system()
or its alternatives. So what my factory method does is (roughly again):
def get_impl():
"""Factory method returning the appropriate implementation depending on system."""
try:
system = platform.system() # This returns: { Linux, Windows, FreeBSD, ... }
system_class = getattr("cpu", system + "CpuMetrics" )
except AttributeError, attr_err:
print ("Error: No class named " + system + "CpuMetrics in module cpu. ")
raise attr_err
return system_class()
I feel very uncomfortable with this for two reasons:
1) I force a future programmer (or even myself) to follow a naming convention for his class. For example, if someone decides to extend my system, say, for Solaris he absolutely has to name his class SolarisCpuMetrics
.
2) If in a future version of Python the values of platform.system()
(or an other alternative I will choose to use) are modifiled, then I have to change my naming convention and modify my factory method a lot.
So my question : is there a workaround for my concern ? Will my code become unreadable or my concern is not valid? If you believe there is one workaround, how much do I need to modify / refactor my code and change my design?
I don not have experience in designing projects from scratch, so I could use any advice. Also, I have some more experience in Java. I try to think in a as much pythonic way as possible when writing Python, but sometimes fail to do a proper seperation between the two. Your constructive criticism is very desirable.
Use a class decorator to enumerate classes. And override the allocator .
sysmap = {}
class metric:
def __init__(self, system):
self.system = system
def __call__(self, cls):
sysmap[self.system] = cls
return cls
class CpuMetrics:
def __new__(self):
cls = sysmap.get(platform.system)
if not cls:
raise RuntimeError('No metric class found!')
else:
return cls()
...
...
@metric('Linux')
class SomeLinuxMetrics(CpuMetrics):
...
...
metrics = CpuMetrics()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.