Class with Excessive Number of Child Classes
Description
Class with Excessive Number of Child Classes occurs when a class contains an unnecessarily large number of children (subclasses). CISQ recommends a default maximum of 10 child classes as a threshold. When a class has too many direct subclasses, it often indicates that the class is too general or is being used as a catch-all base class. This creates a fragile base class problem where changes to the parent can break many subclasses, and makes the inheritance hierarchy difficult to understand and maintain.
Risk
Excessive child classes have indirect security implications. The fragile base class problem means security fixes to the parent may break child classes. Understanding the complete behavior requires examining many subclasses. Security audits become impractical with too many variations. Changes to shared parent functionality have high regression risk. The complexity makes it easier to introduce vulnerabilities. Inconsistent override behaviors across many children can create security gaps. The design often indicates misuse of inheritance where composition would be more appropriate.
Solution
Favor composition over inheritance - use delegation instead of subclassing. Apply the Strategy pattern to encapsulate varying behavior. Use interfaces to define contracts without implementation inheritance. Consider if subclasses should be separate classes using the parent via composition. Group related subclasses into intermediate abstract classes. Apply the Template Method pattern for controlled extension points. Use static analysis to detect classes with too many children. Refactor hierarchies to reduce depth and breadth. Consider using generics/templates instead of subclass variations.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Many child classes make the parent difficult to change safely. |
| Other | Scope: Other Increase Analytical Complexity - Understanding behavior requires examining many subclasses. |
| Other | Scope: Other Quality Degradation - The fragile base class problem causes cascading bugs. |
Example Code
Vulnerable Code
// Vulnerable: Base class with too many children (20+ subclasses)
public abstract class VulnerableNotificationHandler {
protected User recipient;
protected String message;
public abstract void send();
public void setRecipient(User recipient) {
this.recipient = recipient;
}
public void setMessage(String message) {
this.message = message;
}
// If we change this method signature, 20+ classes break!
protected void logNotification() {
System.out.println("Notification sent to: " + recipient.getEmail());
}
}
// Child 1: Email notification
public class EmailNotificationHandler extends VulnerableNotificationHandler {
@Override
public void send() {
sendEmail(recipient.getEmail(), message);
logNotification();
}
}
// Child 2: SMS notification
public class SmsNotificationHandler extends VulnerableNotificationHandler {
@Override
public void send() {
sendSms(recipient.getPhone(), message);
logNotification();
}
}
// Child 3: Push notification
public class PushNotificationHandler extends VulnerableNotificationHandler {
@Override
public void send() { /* ... */ }
}
// Child 4-20: More notification types...
public class SlackNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class TeamsNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class DiscordNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class TelegramNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class WhatsAppNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class WebhookNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class InAppNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class DesktopNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class VoiceNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class FaxNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class PagerNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class TwitterNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class FacebookNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class LinkedInNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class IrcNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
public class RssNotificationHandler extends VulnerableNotificationHandler { /* ... */ }
// Problems:
// 1. 20+ child classes - exceeds CISQ threshold of 10
// 2. Changing parent affects all children
// 3. Hard to understand complete notification behavior
// 4. Each child has nearly identical structure - code smell
// 5. Adding new notification type requires new class
# Vulnerable: Base class with too many subclasses
class VulnerablePaymentProcessor:
"""Base payment processor - too many subclasses."""
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def process(self):
raise NotImplementedError
def validate(self):
if self.amount <= 0:
raise ValueError("Invalid amount")
def log_transaction(self):
print(f"Processed {self.amount} {self.currency}")
# 15+ child classes for different payment methods
class CreditCardProcessor(VulnerablePaymentProcessor):
def process(self):
self.validate()
# Process credit card
self.log_transaction()
class DebitCardProcessor(VulnerablePaymentProcessor):
def process(self):
self.validate()
# Process debit card
self.log_transaction()
class PayPalProcessor(VulnerablePaymentProcessor):
def process(self):
self.validate()
# Process PayPal
self.log_transaction()
class StripeProcessor(VulnerablePaymentProcessor):
def process(self): pass
class SquareProcessor(VulnerablePaymentProcessor):
def process(self): pass
class ApplePayProcessor(VulnerablePaymentProcessor):
def process(self): pass
class GooglePayProcessor(VulnerablePaymentProcessor):
def process(self): pass
class VenmoProcessor(VulnerablePaymentProcessor):
def process(self): pass
class CashAppProcessor(VulnerablePaymentProcessor):
def process(self): pass
class BitcoinProcessor(VulnerablePaymentProcessor):
def process(self): pass
class WireTransferProcessor(VulnerablePaymentProcessor):
def process(self): pass
class AchProcessor(VulnerablePaymentProcessor):
def process(self): pass
class CheckProcessor(VulnerablePaymentProcessor):
def process(self): pass
class CashProcessor(VulnerablePaymentProcessor):
def process(self): pass
class GiftCardProcessor(VulnerablePaymentProcessor):
def process(self): pass
# 15 child classes - exceeds threshold
Fixed Code
// Fixed: Use Strategy pattern and composition instead of inheritance
// Define interface for notification sending
public interface NotificationSender {
void send(User recipient, String message);
String getChannel();
}
// Implementations are not subclasses - they implement interface
public class EmailSender implements NotificationSender {
private final EmailClient emailClient;
public EmailSender(EmailClient emailClient) {
this.emailClient = emailClient;
}
@Override
public void send(User recipient, String message) {
emailClient.send(recipient.getEmail(), message);
}
@Override
public String getChannel() {
return "email";
}
}
public class SmsSender implements NotificationSender {
private final SmsGateway gateway;
public SmsSender(SmsGateway gateway) {
this.gateway = gateway;
}
@Override
public void send(User recipient, String message) {
gateway.send(recipient.getPhone(), message);
}
@Override
public String getChannel() {
return "sms";
}
}
// Fixed: Single NotificationService uses composition, not inheritance
public class NotificationService {
private final Map<String, NotificationSender> senders;
private final NotificationLogger logger;
public NotificationService(List<NotificationSender> senders,
NotificationLogger logger) {
this.senders = senders.stream()
.collect(Collectors.toMap(
NotificationSender::getChannel,
Function.identity()
));
this.logger = logger;
}
public void send(String channel, User recipient, String message) {
NotificationSender sender = senders.get(channel);
if (sender == null) {
throw new UnsupportedChannelException(channel);
}
sender.send(recipient, message);
logger.log(channel, recipient, message);
}
// Add new channel without creating new class
public void registerSender(NotificationSender sender) {
senders.put(sender.getChannel(), sender);
}
}
// Benefits:
// - No inheritance hierarchy
// - Adding new sender is just implementing interface
// - Easy to test each sender independently
// - NotificationService is stable - no fragile base class
# Fixed: Strategy pattern with composition
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, Callable
from decimal import Decimal
# Strategy interface
class PaymentGateway(ABC):
"""Interface for payment gateways."""
@abstractmethod
def process_payment(self, amount: Decimal, currency: str,
payment_details: dict) -> PaymentResult:
pass
@abstractmethod
def get_name(self) -> str:
pass
# Concrete strategies (not subclasses of a common processor)
class StripeGateway(PaymentGateway):
"""Stripe payment implementation."""
def __init__(self, api_key: str):
self._api_key = api_key
def process_payment(self, amount: Decimal, currency: str,
payment_details: dict) -> PaymentResult:
# Stripe-specific implementation
return PaymentResult(success=True, transaction_id="stripe_123")
def get_name(self) -> str:
return "stripe"
class PayPalGateway(PaymentGateway):
"""PayPal payment implementation."""
def __init__(self, client_id: str, secret: str):
self._client_id = client_id
self._secret = secret
def process_payment(self, amount: Decimal, currency: str,
payment_details: dict) -> PaymentResult:
# PayPal-specific implementation
return PaymentResult(success=True, transaction_id="paypal_456")
def get_name(self) -> str:
return "paypal"
# Fixed: Processor uses composition, not inheritance
@dataclass
class PaymentResult:
success: bool
transaction_id: str
error: str = None
class PaymentProcessor:
"""Main payment processor using strategy pattern."""
def __init__(self, validator: PaymentValidator, logger: TransactionLogger):
self._gateways: Dict[str, PaymentGateway] = {}
self._validator = validator
self._logger = logger
def register_gateway(self, gateway: PaymentGateway) -> None:
"""Register a payment gateway."""
self._gateways[gateway.get_name()] = gateway
def process(self, gateway_name: str, amount: Decimal,
currency: str, payment_details: dict) -> PaymentResult:
"""Process payment through specified gateway."""
# Validate
self._validator.validate(amount, currency, payment_details)
# Get gateway
gateway = self._gateways.get(gateway_name)
if not gateway:
raise ValueError(f"Unknown gateway: {gateway_name}")
# Process
result = gateway.process_payment(amount, currency, payment_details)
# Log
self._logger.log(gateway_name, amount, currency, result)
return result
# Usage: Add gateways without creating subclasses
processor = PaymentProcessor(validator, logger)
processor.register_gateway(StripeGateway("sk_live_xxx"))
processor.register_gateway(PayPalGateway("client_id", "secret"))
# Process payment
result = processor.process("stripe", Decimal("99.99"), "USD", {
"card_token": "tok_xxx"
})
# For truly configurable behavior, use function registry
class FunctionalPaymentProcessor:
"""Alternative: Function-based strategy."""
def __init__(self):
self._processors: Dict[str, Callable] = {}
def register(self, name: str, processor: Callable) -> None:
self._processors[name] = processor
def process(self, name: str, **kwargs) -> PaymentResult:
processor = self._processors.get(name)
if not processor:
raise ValueError(f"Unknown processor: {name}")
return processor(**kwargs)
# Register processors as functions
processor = FunctionalPaymentProcessor()
processor.register("stripe", lambda **kw: stripe_process(**kw))
processor.register("paypal", lambda **kw: paypal_process(**kw))
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality/maintainability concern rather than a direct security vulnerability.
Related CWEs
- CWE-1093: Excessively Complex Data Representation (parent)
- CWE-1074: Class with Excessively Deep Inheritance (related)
- CWE-1055: Multiple Inheritance from Concrete Classes (related)
References
- MITRE Corporation. "CWE-1086: Class with Excessive Number of Child Classes." https://cwe.mitre.org/data/definitions/1086.html
- CISQ. "Automated Source Code Quality Measures."
- Gamma, Erich et al. "Design Patterns" - Strategy Pattern.