← Computer Programming II

Problem 1 (Easy): Playlist Manager

A music app has a Playlist class. It keeps the songs, calculates total minutes, AND saves the playlist to a text file, AND prints a “share card” to send to friends. Too many jobs in one class — fix it.

Bad Code

class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []   # list of (title, minutes)

    def add(self, title, minutes):
        self.songs.append((title, minutes))

    def total_minutes(self):
        return sum(m for _, m in self.songs)

    def save_to_file(self, filename):
        with open(filename, "w") as f:
            f.write(f"{self.name}\n")
            for title, minutes in self.songs:
                f.write(f"{title} - {minutes} min\n")

    def print_share_card(self):
        print(f"🎵 {self.name} ({self.total_minutes()} min)")
        for title, _ in self.songs:
            print(f"  • {title}")
  1. Apply the Single Responsibility Principle (SRP).
  2. Identify the three different jobs this class does.
  3. Keep Playlist responsible only for storing songs and calculating total_minutes(). Keep methods add(title, minutes) and total_minutes().
  4. Move file saving into a new class called PlaylistFileWriter with a method save(playlist, filename).
  5. Move the share card printing into a new class called PlaylistSharer with a method share(playlist).

Usage

p = Playlist("Road Trip")
p.add("Highway Star", 6)
p.add("Born to Run", 5)

print(p.total_minutes())

PlaylistFileWriter().save(p, "trip.txt")
PlaylistSharer().share(p)

Expected output

11
🎵 Road Trip (11 min)
  • Highway Star
  • Born to Run

Problem 2 (Easy+): Parking Fee Calculator

A parking lot charges different hourly fees depending on the vehicle type. The current code uses a long if/elif chain. Every time the city adds a new vehicle type (scooter, bus, truck…), a developer has to reopen this class and edit it. Fix it.

Bad Code

class ParkingFeeCalculator:
    def fee(self, vehicle_type, hours):
        if vehicle_type == "bike":
            return 1000 * hours
        elif vehicle_type == "car":
            return 3000 * hours
        elif vehicle_type == "truck":
            return 7000 * hours
        else:
            raise ValueError(f"unknown vehicle: {vehicle_type}")
  1. Apply the Open/Closed Principle (OCP).
  2. Replace the if/elif chain with polymorphism, not a dictionary.
  3. Create an abstract base class Vehicle (use ABC and @abstractmethod) with one abstract method fee(hours).
  4. Create three concrete classes: Bike, Car, Truck. Each implements fee(hours) with its own rate (1000, 3000, 7000 so’m/hour).
  5. Rewrite ParkingFeeCalculator so its method calculate(vehicle: Vehicle, hours) simply delegates to the vehicle’s fee().
  6. Prove the principle works: add a new class Scooter (rate = 500 so’m/hour) without editing ParkingFeeCalculator, Bike, Car, or Truck.

Usage

calc = ParkingFeeCalculator()

print(calc.calculate(Bike(), 2))
print(calc.calculate(Car(), 3))
print(calc.calculate(Truck(), 1))
print(calc.calculate(Scooter(), 4))

Expected output

2000
9000
7000
2000

Problem 3 (Medium): Employee Payroll

A small company built a Payroll class. It calculates the monthly salary depending on the employee type (full-time, part-time, contractor) using an if/elif chain, AND it prints a payslip (a document showing an employee’s salary details), AND it “saves” the record to the database. Two principles are broken at once. Fix both.

Bad Code

class Payroll:
    def __init__(self, name, emp_type, base):
        self.name = name
        self.emp_type = emp_type   # "full_time" | "part_time" | "contractor"
        self.base = base

    def salary(self):
        if self.emp_type == "full_time":
            return self.base + 500_000          # fixed bonus
        elif self.emp_type == "part_time":
            return self.base * 0.5
        elif self.emp_type == "contractor":
            return self.base * 1.2              # higher rate, no benefits
        else:
            raise ValueError(f"unknown type: {self.emp_type}")

    def print_payslip(self):
        print(f"--- Payslip for {self.name} ---")
        print(f"Type: {self.emp_type}")
        print(f"Salary: {self.salary()} so'm")

    def save_to_db(self):
        print(f"INSERT INTO payroll VALUES ('{self.name}', {self.salary()})")
  1. Apply both SRP and OCP.
  2. OCP fix: create an abstract class Employee with an __init__ method that accepts name and base, and an abstract salary() method. Then create three subclasses: FullTime, PartTime, Contractor. Each one implements its own salary() formula.
  3. SRP fix: the Employee classes must ONLY know how to compute salary. They must NOT print or save anything.
  4. Move payslip printing into a separate class PayslipPrinter with method display(employee).
  5. Move the database saving into a separate class PayrollRepository with method save(employee).
  6. Both helper classes must work with any subclass of Employee.
  7. Demonstrate OCP by adding a new class Intern (salary = base * 0.3) without changing any existing class.

Usage

employees = [
    FullTime("Aziz", 4_000_000),
    PartTime("Malika", 3_000_000),
    Contractor("Rustam", 5_000_000),
    Intern("Dilnoza", 2_000_000),
]

printer = PayslipPrinter()
repo = PayrollRepository()

for e in employees:
    printer.display(e)
    repo.save(e)

Expected output

--- Payslip for Aziz ---
Salary: 4500000 so'm
INSERT INTO payroll VALUES ('Aziz', 4500000)
--- Payslip for Malika ---
Salary: 1500000.0 so'm
INSERT INTO payroll VALUES ('Malika', 1500000.0)
--- Payslip for Rustam ---
Salary: 6000000.0 so'm
INSERT INTO payroll VALUES ('Rustam', 6000000.0)
--- Payslip for Dilnoza ---
Salary: 600000.0 so'm
INSERT INTO payroll VALUES ('Dilnoza', 600000.0)

Problem 4 (Medium+): Newsletter Sender

A startup wrote a NewsletterSender that always gets subscribers from the real database and always sends through the real SMTP server. It works in production, but QA cannot test it safely because every test touches external systems. Fix the design so production code can use real services, while tests can use fake services.

Bad Code

class SmtpEmailServer:
    def send(self, to, subject, body):
        print(f"[SMTP → {to}] {subject}: {body}")   # imagine real network call


class SubscriberDB:
    def all_subscribers(self):
        print("[DB] SELECT * FROM subscribers ...")
        return ["aziz@example.com", "malika@example.com"]


class NewsletterSender:
    def __init__(self):
        self.email = SmtpEmailServer()
        self.db = SubscriberDB()

    def send_newsletter(self, subject, body):
        for addr in self.db.all_subscribers():
            personalized = f"Hello {addr.split('@')[0]}!\n\n{body}"
            self.email.send(addr, subject, personalized)
  • DIP problem: NewsletterSender creates low-level concrete objects (SmtpEmailServer, SubscriberDB) itself.
  • OCP problem: changing the subscriber source or mailer requires editing NewsletterSender instead of plugging in a new implementation.
  1. Create an abstract class SubscriberSource with one method all_subscribers().
  2. Make SubscriberDB implement SubscriberSource.
  3. Create an abstract class Mailer with one method send(to, subject, body).
  4. Make SmtpEmailServer implement Mailer.
  5. Rewrite NewsletterSender.__init__ so it receives subscribers: SubscriberSource and mailer: Mailer from the outside. It must not create SubscriberDB() or SmtpEmailServer() itself.
  6. Move the greeting logic into a Personalizer class with method for_address(address, body). NewsletterSender may create a default Personalizer, but it should also allow a custom one to be passed in.
  7. Prove the design is testable by adding FakeSubscribers and FakeMailer. FakeSubscribers should return one hard-coded address. FakeMailer should print a fake send message and also append that message to a list.

Usage

# production wiring
sender = NewsletterSender(
    subscribers=SubscriberDB(),
    mailer=SmtpEmailServer(),
)
sender.send_newsletter("Weekly news", "Lots of updates this week.")

# test wiring — no network, no DB
mailer = FakeMailer()
NewsletterSender(FakeSubscribers(), mailer).send_newsletter("Hi", "body")
print(mailer.sent)

Expected output

[DB] SELECT * FROM subscribers ...
[SMTP → aziz@example.com] Weekly news: Hello aziz!

Lots of updates this week.
[SMTP → malika@example.com] Weekly news: Hello malika!

Lots of updates this week.
[FAKE → test@x.com] Hi: Hello test!

body
['[FAKE → test@x.com] Hi: Hello test!\n\nbody']

📁 Tutorial Project: PantryPilot