-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How can we make Communicators reliable and portable #49
Comments
Thank you, this was an interesting read. Some thoughts:
This is an interesting idea, but note that it explicitly doesn't comply with PEP 8.
If I understand correctly, you would like to have something like this:
One thing I see here is that ColoramaCommunicator() is no longer solely a 'visualising' class: it also controls itself (see
If I get it right, what you're trying to say is that you should, for example, be able to use |
Good catches, some replies below:
You're right indeed, I wasn't sure this is possible in Python. We should look into another way of realizing the crash locally criterion above (i.e. import only when needed). I think this criterion is crucial and should somehow be accomplished. I originally picked up the idea of importing on init from this link, where we can maybe look a bit further. This link confirms indeed it can be very ugly. I looked into it a bit further now. The idea/problem is not unknown to pyhton programmers ;-) It is sometimes called lazy import. Check out some interesting links : 1 2 3 [1] is definitely most interesting for us, it also quotes PEP you mentioned. Moreover the top answer states:
Long story short: we should look into a clean way of doing this and then not having the imports at the top of the file is no big deal I think...
Good point indeed, but the rationale here would be that a
No that's not really what I'm trying to say here. It is indeed a bit fuzzy, but in fact what I want to express here is straightforward: Communicators shouldn't 'rely' in any way on external code that should make sure the correct platform-dependent instance is instantiated. Communicators should always fail gracefully. That is to say, never 'trust' that they are correctly instantiated. More specific example: an About the |
I see communicator getting stuffed up with Such thins are commonly called bad smells, indicating the need for refactoring the code. Apart from the more drastic changes sketched above and here, I propose the following:
Communicators should be thought of as a kind of Strategy pattern or Template method pattern. This would justify option ii: further subclass communicators where needed. I think option ii is a good one, but we maybe wont want the |
I got it! This is a nice example of a Factory pattern The key idea is to encapsulate the login/logout dependent creation of Communicators in another object: the factory. Checkout the wikipedia links to get the overall idea if you're not familiar with it...
def aanstuurderObvArgumenten(argumenten):
...
############## switch on login-type flags ##############
if argumenten.worker == "login":
print "ik wil inloggen"
fac = LoginCommunicatorFactory()
elif argumenten.worker == "logout":
print "ik wil uitloggen"
fac = LogoutCommunicatorFactory()
############## switch on communicator-related flags ##############
if argumenten.communicator == "dialog":
print "ik wil fancy dialogs"
co = fac.createDialogCommunicator()
elif argumenten.communicator == "bubble":
print "ik wil bellen blazen"
co = fac.createBubbleCommunicator()
... Creating of communicators is thus effectively encapsulated in the factory. |
Thank you for looking this up. This should be implemented before releasing 2.0.0. |
So that means that class LoginCommunicatorFactory():
class CreateQuietCommunicator(self):
def __init__(self):
pass
def eventNetloginStart(self):
pass
(...)
class CreatePlaintextCommunicator(self.CreateQuietCommunicator):
def __init__(self):
Fore.RED = ""
(...)
class LogoutCommunicatorFactory():
class CreateQuietCommunicator(LoginCommunicatorFactory.CreateQuietCommunicator):
def some_specific_logout_feature(self):
(...) Right? |
No, not at whole. I see the pseudo code I posted confused you since the method names are wrongly capitalized. Fixed it above The idea of a factory pattern is to encapsulate the creation of the correct object in a complex hierarchy in another object: the factory. The factory provides a method for every communicator and returns the correct communicator depending whether its a login/logout factory. Checkout the wiki description: https://en.wikipedia.org/wiki/Abstract_factory_pattern |
The idea is to remove all In order to create the correct (sub)class communicator, we'll then use a factory: ## Abstract super class (not intended to directly create), encapsulating things common
## to a Login- and LogoutSummaryCommunicator
class SuperSummaryCommunicator(QuietCommunicator):
def eventPingFailure(self):
print "Niet verbonden met het KU Leuven-netwerk."
def eventPingAlreadyOnline(self):
print "U bent al online."
class LoginSummaryCommunicator(SuperSummaryCommunicator):
def eventTegoedenBekend(self, downloadpercentage, uploadpercentage):
print "Download: " + str(downloadpercentage) + "%" + ",",
print "Upload: " + str(uploadpercentage) + "%"
def beeindig_sessie(self, error_code=0):
if error_code == 0:
print "login succesvol."
else:
print "login mislukt."
class LogoutSummaryCommunicator(SuperSummaryCommunicator):
def beeindig_sessie(self, error_code=0):
if error_code == 0:
print "logout succesvol."
else:
print "logout mislukt."
## The abstract factory specifying the interface and maybe returning
## some defaults (or just passing)
class SuperCommunicatorFabriek:
def createSummaryCommunicator():
pass
class LoginCommunicatorFabriek(SuperCommunicatorFabriek):
def createSummaryCommunicator():
LoginSummaryCommunicator()
class LogoutCommunicatorFabriek(SuperCommunicatorFabriek):
def createSummaryCommunicator():
LogoutSummaryCommunicator()
### kotnetcli.py
def aanstuurderObvArgumenten(argumenten):
...
############## switch on login-type flags ##############
if argumenten.worker == "login":
print "ik wil inloggen"
fac = LoginCommunicatorFabriek()
elif argumenten.worker == "logout":
print "ik wil uitloggen"
fac = LogoutCommunicatorFabriek()
############## switch on communicator-related flags ##############
if argumenten.communicator == "summary":
print "ik wil fancy dialogs"
## next line will auto create correct instance :-)
co = fac.createSummaryCommunicator()
... |
the nice thing is that we can then encapsulate all action-specific communication nicely e.g. print "Netlogout openen....... " instead of "Netlogin openen....... " on reply to a |
Thank you for taking the time and effort to provide this piece of code, it's helped me understand what you want to do exactly. I read the Wikipedia page, but I didn't get it. The code example you provided looks very interesting, my hands are itching to code again and get this factory model done. Maybe I can do some work tonight. |
👍 |
I'm working on the code now. I think I didn't really get it. class LoginCommunicatorFabriek(SuperCommunicatorFabriek):
def createSummaryCommunicator():
LoginSummaryCommunicator() Why not just put the contents of |
No the factory is solely responsible for creating the object. It is called a creational design pattern, for outsourcing creation of an object in a complex hierarchy. When putting the implementation there, you (1) lose cohesion (2) it's impossible, since this is a constructor, there are various other methods, you cant just 'paste in' (eg kuleuvenEvent(), ....). A method ain't a class... |
lazy importing as discussed above can easily be achieved in the corresponding factory methods: 980af26 This allows kotnetcli to run without first imporiting all packages, even those not used (eg dialog, notify2, ...) To test: change the |
Jo, how much work is left on this issue? As far as I can tell, we only need to finish the lazy imports, right? |
Yes, proof-of-concept (including lazy imports) works indeed. We need to decide on the definitive communicator API and finish things. |
Alright. Jo, you're a lot better than I am when it comes to software design. Could you go ahead and design the API? |
Ok, I'll try to look at it this weekend... |
As one of the final things to be done here, I think we should move the credentials querying (i.e. the user input) to the Currently, the front-end ( Improved design outline:
Advantages:
|
Communicators, instead of the various front-ends, are responsible for querying username/pwd if requested by Worker. Front-end is solely responsible for creating required creds instantiation, and only Worker/Browser are coupled to Credentials. Credential removal proceeds via a dedicated worker that can be re- used by the various front-end. Also implemented proper guest credentials, and custom Colorama/Dialog/GUI communicator prompting.
Done! |
I consider this one done! Comminicators have been thoroughly refactored in the |
Problems with the Communicator pattern
I think the switch to the communicator pattern has been one of the key moves in the
kotnetcli
project (i.a. discussed in #13). I strongly belief it is the good way to continue on. It nicely decouples core from visualisation concerns, allowing a pluggable visualisation system for everyones needs. It also keeps the code clean, allowing the programmer to focus on his task: the UNIX philosophy - do one thing and do it good.Currently the pattern merely encapsulates all the visualisation calls. Initiating it can still be messy somehow. As we are porting
kotnetcli
on various platforms now however, time has come to enhance the pattern once more.As discussed in various issues already (#48 #43), the current communicator implementations aren't portable / cross platform. This creates a dependency hell and makes keeping everything stable and up-to-date a hassle... :-/
Tackle the problem, not its symptoms
I belief this problem is not due to the communicator pattern an sich though: we shouldn't solve the problem by tackling its symptoms. That is to say: the real problem here is that specific
Communicator
instantiations currently aren't portable. TheCursesCommunicator
orDialogCommunicator
for example happen to be to worst: i.e. the 'symptoms'. The problem itself is that the current code somehow expects a client to use all of the communicators (by importing them all) which then are instantiated according to the command-line flags passed.What we should really fix therefore is the portability of communicators, the core of the problem.
Requirements for a solution
In my opinion, a good portable solution to this problem should have the following characteristics:
dialog
installed, but you don't use theDialogCommunicator
there should absolutely be no problem)cursesCommunicator
andColoramaCommunicator
have things to do for reinitializing the screen...)if (os ==)
checks wandering everywhere in the code. Platform-specific code should be cleanly separated from platform-independent code.Vers une architecture logicielle
I'll quickly sketch a possible outline for a solution here:
exitFailure(msg)
andexitSucces()
: The non-communicator code should always call a function likeco.exitFailure(msg)
when it catches an exception and wants to exit the system. The communicator can then leave its internal state as desired and if needed, communicate an error message to the end-user. See the fail-gracefully criterion above. Symmetrically, we'll need a function likeexitSucces()
that also cleans up internal state and -if needed- communicates success to the end-user. Things like Create an extra print-statement for the end-status of the login #47 are then trivial BTW.import
statements in a cleaner way: only import when actually used. I.e. importdialog
only in DialogCommunicator.init() andcolorama
only in ColoramaCommunicator.init() etc. See the crash locally criteria above. More specifically, a user should be able to runkotnetcli
without first installingcolorama
and/ordialog
etc., be it in 'simple' plaintext mode then.exitFailure()
;Argumentenparser()
per OS, as discussed in issue Mac OS X mode issues #43 can surely be done. This should be merely 'syntactic-sugar-like' however: it shouldn't prevent you from using the communicator in the first place, merely saving you from trying communicators that wont work anyway (but wont crash unpredictibaly).This is the end - the end
Sorry for the looong post, but I think it's good to share some thoughts on this one ;-) I'll try to come up with more specific implementation ideas / class hierarchies and things soon...
Please comment below for impressions / ideas / comments / etc
The text was updated successfully, but these errors were encountered: