Reputation: 45
I'm reading a book about OOP: Elegant Objects by @yegor256. It says: "always work with immutable objects" (the code will be more maintainable).
So, if I have the User class and I want to change the address, should I return a new user (change_address_option2)?. Until this moment I usually do the change_address_option1.
class User:
def __init__(self, name, address):
self.name = name
self.address = address
def change_address_option1(self, new_address):
self.address = new_address
def change_address_option2(self, new_address):
return User(self.name, new_address)
Is this really a good practice?. I didn't see this in other projects:
if __name__== "__main__":
john = User("John", "California")
# Option 1
john.change_address_option1("New York")
# continue working with john
# Option 2
john_in_new_york = john.change_address_option2("New York")
# continue working with john_in_new_york
EDIT: After reading @ShadowRanger's comments, I edited the question to be more specific. Thanks.
Upvotes: 4
Views: 260
Reputation: 31
That depends on the meaning of the user in your application. Probably it's ok for a user to change the address. Real user which usually represents the human who uses the application could move to another address, right?
But despite @ShadowRanger's advice to use mutable objects when they backed by DB, I would recommend not to do it. The better approach is to encapsulate user address change inside the object. I mean something like thing
class User:
def __init__(self, db, name, address):
self.db = db
self.name = name
def change_address_option3(self, new_address):
self.db.exec("update user set address = ? where name = ?", new_address, self.name)
So whatever in some handler or UI command user address needs to be changed it could be done by calling
User user = new User(db, "John")
user.change_address_option3("New York")
Using mutable objects it would be something like this
User user = new User(db, "John")
user.change_address("New York")
db.exec("update users set address = ? where name = ?", user.address, user.name)
So the application logic related to users is kinda becomes spread across the application. Instead of being encapsulated inside the object.
It look like a mutable object because something could be changed. But thanks to encapsulation of the logic its easy understand and to change something. Because in other places of your application you only use User's class methods to work with the user (changing address for example) it's easy to change something, because other application's coe do not rely on the user implementation details (is it backed by a DB or file or whatever).
There is an article about that approach in the book.
Upvotes: 1
Reputation: 664
Ok, here is what I think. If you need to change user address then you probably need to get access to old addresses, or you need to make empty address for user. This suggests that address - is not simple string, it must be an object, and user must have a collection of addresses.
class User:
def __init__(self, name: str):
self.name = name
self.addresses = Addresses()
def addresses(self) -> Addresses:
return self.addresses
class Addresses:
def clean(self) -> None:
...
def add(self, address: Address) -> None:
...
class Address:
def __init__(self, address: str):
self.address = address
# changing address:
user = User('nikialeksey')
user.addresses().clean()
user.addresses().add(Address('new address'))
Upvotes: 1
Reputation: 155604
The book you're reading seems to be trying to encourage a functional programming style. While it's true that functional programming generally eschews mutable objects, I wouldn't consider this a strategy that is commonly used or endorsed in Python in situations like yours.
In particular, this strategy is only relatively cheap in functional languages (Python supports functional programming styles, but it's a multiparadigm language that doesn't require functional coding), where the language itself can often recognize a pattern like this and implement it as mutation in cases where it doesn't change observed behavior, to minimize the overhead of doing so. Python provides no support for this, so it's going to be fairly costly to do it.
For something like a model backed by a DB table, just use mutable objects. There are use cases for immutable objects, but at least in idiomatic Python, they're usually limited to cases where changes won't be made at all, or if changes are made, they're producing wholly unrelated objects. In this case, it's always going to be the same "John", and producing new objects instead of mutating them in place runs a significant risk of data getting out of sync, with references to the old object stored in some places, and the new object in others, with no obvious way to determine which is newer. Having a single mutable, gold standard for the current state of a unique user is worth the headaches of using mutable objects.
Upvotes: 1