alphanumeric
alphanumeric

Reputation: 19349

How to switch Classes

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

Answers (4)

Michael Butscher
Michael Butscher

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

internet_user
internet_user

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

SteveJ
SteveJ

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

Menglong Li
Menglong Li

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

Related Questions