Exercises: Interfaces, Abstract Classes, and Design Principles

Part A: Conceptual Questions

A1. Explain in your own words the difference between an "is-a" relationship (modeled by inheritance) and a "can-do" relationship (modeled by interfaces). Give an example of each from a domain other than finance.

A2. Why does Object Pascal not support multiple inheritance for classes? What problems does multiple inheritance cause in languages like C++ that support it?

A3. What is a GUID, and why must every interface have one? What happens if you try to use Supports() on an interface without a GUID?

A4. Explain reference counting in your own words. What are its advantages over manual memory management with Free? Can you think of a situation where reference counting could cause a problem? (Hint: think about two objects that reference each other.)

A5. Describe the Interface Segregation Principle. Why is it better to have five small interfaces than one large one?

A6. What is the Dependency Inversion Principle? Explain how interfaces make dependency injection possible.

A7. Compare the Template Method pattern (using an abstract class) with the Strategy pattern (using an interface). When would you choose one over the other?


Part B: Tracing and Prediction

B1. What is the output of the following code? Trace the reference count of the object.

type
  ILogger = interface
    ['{11111111-2222-3333-4444-555555555555}']
    procedure Log(const Msg: String);
  end;

  TConsoleLogger = class(TInterfacedObject, ILogger)
  public
    procedure Log(const Msg: String);
    destructor Destroy; override;
  end;

procedure TConsoleLogger.Log(const Msg: String);
begin
  WriteLn('LOG: ', Msg);
end;

destructor TConsoleLogger.Destroy;
begin
  WriteLn('Logger destroyed');
  inherited;
end;

procedure TestLogger;
var
  Logger: ILogger;
begin
  Logger := TConsoleLogger.Create;
  Logger.Log('First message');
  Logger.Log('Second message');
  WriteLn('About to exit procedure');
end;

begin
  TestLogger;
  WriteLn('Back in main');
end.

B2. Will the following code compile? If not, identify the error.

type
  IShape = interface
    function GetArea: Double;
    function GetPerimeter: Double;
  end;

  TCircle = class(TObject, IShape)
  private
    FRadius: Double;
  public
    function GetArea: Double;
    function GetPerimeter: Double;
  end;

B3. Consider this code. Which SOLID principle does it violate, and how would you fix it?

type
  TExpenseManager = class
    procedure AddExpense(E: TExpense);
    procedure RemoveExpense(E: TExpense);
    procedure SaveToCSV(const FileName: String);
    procedure LoadFromCSV(const FileName: String);
    procedure PrintReport;
    procedure SendEmailReport(const Recipient: String);
    procedure GenerateChart;
  end;

Part C: Short Programming Exercises

C1. IPrintable Interface Define an IPrintable interface with a single method procedure PrintSelf. Create two classes — TStudent (with name and GPA) and TBook (with title and author) — that both implement IPrintable. Write a procedure PrintAll that accepts an array of IPrintable and prints each one.

C2. IComparable Interface Define an IComparable interface with method function CompareTo(const AOther: IComparable): Integer. Implement it on a TTemperature class that holds a value and a scale (Celsius or Fahrenheit). CompareTo should convert both temperatures to Celsius before comparing.

C3. Abstract TShape Class Create an abstract TShape class with abstract methods GetArea and GetPerimeter, and a concrete method Describe that returns a string like "Shape with area 28.27 and perimeter 18.85". Derive TCircle, TRectangle, and TTriangle from it.

C4. Method Resolution Create two interfaces — IEnglishSpeaker and ISpanishSpeaker — each with a method function Greet: String. Create a TBilingualPerson class that implements both, using method resolution clauses so that the English version returns 'Hello!' and the Spanish version returns 'Hola!'.

C5. Dependency Injection Create an IMessageSender interface with procedure SendMessage(const Recipient, Body: String). Implement TEmailSender and TConsoleSender. Create a TNotificationService class whose constructor accepts an IMessageSender. Write a main program that creates a TNotificationService with each sender and demonstrates sending a message through both.


Part D: Design Challenges

D1. Plugin Architecture Design (on paper or in code) a simple plugin system for a text editor. Define an IPlugin interface with methods: function GetName: String, procedure Initialize, procedure Execute, and procedure Shutdown. Create at least three plugin classes: TWordCountPlugin, TSpellCheckPlugin, and TAutoSavePlugin. Write a TPluginManager class that maintains a list of IPlugin references and can initialize, execute, and shut down all plugins.

D2. SOLID Refactoring The following class violates multiple SOLID principles. Identify which principles are violated and refactor the code using interfaces and smaller classes:

type
  TUserAccount = class
    procedure CreateUser(Name, Email, Password: String);
    procedure DeleteUser(UserID: Integer);
    procedure SendWelcomeEmail(Email: String);
    procedure SendPasswordResetEmail(Email: String);
    procedure SaveToDatabase(UserID: Integer);
    procedure LoadFromDatabase(UserID: Integer);
    procedure ValidateEmail(Email: String);
    procedure ValidatePassword(Password: String);
    procedure GenerateUserReport(UserID: Integer);
    procedure ExportUserToCSV(UserID: Integer);
    procedure ExportUserToJSON(UserID: Integer);
  end;

D3. Observer Pattern for PennyWise Implement a working Observer pattern for PennyWise. Create an IExpenseObserver interface and an IExpenseSubject interface. Implement the subject in a TExpenseList class. Create three observer classes: TBudgetTracker (prints a warning when spending exceeds a limit), TExpenseLogger (writes every addition to the console), and TAutoSaver (prints "Saving..." whenever the list changes). Demonstrate adding expenses and seeing all three observers react.