4 Stimmen

Vermeidung von Typenschaltungen

Wenn Sie in einem Team arbeiten und ein Programmierer Ihnen eine Schnittstelle mit Erstellungs-, Lese-, Aktualisierungs- und Löschmethoden gibt, wie vermeiden Sie dann Typwechsel?

Quoting Clean Code A Handbook of Agile Software Craftsmanship:

public Money calculatePay(Employee e)
    throws InvalidEmployeeType {
        switch (e.type) {
            case COMMISSIONED:
                return calculateCommissionedPay(e);
            case HOURLY:
                return calculateHourlyPay(e);
            case SALARIED:
                return calculateSalariedPay(e);
            default:
                throw new InvalidEmployeeType(e.type);
    }
}

Es gibt mehrere Probleme mit dieser Funktion. Erstens ist sie sehr umfangreich, und wenn neue Mitarbeitertypen hinzugefügt werden, wird sie wachsen. Zweitens macht sie ganz klar mehr als eine Sache. Drittens verstößt sie gegen den Grundsatz der einzigen Verantwortung7 (SRP), da es mehr als einen Grund für eine Änderung gibt. Viertens verstößt sie gegen das Open-Closed-Prinzip8 (OCP), weil sie sich ändern muss, wenn neue Typen hinzugefügt werden. Aber das schlimmste Problem bei dieser Funktion ist, dass es eine unbegrenzte Anzahl anderer Funktionen gibt, die die gleiche Struktur haben. Wir könnten zum Beispiel haben

isPayday(Employee e, Date date),

o

deliverPay(Employee e, Money pay),

oder eine Vielzahl anderer. Sie alle hätten die gleiche schädliche Struktur.

Das Buch sagt mir, dass ich das Factory Pattern verwenden soll, aber in einer Weise, die mir das Gefühl gibt, dass ich es nicht wirklich verwenden sollte.

Ich zitiere noch einmal das Buch:

Die Lösung für dieses Problem (siehe Listing 3-5) besteht darin, die switch-Anweisung in den Keller einer ABSTRACT FACTORY9 zu vergraben und sie niemanden sehen zu lassen.

Ist die Switch-Anweisung hässlich?

7voto

Tyler Carter Punkte 58971

In Wirklichkeit sollte das Mitarbeiterobjekt über eine eigene Funktion zur Lohnberechnung verfügen, die Ihnen den Lohn ausgibt. Diese Funktion zur Berechnung des Gehalts würde sich je nach Art des Mitarbeiters ändern.

Auf diese Weise ist es Sache des Objekts, die Implementierung zu definieren, und nicht des Benutzers des Objekts.

abstract class Employee
{
     public abstract function calculatePay();
}

class HourlyEmployee extends Employee
{
     public function calculatePay()
     {
          return $this->hour * $this->pay_rate;
     }
}

class SalariedEmployee extends Employee
{
     public function calculatePay()
     {
          return $this->monthly_pay_rate;
     }
}

Wenn Sie die Fabrik bauen, DANN machen Sie die Switch-Anweisung dort, und nur einmal, um den Mitarbeiter zu bauen.

Angenommen, Employee befände sich in einem Array, und der Typ des Mitarbeiters sei in $array['Type']

public function buildEmployee($array)
{
    switch($array['Type']){
       case 'Hourly':
            return new HourlyEmployee($array);
            break;
       case 'Salaried':
            return new SalariedEmployee($array);
            break;
}

Zur Berechnung des Gehalts schließlich

$employee->calculatePay();

Jetzt ist es nicht mehr nötig, mehr als eine Bescheinigung zu verwenden, um das Gehalt des Mitarbeiters je nach Art des Mitarbeiters zu berechnen. Es ist einfach ein Teil des Mitarbeiterobjekts.

Haftungsausschluss , Da ich minderjährig bin, weiß ich nicht genau, wie einige dieser Gehälter berechnet werden. Aber die Basis des Arguments ist immer noch gültig. Der Lohn sollte im Objekt berechnet werden.

Haftungsausschluss 2 , Dies ist PHP-Code. Aber noch einmal, das Argument sollte für jede Sprache gültig sein.

2voto

Anurag Punkte 136648

Sie können den Schalter vollständig entfernen, indem Sie eine Map um die Art eines Mitarbeiters dem entsprechenden Lohnrechner zuzuordnen. Dies hängt von der Reflexion ab und ist in allen mir bekannten Sprachen möglich.

Unter der Annahme, dass die Lohnberechnung nicht in der Verantwortung eines Mitarbeiters liegt, haben wir eine Schnittstelle PayCalculation :

interface PayCalculation {
    function calculatePay(Employee $employee);
}

Für jede Mitarbeiterkategorie gibt es eine Umsetzung:

class SalariedPayCalculator implements PayCalculation {
    public function calculatePay(SalariedEmployee $employee) {
        return $employee.getSalary();
    }
}

class HourlyPayCalculator implements PayCalculation {
    public function calculatePay(HourlyEmployee $employee) {
        return $employee.getHourlyRate() * e.getHoursWorked();
    }
}

class CommissionedPayCalculator implements PayCalculation {
    public function calculatePay(CommissionedEmployee $employee) {
        return $employee.getCommissionRate() * $employee.getUnits();
    }
}

Die Berechnung des Gehalts würde in etwa so funktionieren. Reflection ist wichtig, um ein Objekt zu betrachten und seine Klasse zur Laufzeit zu bestimmen. Damit kann die Switch-Schleife eliminiert werden.

public class EmployeePayCalculator implements PayCalculation {

    private $map = array();

    public function __construct() {
        $this->map['SalariedEmployee'] = new SalariedPayCalculator();
        $this->map['HourlyEmployee'] = new HourlyPayCalculator();
        $this->map['CommissionedEmployee'] = new CommissionedPayCalculator();
    }

    public function calculatePay(Employee $employee) {
        $employeeType = get_class($employee);
        $calculator = $this->map[$employeeType];
        return $calculator->calculatePay($employee);
    }
}

Hier initialisieren wir die Karte im Konstruktor, aber sie kann leicht nach außen in eine XML-Konfigurationsdatei oder eine Datenbank verschoben werden:

<payCalculation>
    <category>
        <type>Hourly</type>
        <payCalculator>HourlyPayCalculator</payCalculator>
    </category>
    <category>
        <type>Salaried</type>
        <payCalculator>SalariedPayCalculator</payCalculator>
    </category>
    ...
</payCalculation>

1voto

Zoli Punkte 1127

Ich habe irgendwo gelesen, dass man bei der Verwendung eines switch dann ist es verdächtig, dass es zu viele Unterschiede gibt. Und wenn wir zu viel Variation haben, sollten wir versuchen, die Variation hinter einer Schnittstelle zu kapseln und so die Abhängigkeiten zwischen den Objekten zu entkoppeln. Davon abgesehen denke ich, dass man versuchen sollte, eine SalaryType leichtgewichtiges Basisklassenobjekt, das diese Art von Logik kapseln wird. Dann machen Sie es zu einem Mitglied von class Employee und befreien Sie sich von den switch konstruieren. Das meine ich kurz und bündig:

abstract class SalaryType
{
   function calculatePay() {}
}

class CommissionedType extends SalaryType
{
   function calculatePay() {}    
}

class HourlyType extends SalaryType
{
   function calculatePay() {}    
}

class SalaryType extends SalaryType
{
   function calculatePay() {}    
}

class Employee
{
  private $salaryType;

  public function setType( SalaryType emp )
  {
     $this->salaryType = emp;
  }

  public function calculatePay()
  {
     $this->salaryType->calculatePay();
  }
}

Übrigens scheint ein großer Teil Ihres Beispielcodes nicht sehr "PHP-isch" zu sein. Es gibt keine Rückgabetypen in PHP und auch keine echte Typsicherheit. Bedenken Sie auch, dass PHP nicht wirklich polymorph ist, so dass einige der polymorphen Verhaltensweisen, die in typischen typsicheren Sprachen zu finden sind, hier möglicherweise nicht wie erwartet funktionieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X