Tuesday, 28 January 2020

Design Jukebox

Classic Jukebox

Classic Jukebok OOD

jb.jpg

Requirements

Simulate a classic jukebox, where user can insert coin and play songs using by choosing the Albhabet and Number for that song from display board. Check this demo : https://www.youtube.com/watch?v=hcE7UBznqTE Machine administrator should be able to add and remove songs and also should be able to know popularity of songs played.

Main ideas here :

  • KISS : Start with very simple structure
  • Decompose what a classic jukebox means
  • Comeup with classes which represent various components
  • Analyse how various components work together
  • Compose them back and simulate runing it via a client program

Actors and use cases

  • Customer
    • Puts a coin and gets some credit
    • Chooses to play a song (a record disk) from selection
    • Can enqueue more songs till there are credits
  • Admin
    • Adds/removes songs
    • Sets up which song is played for a selection
    • Can check the popularity of songs

Classes and their responsibilities

  • RecordPlayer
    • play(record)
  • RecordSelector
    • handles user selection input and gives out corresponsing record to record player to play
    • get_record(user_selection)
  • Playlist
    • enqueue()
    • dequeue()
  • Jukebox
    • makes sure all components work together
    • takes input from user, passes to record selector, gets the record, gives to record player
    • when song ends removes the cd and if there is another song in playlist plays it or goes in standby mode
    • takes coin
    • takes song selection
    • checks popularity, adds record, removes record, prints selections
  • JukeboxCLIDriver
    • Simulate Jukebox on CLI

jukebox.png

In [46]:
from collections import deque
import time
import enum

class Record(object):
    def __init__(self, song_name, artist_name):
        self.song_name = song_name
        self.artist_name = artist_name
    def __repr__(self):
        return '[ Song name : %s, Singer : %s ]'%(self.song_name, self.artist_name)

class RecordPlayer(object):
    def set_observer(self, observer):
        self.observer = observer
    
    def play(self, record):
        print('Playing "%s" , sung by "%s"\n' % (record.song_name, record.artist_name))
        time.sleep(5)
        print('Finished Playing %s.\n' % record.song_name)
        self.observer.notify_record_finish()
        
class RecordSelector(object):
    def __init__(self, input_record_map):
        self.input_record_map = input_record_map
    
    def get_record(self, selection):
        print('Selecting record for selection : %s' % selection)
        if selection not in self.input_record_map:
            return None
        return self.input_record_map[selection]
    
class Playlist(object):
    def __init__(self):
        self.records = deque([])
        
    def enqueue(self, record):
        self.records.append(record)
        
    def deque(self):
        return self.records.popleft()
    
    def size(self):
        return len(self.records)

class JukeboxState(enum.Enum):
    START = 0
    AWAIT_COIN = 1
    AWAIT_SELECTION = 2
    PLAYING_SONG = 3
    
# TODO - make Jukebox implement an observer interface
class Jukebox(object):
    def __init__(self, playlist, record_selector, record_player, admin_key):
        self.playlist = playlist
        self.record_selector = record_selector
        
        record_player.observer = self
        self.record_player = record_player
        self.admin_key = admin_key
        self.state = JukeboxState.AWAIT_COIN
        self.songs_allowed = 0
        self.earning = 0
        
        self.popularity_counter = {}
    
    def __get_coin_value(self, coin):
        # find out the value of the coin
        return coin
    
    def __get_allowed_songs(self, coin):
        # find out how many songs can be allowed with these many coing
        # for simplicity lets assume that it maps one to one
        return coin
    
    def put_coin(self, coin):
        self.songs_allowed = self.__get_allowed_songs(coin)
        self.earning = self.earning + self.__get_coin_value(coin)
        self.state = JukeboxState.AWAIT_SELECTION
        
    def input_selection(self, selection):
        # here we assume that user can give a selection while one song is
        # already playing
        record = self.record_selector.get_record(selection)
        if self.state == JukeboxState.AWAIT_SELECTION:
            self.songs_allowed = self.songs_allowed - 1
            self.record_player.play(record)
            self._increment_popularity(record)
            self.state == JukeboxState.PLAYING_SONG
        elif self.state == JukeboxState.PLAYING_SONG:
            self.songs_allowed = self.songs_allowed - 1
            self.playlist.enqueue(record)
        else:
            raise Exception('Cant make a selection without having putting coin')
    
    def notify_record_finish(self):
        if self.playlist.size() == 0:
            if self.songs_allowed == 0:
                self.state = JukeboxState.AWAIT_COIN
            else:
                self.state = JukeboxState.AWAIT_SELECTION
        else:
            record = self.playlist.dequeue()
            self.record_player.play(record)
            
    def stop(self):
        raise NotImplementedError('cant stop in a single threaded simulation!')
    
    def check_popularity(self):
        popularity_list = [(k, v) for k, v in self.popularity_counter.items()]
        sorted_popularity = sorted(popularity_list, key=lambda x : -x[1])
        return {'top' : sorted_popularity[:5], 'bottom' : sorted_popularity[-5:]}
    
    def _increment_popularity(self, record):
        if record not in self.popularity_counter:
                self.popularity_counter[record] = 1
        else:
                self.popularity_counter[record] = self.popularity_counter[record]+1
In [ ]:
class JukeboxCLIDriver(object):
    def __init__(self):
        playlist = Playlist()
        self.record_map = self._build_record_map()
        record_selector = RecordSelector(self.record_map)
        record_player = RecordPlayer()
        self.admin_key = '007'
        
        self.jukebox = Jukebox(playlist, record_selector, record_player, self.admin_key)
        record_player.set_observer(self.jukebox)
        
    def _build_record_map(self):
        a1 = Record('Ek do teen', 'Alka Yagnik')
        b3 = Record('Zindagi Ki Yahi Reet Hai', 'Kishore Kumar')
        return {'a1' : a1, 'b3' : b3}
    
    def _handle_admin(self):
        while True:
            action = input('Choose action : 1)see popularity, 2)add record, 3)remove record, 4)see selections 5)Exit admin mode')
            if action == '1':
                print(self.jukebox.check_popularity())
            elif action == '2':
                song_name = input('Name of song')
                singer_name = input('Name of singer')
                selector_key = input('Selector key')
                new_record = Record(song_name, singer_name)
                self.record_map[selector_key] = new_record
            elif action == '3':
                selector_key = input('Selector key for song to be removed')
                del self.record_map[selector_key]
            elif action == '4':
                self._print_selections()
            elif action == '5':
                return
    
    
    def _handle_user_coin_action(self):
        money = input('Enter coin (enter to reset): ')
        if money == '':
            self.jukebox.state = JukeboxState.START
            return
        self.jukebox.put_coin(int(money))
        self._handle_user_selection_action()
        
    def _print_selections(self):
        print('--------------------------------------\n')
        for key, rec in self.record_map.items():
            print('[ %s ] [ %s ]' % (key, rec))
        print('--------------------------------------\n')
    
    def _handle_user_selection_action(self):
        print('Here are our records, make a selection : \n')
        self._print_selections()
        selection = input('Select song by key : ')
        self.jukebox.input_selection(selection)
        
    
    def _handle_user(self):
        while True:
            if self.jukebox.state == JukeboxState.AWAIT_COIN:
                self._handle_user_coin_action()
            elif self.jukebox.state == JukeboxState.AWAIT_SELECTION:
                self._handle_user_selection_action()
            elif self.jukebox.state == JukeboxState.START:
                return
    
    def start(self):
        while True:
            maybe_admin_key = input('Enter key if admin otherwise press enter : ')
            if maybe_admin_key == self.admin_key:
                self._handle_admin()
            else:
                self.jukebox.state = JukeboxState.AWAIT_COIN
                self._handle_user()
            
if __name__ == '__main__':
    driver = JukeboxCLIDriver()
    driver.start()
In [ ]:
 

No comments:

Post a Comment