| name | oop-polymorphism |
| user-invocable | false |
| description | Use when implementing polymorphism and interfaces in object-oriented design. Use when creating flexible, extensible systems with interchangeable components. |
| allowed-tools | ["Bash","Read"] |
OOP Polymorphism
Master polymorphism to create flexible, extensible object-oriented systems. This skill focuses on understanding and applying polymorphic behavior through interfaces, abstract classes, and runtime type substitution.
Understanding Polymorphism
Polymorphism allows objects of different types to be treated uniformly through a common interface. It enables writing code that works with abstractions rather than concrete implementations.
Interface-Based Polymorphism in Java
public interface PaymentMethod {
PaymentResult process(BigDecimal amount);
boolean isValid();
String getDisplayName();
}
public class CreditCard implements PaymentMethod {
private final String cardNumber;
private final String cardholderName;
private final String expiryDate;
private final String cvv;
public CreditCard(String cardNumber, String cardholderName, String expiryDate, String cvv) {
this.cardNumber = cardNumber;
this.cardholderName = cardholderName;
this.expiryDate = expiryDate;
this.cvv = cvv;
}
@Override
public PaymentResult process(BigDecimal amount) {
if (!isValid()) {
return PaymentResult.failed("Invalid credit card");
}
String transactionId = UUID.randomUUID().toString();
System.out.println("Processing $" + amount + " via credit card ending in " +
cardNumber.substring(cardNumber.length() - 4));
return PaymentResult.success(transactionId, amount);
}
@Override
public boolean isValid() {
return cardNumber != null &&
cardNumber.length() == 16 &&
!isExpired(expiryDate);
}
@Override
public String getDisplayName() {
return "Credit Card ending in " + cardNumber.substring(cardNumber.length() - 4);
}
private boolean isExpired(String expiryDate) {
return false;
}
}
public class PayPal implements PaymentMethod {
private final String email;
private final String password;
public PayPal(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public PaymentResult process(BigDecimal amount) {
if (!isValid()) {
return PaymentResult.failed("Invalid PayPal credentials");
}
String transactionId = UUID.randomUUID().toString();
System.out.println("Processing $" + amount + " via PayPal account " + email);
return PaymentResult.success(transactionId, amount);
}
@Override
public boolean isValid() {
return email != null && email.contains("@") && password != null;
}
@Override
public String getDisplayName() {
return "PayPal (" + email + ")";
}
}
public class BankTransfer implements PaymentMethod {
private final String accountNumber;
private final String routingNumber;
private final String accountHolderName;
public BankTransfer(String accountNumber, String routingNumber, String accountHolderName) {
this.accountNumber = accountNumber;
this.routingNumber = routingNumber;
this.accountHolderName = accountHolderName;
}
@Override
public PaymentResult process(BigDecimal amount) {
if (!isValid()) {
return PaymentResult.failed("Invalid bank account");
}
String transactionId = UUID.randomUUID().toString();
System.out.println("Processing $" + amount + " via bank transfer from " + accountHolderName);
return PaymentResult.success(transactionId, amount);
}
@Override
public boolean isValid() {
return accountNumber != null &&
routingNumber != null &&
accountNumber.length() > 0;
}
@Override
public String getDisplayName() {
return "Bank Account (" + accountHolderName + ")";
}
}
public class PaymentProcessor {
private final List<PaymentMethod> paymentMethods;
public PaymentProcessor() {
this.paymentMethods = new ArrayList<>();
}
public void addPaymentMethod(PaymentMethod method) {
paymentMethods.add(method);
}
public PaymentResult processPayment(BigDecimal amount) {
for (PaymentMethod method : paymentMethods) {
if (method.isValid()) {
System.out.println("Attempting payment with " + method.getDisplayName());
PaymentResult result = method.process(amount);
if (result.isSuccess()) {
return result;
}
}
}
return PaymentResult.failed("No valid payment method available");
}
public List<String> getAvailablePaymentMethods() {
return paymentMethods.stream()
.filter(PaymentMethod::isValid)
.map(PaymentMethod::getDisplayName)
.collect(Collectors.toList());
}
}
PaymentProcessor processor = new PaymentProcessor();
processor.addPaymentMethod(new CreditCard("1234567890123456", "John Doe", "12/25", "123"));
processor.addPaymentMethod(new PayPal("john@example.com", "secret"));
processor.addPaymentMethod(new BankTransfer("9876543210", "123456789", "John Doe"));
PaymentResult result = processor.processPayment(new BigDecimal("99.99"));
Protocol-Based Polymorphism in Python
from typing import Protocol, List, Optional
from abc import ABC, abstractmethod
from dataclasses import dataclass
import json
class Serializable(Protocol):
"""Protocol for objects that can be serialized."""
def to_dict(self) -> dict:
"""Convert to dictionary."""
...
def to_json(self) -> str:
"""Convert to JSON string."""
...
class Identifiable(Protocol):
"""Protocol for objects with an ID."""
@property
def id(self) -> str:
"""Get unique identifier."""
...
@dataclass
class User:
"""User implementation with both protocols."""
_id: str
username: str
email: str
age: int
@property
def id() -> :
._
() -> :
{
: ._,
: .username,
: .email,
: .age
}
() -> :
json.dumps(.to_dict())
:
_:
name:
price:
category:
() -> :
._
() -> :
{
: ._,
: .name,
: .price,
: .category
}
() -> :
json.dumps(.to_dict())
:
_:
user_id:
items: []
total:
() -> :
._
() -> :
{
: ._,
: .user_id,
: .items,
: .total
}
() -> :
json.dumps(.to_dict())
() -> :
(filename, ) f:
f.write(obj.to_json())
() -> :
obj.
() -> :
json.dumps([obj.to_dict() obj objects])
user = User(, , , )
product = Product(, , , )
order = Order(, , [], )
save_to_file(user, )
save_to_file(product, )
all_objects = [user, product, order]
batch_json = serialize_batch(all_objects)
Abstract Base Classes in Python
from abc import ABC, abstractmethod
from typing import List, Dict, Any
from datetime import datetime
class DataStore(ABC):
"""Abstract base class for data storage."""
@abstractmethod
def connect(self) -> None:
"""Establish connection to data store."""
pass
@abstractmethod
def disconnect(self) -> None:
"""Close connection to data store."""
pass
@abstractmethod
def save(self, key: str, value: Any) -> bool:
"""Save value with given key."""
pass
@abstractmethod
def load(self, key: str) -> Optional[Any]:
"""Load value for given key."""
pass
@abstractmethod
def delete(self, key: str) -> :
() -> []:
() -> :
.load(key)
() -> :
count =
key, value items.items():
.save(key, value):
count +=
count
():
():
._data: [, ] = {}
._connected =
() -> :
._connected =
()
() -> :
._connected =
()
() -> :
._connected:
RuntimeError()
._data[key] = value
() -> []:
._connected:
RuntimeError()
._data.get(key)
() -> :
._connected:
RuntimeError()
key ._data:
._data[key]
() -> []:
._connected:
RuntimeError()
(._data.keys())
():
():
._directory = directory
._connected =
() -> :
os
os.makedirs(._directory, exist_ok=)
._connected =
()
() -> :
._connected =
()
() -> :
._connected:
RuntimeError()
json
filepath = os.path.join(._directory, )
:
(filepath, ) f:
json.dump(value, f)
Exception e:
()
() -> []:
._connected:
RuntimeError()
json
filepath = os.path.join(._directory, )
:
(filepath, ) f:
json.load(f)
FileNotFoundError:
Exception e:
()
() -> :
._connected:
RuntimeError()
os
filepath = os.path.join(._directory, )
:
os.remove(filepath)
FileNotFoundError:
() -> []:
._connected:
RuntimeError()
os
[
f[:-] f os.listdir(._directory)
f.endswith()
]
:
():
._store = store
():
._store.connect()
():
._store.disconnect()
() -> :
count =
key source_store.list_keys():
value = source_store.load(key)
value ._store.save(key, value):
count +=
count
() -> :
count =
key ._store.list_keys():
value = ._store.load(key)
value target_store.save(key, value):
count +=
count
DataManager(MemoryStore()) manager:
manager._store.save(, {: , : })
DataManager(FileStore()) manager:
manager._store.save(, {: , : })
TypeScript Polymorphism
interface Logger {
log(message: string, level: string): void;
flush(): void;
}
interface Formatter {
format(message: string, level: string): string;
}
class ConsoleLogger implements Logger {
private formatter: Formatter;
constructor(formatter: Formatter) {
this.formatter = formatter;
}
log(message: string, level: string): void {
const formatted = this.formatter.format(message, level);
console.log(formatted);
}
flush(): void {
}
}
class FileLogger implements {
: ;
: [] = [];
: ;
() {
. = filename;
. = formatter;
}
(: , : ): {
formatted = ..(message, level);
..(formatted);
(.. >= ) {
.();
}
}
(): {
(.. === ) ;
content = ..();
.(, content);
. = [];
}
}
{
(: , : ): {
.({
message,
level,
: ().()
});
}
}
{
(: , : ): {
timestamp = ().();
;
}
}
{
: [] = [];
(: ): {
..(logger);
}
(: , : ): {
( logger .) {
logger.(message, level);
}
}
(): {
( logger .) {
logger.();
}
}
}
{
: ;
() {
. = logger;
}
(): {
..(, );
.();
..(, );
..();
}
(): {
..(, );
}
}
jsonFormatter = ();
textFormatter = ();
consoleLogger = (textFormatter);
fileLogger = (, jsonFormatter);
multiLogger = ();
multiLogger.(consoleLogger);
multiLogger.(fileLogger);
app = (multiLogger);
app.();
C# Polymorphism with Interfaces
public interface INotificationService
{
Task SendAsync(string recipient, string subject, string body);
bool IsAvailable();
string GetServiceName();
}
public class EmailNotificationService : INotificationService
{
private readonly string _smtpServer;
private readonly int _port;
private readonly string _username;
private readonly string _password;
public EmailNotificationService(string smtpServer, int port, string username, string password)
{
_smtpServer = smtpServer;
_port = port;
_username = username;
_password = password;
}
public async Task SendAsync(string recipient, string subject, string body)
{
if (!IsAvailable())
throw InvalidOperationException();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Task.Delay();
}
{
!.IsNullOrEmpty(_smtpServer);
}
{
;
}
}
:
{
_apiKey;
_phoneNumber;
{
_apiKey = apiKey;
_phoneNumber = phoneNumber;
}
{
(!IsAvailable())
InvalidOperationException();
Console.WriteLine();
Console.WriteLine();
Task.Delay();
}
{
!.IsNullOrEmpty(_apiKey);
}
{
;
}
}
:
{
_appId;
_apiKey;
{
_appId = appId;
_apiKey = apiKey;
}
{
(!IsAvailable())
InvalidOperationException();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Task.Delay();
}
{
!.IsNullOrEmpty(_appId) && !.IsNullOrEmpty(_apiKey);
}
{
;
}
}
{
List<INotificationService> _services;
{
_services = List<INotificationService>();
}
{
_services.Add(service);
}
{
availableServices = _services.Where(s => s.IsAvailable()).ToList();
(!availableServices.Any())
{
InvalidOperationException();
}
Console.WriteLine();
tasks = availableServices.Select(service =>
NotifyWithServiceAsync(service, recipient, subject, body)
);
Task.WhenAll(tasks);
}
{
{
service.SendAsync(recipient, subject, body);
Console.WriteLine();
}
(Exception ex)
{
Console.WriteLine();
}
}
{
_services
.Where(s => s.IsAvailable())
.Select(s => s.GetServiceName())
.ToList();
}
}
manager = NotificationManager();
manager.RegisterService( EmailNotificationService(, , , ));
manager.RegisterService( SmsNotificationService(, ));
manager.RegisterService( PushNotificationService(, ));
manager.NotifyAsync(, , );
Method Overloading
Overloading in Java
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
public double add(double a, double b) {
return a + b;
}
public int add(int[] numbers) {
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
public double add(int a, double b) {
return a + b;
}
public double add {
a + b;
}
}
{
String {
text.trim();
}
String {
format(text);
uppercase ? trimmed.toUpperCase() : trimmed.toLowerCase();
}
String {
format(text);
trimmed.length() > maxLength
? trimmed.substring(, maxLength) +
: trimmed;
}
String {
format(text, uppercase);
format(formatted, maxLength);
}
}
Overloading in C
public class DocumentProcessor
{
public ProcessResult Process(string content)
{
return new ProcessResult
{
Type = "text",
Length = content.Length,
ProcessedContent = content.Trim()
};
}
public ProcessResult Process(byte[] content)
{
return new ProcessResult
{
Type = "binary",
Length = content.Length,
ProcessedContent = Convert.ToBase64String(content)
};
}
public ProcessResult Process(string content, ProcessOptions options)
{
var result = Process(content);
if (options.RemoveWhitespace)
{
result.ProcessedContent = Regex.Replace(
result.ProcessedContent.ToString(),
@"\s+",
" "
);
}
if (options.MaxLength > 0)
{
var text = result.ProcessedContent.ToString();
result.ProcessedContent = text.Length > options.MaxLength
? text.Substring(0, options.MaxLength)
: text;
}
return result;
}
public async Task<ProcessResult> ()
{
content = File.ReadAllTextAsync(.FullName);
Process(content);
}
{
reader = StreamReader(stream);
content = reader.ReadToEndAsync();
Process(content);
}
}
Operator Overloading
C# Operator Overloading
public struct Vector3D
{
public double X { get; }
public double Y { get; }
public double Z { get; }
public Vector3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public static Vector3D operator +(Vector3D a, Vector3D b)
{
return new Vector3D(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}
public static Vector3D operator -(Vector3D a, Vector3D b)
{
return new Vector3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
}
public static Vector3D operator *(Vector3D v, double scalar)
{
return new Vector3D(v.X * scalar, v.Y * scalar, v.Z * scalar);
}
public static Vector3D operator *(double scalar, Vector3D v)
{
return v * scalar;
}
public static Vector3D operator /(Vector3D v, double scalar)
{
if (scalar == 0)
throw new DivideByZeroException();
Vector3D(v.X / scalar, v.Y / scalar, v.Z / scalar);
}
Vector3D -(Vector3D v)
{
Vector3D(-v.X, -v.Y, -v.Z);
}
==(Vector3D a, Vector3D b)
{
a.X == b.X && a.Y == b.Y && a.Z == b.Z;
}
!=(Vector3D a, Vector3D b)
{
!(a == b);
}
{
obj Vector3D vector && == vector;
}
{
HashCode.Combine(X, Y, Z);
}
{
;
}
{
Math.Sqrt(X * X + Y * Y + Z * Z);
}
{
mag = Magnitude();
mag > ? / mag : ;
}
{
X * other.X + Y * other.Y + Z * other.Z;
}
{
Vector3D(
Y * other.Z - Z * other.Y,
Z * other.X - X * other.Z,
X * other.Y - Y * other.X
);
}
}
v1 = Vector3D(, , );
v2 = Vector3D(, , );
v3 = v1 + v2;
v4 = v1 * ;
v5 = -v1;
Python Magic Methods (Operator Overloading)
class Money:
"""Money class with operator overloading."""
def __init__(self, amount: float, currency: str = "USD"):
self.amount = amount
self.currency = currency
def __add__(self, other):
"""Add two money amounts."""
if isinstance(other, Money):
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
elif isinstance(other, (int, float)):
return Money(self.amount + other, self.currency)
return NotImplemented
def __sub__(self, other):
"""Subtract two money amounts."""
if isinstance(other, Money):
if self.currency != other.currency:
raise ValueError("Cannot subtract different currencies")
return Money(self.amount - other.amount, .currency)
(other, (, )):
Money(.amount - other, .currency)
():
(other, (, )):
Money(.amount * other, .currency)
():
(other, (, )):
other == :
ValueError()
Money(.amount / other, .currency)
():
(other, Money):
.amount == other.amount .currency == other.currency
():
(other, Money):
.currency != other.currency:
ValueError()
.amount < other.amount
():
== other < other
():
(other, Money):
.currency != other.currency:
ValueError()
.amount > other.amount
():
== other > other
():
():
():
((.amount, .currency))
price = Money()
tax = Money()
total = price + tax
discounted = total *
(total > price)
Duck Typing and Structural Polymorphism
Duck Typing in Python
class FileWriter:
"""Writes to a file."""
def __init__(self, filename: str):
self.file = open(filename, 'w')
def write(self, data: str) -> None:
self.file.write(data)
def close(self) -> None:
self.file.close()
class StringWriter:
"""Writes to a string buffer."""
def __init__(self):
self.buffer = []
def write(self, data: str) -> None:
self.buffer.append(data)
def close(self) -> None:
pass
def get_value(self) -> str:
return ''.join(self.buffer)
class NetworkWriter:
():
.host = host
.port = port
.connected =
() -> :
()
() -> :
.connected =
()
() -> :
writer.write()
writer.write( * + )
item data:
writer.write()
writer.close()
save_report(FileWriter(), , [, ])
save_report(StringWriter(), , [, ])
save_report(NetworkWriter(, ), , [])
When to Use This Skill
Apply polymorphism when:
- Building extensible plugin architectures
- Creating interchangeable implementations
- Writing code against interfaces/abstractions
- Implementing the strategy pattern
- Building dependency injection systems
- Creating framework hooks and extension points
- Supporting multiple data formats or protocols
- Implementing command patterns
- Building notification or messaging systems
- Creating abstract data access layers
- Supporting multiple rendering engines
- Implementing visitor patterns
- Building state machines with polymorphic states
- Creating factory methods that return polymorphic types
- Designing testable code with mock objects
Best Practices
- Program to interfaces, not implementations
- Use abstract base classes for shared behavior
- Keep interfaces small and focused (ISP)
- Use polymorphism to eliminate switch statements
- Favor composition over inheritance for flexibility
- Make polymorphic methods virtual/abstract appropriately
- Use dependency injection for polymorphic dependencies
- Document the contract/behavior expected from implementations
- Provide default implementations where sensible
- Use generics with polymorphism for type safety
- Override equality methods when overriding operators
- Keep polymorphic hierarchies shallow
- Use factory patterns to create polymorphic objects
- Test each implementation of a polymorphic interface
- Consider using protocols/structural typing for flexibility
Common Pitfalls
- Breaking Liskov Substitution Principle
- Creating too many small interfaces
- Not providing consistent behavior across implementations
- Overusing inheritance for polymorphism
- Forgetting to override Object methods (equals, hashCode)
- Creating leaky abstractions
- Mixing abstraction levels in interfaces
- Not handling null/None in polymorphic code
- Creating circular dependencies between polymorphic types
- Overloading methods with similar but different semantics
- Not considering performance of virtual method calls
- Using reflection instead of polymorphism
- Creating god interfaces with too many methods
- Not testing substitutability of implementations
- Coupling to concrete types instead of abstractions
Resources