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

AuswirkungDetails
AndereBereich: Ändere

Reduzierte Wartbarkeit - Viele Kindklassen machen das Elternteil schwer sicher zu ändern.
AndereBereich: Ändere

Erhöhte analytische Komplexität - Verhaltensverständnis erfordert Untersuchung vieler Unterklassen.
AndereBereich: Ä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

  1. MITRE Corporation. "CWE-1086: Class with Excessive Number of Child Classes." https://cwe.mitre.org/data/definitions/1086.html

  2. CISQ. "Automated Source Code Quality Measures."

  3. Gamma, Erich et al. "Design Patterns" - Strategy Pattern.