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.