I have a tool that displays data in some format. For example, it can display a list of dogs in a CSV or JSON formats.
In the code, I had a lot of places where I had to check the format and display the data in that format. The only difference between the code blocks is the calls to the data.
Some of them call get_name() and get_id() while others call get_type() and so on.
I tried to create an abstract class that has the display method and the derived classes have the get_block (for JSON), get_line (for CSV) and so on.
The code looks like:
class DisplayingFormatEnum(Enum):
OPTION_A = 'user-option-a'
OPTION_B = 'user-option-b'
class DisplayingFormat(ABC):
def get_row(data):
pass
def get_block(data):
pass
def get_line(data):
pass
def display_format_table(self):
table = PrettyTable(self.title)
for d in self.data:
table.add_row(self.get_row(d))
print(table)
def print_json(self):
result = []
for d in self.data:
block = self.get_block(d)
result.append(block)
print(result)
def display_format_dsv(self):
for d in self.data:
print(f"{self.get_line(d)}")
def display_format_list(self):
if self.result_format == 'table':
self.display_format_table()
elif self.result_format == 'json':
self.display_format_json()
else:
self.display_format_dsv()
class DisplayingFormatPeople(DisplayingFormat):
def __init__(self, people, result_format, enum_type):
if not isinstance(enum_type, DisplayingFormatEnum):
raise DisplayingFormatError("Entity must be an instance of DisplayingFormatEnum Enum")
if enum_type.value == DisplayingFormatEnum.OPTION_A.value:
self.title = TITLE_OPTION_A['people']
else:
raise DisplayingFormatError(f"DisplayingFormatPeople can only use {DisplayingFormatEnum.OPTION_A.value} as a valid enum_type. Invalid enum_type: {enum_type.value}")
self.result_format = result_format
self.data = people
self.enum_type = enum_type
def get_row(self, person):
return [person.get_name(), person.get_id()]
def get_block(self, person):
block = {}
block[self.title[0]] = person.get_name()
block[self.title[1]] = person.get_id()
return block
def get_line(self, person):
return ';'.join((person.get_name(), person.id()))
class DisplayingFormatCars(DisplayingFormat):
def __init__(self, cars, result_format, enum_type):
if not isinstance(enum_type, DisplayingFormatEnum):
raise DisplayingFormatError("Entity must be an instance of DisplayingFormatEnum Enum")
if enum_type.value == DisplayingFormatEnum.OPTION_A.value:
self.title = TITLE_OPTION_A['cars']
else:
raise DisplayingFormatError(f"DisplayingFormatProjects can only use {DisplayingFormatEnum.OPTION_A.value} as a valid enum_type. Invalid enum_type: {enum_type.value}")
self.result_format = result_format
self.data = cars
self.enum_type = enum_type
def get_row(self, car):
return [car.get_type()]
def get_block(self, car):
block = {}
block[self.title[0]] = car.get_type()
return block
def get_line(self, car):
return car.get_type()
class DisplayingFormatDogs(DisplayingFormat):
def __init__(self, dogs, result_format, enum_type):
if not isinstance(enum_type, DisplayingFormatEnum):
raise DisplayingFormatError("Entity must be an instance of DisplayingFormatEnum Enum")
if enum_type.value == DisplayingFormatEnum.OPTION_A.value:
self.title = TITLE_OPTION_A['dogs']
elif enum_type.value == DisplayingFormatEnum.OPTION_B.value:
self.title = TITLE_OPTION_B['dogs']
else:
error_line = "DisplayingFormatDogs can only use {DisplayingFormatEnum.OPTION_A.value} and {DisplayingFormatEnum.OPTION_B.value} as valid entities."
error_line += "Invalid enum_type: {enum_type.value}"
raise DisplayingFormatError(f"{error_line}")
self.result_format = result_format
self.data = dogs
self.enum_type = enum_type
def get_row(self, dog):
return [dog.get_nickname(), dog.get_type()]
def get_block(self, dog):
block = {}
block[self.title[0]] = dog.get_nickname()
block[self.title[2]] = dog.get_type()
return block
def get_line(self, dog):
return ';'.join((dog.get_nickname(), dog.get_type()))
class DisplayingFormatError(Exception):
pass
I believe that this code is a good use of inheritance but I think it does not fit well with the "composition over inheritance" rule. I'm afraid of unexpexted future changes that will require me to change this code. How should I change my code to follow this rule?