Klasse mit übermäßiger Anzahl von Kindklassen
Beschreibung
Klasse mit übermäßiger Anzahl von Kindklassen tritt auf, wenn eine Klasse eine unnötig große Anzahl von Kindern (Unterklassen) enthält. CISQ empfiehlt ein Standardmaximum von 10 Kindklassen als Schwellenwert. Wenn eine Klasse zu viele direkte Unterklassen hat, deutet dies oft darauf hin, dass die Klasse zu allgemein ist oder als Sammelklasse für Basisklassen verwendet wird. Dies erzeugt ein fragiles Basisklassenproblem, bei dem Änderungen am Elternteil viele Unterklassen brechen können, und macht die Vererbungshierarchie schwer zu verstehen und zu warten.
Risiko
Übermäßige Kindklassen haben indirekte Sicherheitsimplikationen. Das fragile Basisklassenproblem bedeutet, dass Sicherheitsfixes im Elternteil Kindklassen brechen können. Das Verständnis des vollständigen Verhaltens erfordert die Untersuchung vieler Unterklassen. Sicherheitsaudits werden mit zu vielen Variationen unpraktisch. Änderungen an gemeinsam genutzter Elternfunktionalität haben hohes Regressionsrisiko. Die Komplexität erleichtert das Einführen von Schwachstellen. Inkonsistentes Override-Verhalten über viele Kinder kann Sicherheitslücken schaffen. Das Design deutet oft auf Missbrauch von Vererbung hin, wo Komposition angemessener wäre.
Lösung
Bevorzugen Sie Komposition über Vererbung - verwenden Sie Delegation anstatt Unterklassenbildung. Wenden Sie das Strategy-Pattern an, um variierendes Verhalten zu kapseln. Verwenden Sie Interfaces, um Verträge ohne Implementierungsvererbung zu definieren. Erwägen Sie, ob Unterklassen separate Klassen sein sollten, die das Elternteil via Komposition verwenden. Gruppieren Sie verwandte Unterklassen in zwischengeschaltete abstrakte Klassen. Wenden Sie das Template-Method-Pattern für kontrollierte Erweiterungspunkte an. Verwenden Sie statische Analyse, um Klassen mit zu vielen Kindern zu erkennen. Refaktorisieren Sie Hierarchien, um Tiefe und Breite zu reduzieren. Erwägen Sie die Verwendung von Generics/Templates anstelle von Unterklassenvariationen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Andere | Bereich: Ändere Reduzierte Wartbarkeit - Viele Kindklassen machen das Elternteil schwer sicher zu ändern. |
| Andere | Bereich: Ändere Erhöhte analytische Komplexität - Verhaltensverständnis erfordert Untersuchung vieler Unterklassen. |
| Andere | Bereich: Ändere Qualitätsverschlechterung - Das fragile Basisklassenproblem verursacht kaskadierende Fehler. |
Beispielcode
Anfälliger Code
// Anfällig: Basisklasse mit zu vielen Kindern (20+ Unterklassen)
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;
}
// Wenn wir diese Methodensignatur ändern, brechen 20+ Klassen!
protected void logNotification() {
System.out.println("Benachrichtigung gesendet an: " + recipient.getEmail());
}
}
// Kind 1: E-Mail-Benachrichtigung
public class EmailNotificationHandler extends VulnerableNotificationHandler {
@Override
public void send() {
sendEmail(recipient.getEmail(), message);
logNotification();
}
}
// Kind 2: SMS-Benachrichtigung
public class SmsNotificationHandler extends VulnerableNotificationHandler {
@Override
public void send() {
sendSms(recipient.getPhone(), message);
logNotification();
}
}
// Kind 3: Push-Benachrichtigung
public class PushNotificationHandler extends VulnerableNotificationHandler {
@Override
public void send() { /* ... */ }
}
// Kind 4-20: Mehr Benachrichtigungstypen...
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 { /* ... */ }
// Probleme:
// 1. 20+ Kindklassen - überschreitet CISQ-Schwellenwert von 10
// 2. Elternänderung betrifft alle Kinder
// 3. Schwer, vollständiges Benachrichtigungsverhalten zu verstehen
// 4. Jedes Kind hat nahezu identische Struktur - Code-Smell
// 5. Hinzufügen neuer Benachrichtigungstypen erfordert neue Klasse
# Anfällig: Basisklasse mit zu vielen Unterklassen
class VulnerablePaymentProcessor:
"""Basis-Zahlungsprozessor - zu viele Unterklassen."""
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("Ungültiger Betrag")
def log_transaction(self):
print(f"Verarbeitet {self.amount} {self.currency}")
# 15+ Kindklassen für verschiedene Zahlungsmethoden
class CreditCardProcessor(VulnerablePaymentProcessor):
def process(self):
self.validate()
# Kreditkarte verarbeiten
self.log_transaction()
class DebitCardProcessor(VulnerablePaymentProcessor):
def process(self):
self.validate()
# Debitkarte verarbeiten
self.log_transaction()
class PayPalProcessor(VulnerablePaymentProcessor):
def process(self):
self.validate()
# PayPal verarbeiten
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 Kindklassen - überschreitet Schwellenwert
Korrigierter Code
// Korrigiert: Strategy-Pattern und Komposition statt Vererbung verwenden
// Interface für Benachrichtigungsversand definieren
public interface NotificationSender {
void send(User recipient, String message);
String getChannel();
}
// Implementierungen sind keine Unterklassen - sie implementieren 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";
}
}
// Korrigiert: Einzelner NotificationService verwendet Komposition, nicht Vererbung
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);
}
// Neuen Kanal hinzufügen ohne neue Klasse zu erstellen
public void registerSender(NotificationSender sender) {
senders.put(sender.getChannel(), sender);
}
}
// Vorteile:
// - Keine Vererbungshierarchie
// - Neuen Sender hinzufügen ist nur Interface-Implementierung
// - Einfach jeden Sender unabhängig zu testen
// - NotificationService ist stabil - kein fragiles Basisklassenproblem
# Korrigiert: Strategy-Pattern mit Komposition
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, Callable
from decimal import Decimal
# Strategy-Interface
class PaymentGateway(ABC):
"""Interface für Zahlungsgateways."""
@abstractmethod
def process_payment(self, amount: Decimal, currency: str,
payment_details: dict) -> 'PaymentResult':
pass
@abstractmethod
def get_name(self) -> str:
pass
# Konkrete Strategien (keine Unterklassen eines gemeinsamen Prozessors)
class StripeGateway(PaymentGateway):
"""Stripe-Zahlungsimplementierung."""
def __init__(self, api_key: str):
self._api_key = api_key
def process_payment(self, amount: Decimal, currency: str,
payment_details: dict) -> 'PaymentResult':
# Stripe-spezifische Implementierung
return PaymentResult(success=True, transaction_id="stripe_123")
def get_name(self) -> str:
return "stripe"
class PayPalGateway(PaymentGateway):
"""PayPal-Zahlungsimplementierung."""
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-spezifische Implementierung
return PaymentResult(success=True, transaction_id="paypal_456")
def get_name(self) -> str:
return "paypal"
# Korrigiert: Prozessor verwendet Komposition, nicht Vererbung
@dataclass
class PaymentResult:
success: bool
transaction_id: str
error: str = None
class PaymentProcessor:
"""Haupt-Zahlungsprozessor mit 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:
"""Registriert ein Zahlungsgateway."""
self._gateways[gateway.get_name()] = gateway
def process(self, gateway_name: str, amount: Decimal,
currency: str, payment_details: dict) -> PaymentResult:
"""Verarbeitet Zahlung über angegebenes Gateway."""
# Validieren
self._validator.validate(amount, currency, payment_details)
# Gateway abrufen
gateway = self._gateways.get(gateway_name)
if not gateway:
raise ValueError(f"Unbekanntes Gateway: {gateway_name}")
# Verarbeiten
result = gateway.process_payment(amount, currency, payment_details)
# Protokollieren
self._logger.log(gateway_name, amount, currency, result)
return result
# Verwendung: Gateways hinzufügen ohne Unterklassen zu erstellen
processor = PaymentProcessor(validator, logger)
processor.register_gateway(StripeGateway("sk_live_xxx"))
processor.register_gateway(PayPalGateway("client_id", "secret"))
# Zahlung verarbeiten
result = processor.process("stripe", Decimal("99.99"), "EUR", {
"card_token": "tok_xxx"
})
# Für wirklich konfigurierbares Verhalten, Funktionsregistry verwenden
class FunctionalPaymentProcessor:
"""Alternative: Funktionsbasierte Strategie."""
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"Unbekannter Prozessor: {name}")
return processor(**kwargs)
# Prozessoren als Funktionen registrieren
processor = FunctionalPaymentProcessor()
processor.register("stripe", lambda **kw: stripe_process(**kw))
processor.register("paypal", lambda **kw: paypal_process(**kw))
CVE-Beispiele
Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Codequalitäts-/Wartbarkeitsproblem und keine direkte Sicherheitsschwachstelle darstellt.
Verwandte CWEs
- CWE-1093: Excessively Complex Data Representation (Eltern)
- CWE-1074: Class with Excessively Deep Inheritance (verwandt)
- CWE-1055: Multiple Inheritance from Concrete Classes (verwandt)
Referenzen
-
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.