There is one drawback in returning self
. Mutating one of the variables can affect another one. Here is an example
Taken from @TheLazyScripter code but with modified example.
a = Send_message("Hello")
b = a
a = a._from('theLazyscripter')
b = b._from('Kracekumar').to('samba 2')
b()
a.message = 'Hello A'
a.to('samba2')()
b.to('samba 2')()
Send_message("Hello").to('samba2')._from('TheLazyScripter')()
Send_message("Hello").to('samba2')()
The a and b variable points to the same instance. Modifying one's value will affect the other ones. See the second and third line of the output.
The output
Sent 'Hello' from: Kracekumar to samba 2.
Sent 'Hello A' from: Kracekumar to samba2.
Sent 'Hello A' from: Kracekumar to samba 2.
Sent 'Hello' from: TheLazyScripter to samba2.
Sent 'Hello' from: my_address to samba2.
b=a
and modifying the a
's content affects b
's value.
How to remove this side-effect?
Rather than returning self
return a new instance to remove side-effect.
DEFAULT_SENDER = 'my_address'
#Because the sender object is optional I assume you have a default sender
class Send_message(object):
def __init__(self, message):
self.message = message
self.sender = None
self.receiver = None
self.method = None
def _clone(self):
inst = self.__class__(message=self.message)
inst.sender = self.sender
inst.receiver = self.receiver
inst.method = self.method
return inst
def to(self, receiver):
self.receiver = receiver
self.method = self.send()
return self._clone()
def _from(self, sender):
self.sender = sender
self.method = self.send()
return self._clone()
def __call__(self):
if self.method:
return self.method()
return None
def send(self):
if self.receiver:
if not self.sender:
self.sender = DEFAULT_SENDER
return lambda:actual_message_code(self.message, self.sender, self.receiver)
def actual_message_code(message, sender, receiver):
print("Sent '{}' from: {} to {}.".format(message, sender, receiver))
a = Send_message("Hello")
b = a
a = a._from('theLazyscripter')
b = b._from('Kracekumar').to('samba 2')
b()
a.message = 'Hello A'
a.to('samba2')()
b.to('samba 2')()
Send_message("Hello").to('samba2')._from('TheLazyScripter')()
Send_message("Hello").to('samba2')()
The _clone
method creates a new copy of the instance every time. Note: when one of the values is a list or dictionary, the deep copy needs to be called. Here it's string hence not required. But the idea remains the same, copy each attribute before returning.
Output
Sent 'Hello' from: Kracekumar to samba 2.
Sent 'Hello A' from: theLazyscripter to samba2.
Sent 'Hello' from: Kracekumar to samba 2.
Sent 'Hello' from: TheLazyScripter to samba2.
Sent 'Hello' from: my_address to samba2.
The output line number 2
and 3
clearly shows the absence of side-effect in the new code.
I wrote a blog post about Fluent Interface
send_message
would be the factory method to create the builder. – Hampermessage.to("INBOX").from("OUTBOX").send()
is "not as nice" as the previous example, I think you mean there shouldn't be a trailing/build()/send()/whatever()
call, and each method call should automagically figure out if it's the last in the chain, and if yes trigger a.send()
. That sounds undesirable and risky to me, because now you can't do multiple assignments likegeneral_msg = send_message("i like windmills").from("OUTBOX")
andspecific_msg = general_msg.to("Shirley")
.. – Docia.send()
to all. And it forces all your fluent lines to be one line with unlimited line-length, can't split complicated code across lines. – Docia