Reputation: 19349
class Book(object):
def __init__(self, title):
self.title = title
def setCategory(self, category):
self.category = category
class RomanceBook(Book):
def __init__(self, title):
super(RomanceBook, self).__init__(title)
self.category = 'romance'
def getTitle(self):
return self.title.upper()
class FictionBook(Book):
def __init__(self, title):
super(FictionBook, self).__init__(title)
self.category = 'fiction'
def getTitle(self):
return self.title.lower().replace(' ', '_')
Both RomanceBook
and FictionBook
classes inherit from the same Book
class. Both classes have the getTitle()
method which returns the book's title. The RomanceBook
returns the title using the letter upper case while the FictionBook
class returns the title using lower case:
romance_book = RomanceBook('Love Story')
print romance_book, romance_book.getTitle()
fiction_book = FictionBook('Star Wars')
print fiction_book.getTitle()
This prints:
<__main__.RomanceBook object at 0x108d37610> LOVE STORY
<__main__.FictionBook object at 0x108d37650> star_wars
Now I go ahead and change the category
attribute of the fiction_book
instance from 'fiction' to romance
.
fiction_book.setCategory('romance')
It would be great if the fiction_book
instance would switch its class from FictionBook
to RomanceBook
at the same time the category
attribute is changed. It would then inherit all the behaviors of the RomanceBook
acting like a "real" romance book. If I would switch its category to "fiction" it would switch its class again and so on.
How could we modify the example code to make it work?
Upvotes: 0
Views: 69
Reputation: 10959
This can be done in different variations but basically it is about delegating methods:
class RomanceBookSupport:
@staticmethod
def getTitle(book):
return book.title.upper()
class FictionBookSupport:
@staticmethod
def getTitle(book):
return book.title.lower().replace(' ', '_')
SUPPORTMAP = {
'romance' : RomanceBookSupport,
'fiction' : FictionBookSupport
}
class Book(object):
def __init__(self, title):
self.title = title
def setCategory(self, category):
self.category = category
def getTitle(self):
return SUPPORTMAP[self.category].getTitle(self)
romance_book = Book('Love Story')
romance_book.setCategory('romance')
print romance_book, romance_book.getTitle()
fiction_book = Book('Star Wars')
fiction_book.setCategory('fiction')
print fiction_book.getTitle()
fiction_book.setCategory('romance')
print fiction_book.getTitle()
Upvotes: 1
Reputation: 3279
There are probably better ways to do what you want than class switching (maybe something like the strategy pattern) but if you really want to do this, you could assign to the instances __class__
:
class Book(object):
def __init__(self, title):
self.title = title
def setCategory(self, category):
self.category = category
self.__class__ = classes[category] # IMPORTANT
class RomanceBook(Book):
def __init__(self, title):
super(RomanceBook, self).__init__(title)
self.category = 'romance'
def getTitle(self):
return self.title.upper()
class FictionBook(Book):
def __init__(self, title):
super(FictionBook, self).__init__(title)
self.category = 'fiction'
def getTitle(self):
return self.title.lower().replace(' ', '_')
classes = {
'romance': RomanceBook, # IMPORTANT
'fiction': FictionBook, # IMPORTANT
}
Or, if you want to automatically add to classes
with a metaclass:
classes = {}
class BookMetaclass(type):
def __new__(cls, name, bases, attrs):
new_class = type.__new__(cls, name, bases, attrs)
if name != "Book":
classes[new_class.category] = new_class
return new_class
class Book(object, metaclass=BookMetaclass):
def __init__(self, title):
self.title = title
def setCategory(self, category):
self.category = category
self.__class__ = classes[category]
...
Example with the strategy pattern:
class Book(object):
title_funcs = {
'romance': lambda title: title.upper(),
'fiction': lambda title: title.lower().replace(' ', '_'),
}
def __init__(self, category, title):
self.category = category
self.title = title
def setCategory(self, category):
self.category = category
def getTitle(self):
return self.title_funcs[self.category](self.title)
Upvotes: 1
Reputation: 3323
Another possibility is to use a mapping library between the two; GitHub
from mapper.object_mapper import ObjectMapper
class Book(object):
def __init__(self, title):
self.title = title
def setCategory(self, category):
self.category = category
class RomanceBook(Book):
def __init__(self, title=None):
super(RomanceBook, self).__init__(title)
self.category = 'romance'
def getTitle(self):
return self.title.upper()
class FictionBook(Book):
def __init__(self, title=None):
super(FictionBook, self).__init__(title)
self.category = 'fiction'
def getTitle(self):
return self.title.lower().replace(' ', '_')
def new_book_type(book: Book, new_type):
mapper = ObjectMapper()
mapper.create_map(type(book), new_type)
return mapper.map(book, new_type)
romance_book = RomanceBook('Love Story')
print (romance_book, romance_book.getTitle() )
fiction_book = FictionBook('Star Wars')
print(fiction_book, fiction_book.getTitle())
romantic_star_wars = new_book_type(fiction_book, RomanceBook)
print(romantic_star_wars, romantic_star_wars.getTitle())
yields
<__main__.RomanceBook object at 0x7fdf6e69ac50> LOVE STORY
<__main__.FictionBook object at 0x7fdf6d1a0a90> star_wars
<__main__.RomanceBook object at 0x7fdf6d1a0b00> STAR WARS
Upvotes: 0
Reputation: 2255
This should be what you want, by extracting the getTitle method to the base class and dynamically decide which strategy should use:
class Book(object):
def __init__(self, title):
self.title = title
def setCategory(self, category):
self.category = category
def getTitle(self):
if self.category == 'romance':
return self.title.upper()
elif self.category == 'fiction':
return self.title.lower().replace(' ', '_')
else:
raise NotImplementedError()
class RomanceBook(Book):
def __init__(self, title):
super(RomanceBook, self).__init__(title)
self.category = 'romance'
class FictionBook(Book):
def __init__(self, title):
super(FictionBook, self).__init__(title)
self.category = 'fiction'
romance_book = RomanceBook('Love Story')
print(romance_book.getTitle())
fiction_book = FictionBook('Star Wars')
print(fiction_book.getTitle())
fiction_book.setCategory('romance')
print(fiction_book.getTitle())
Upvotes: 0