2014-04-30 2 views
5

Ich erstelle ein Projekt, das ein Flughafenlandesystem modelliert. Ich habe ein plane Objekt, das alle Informationen speichert, die ich brauche, um die plane in eine queue zu sortieren und in einer Datenbank zu speichern. Alle lebenswichtigen Informationen sind in dem Objekt enthalten, aber ich habe auch die Koordinaten für jede Ebene aufgenommen. Mein Problem ist, dass es nicht zusammenhängend sein kann, da jeder plane viele verschiedene Dinge tut.Was ist der Java-Standard für die Objektkohäsion? Bedeutet zu viel Information in einem Objekt schlechtes Design? - Siehe Beispiel

Ich möchte nur wissen, ob dies als schlechtes Design gilt oder gibt es einen besseren Weg, dies zu tun?

Auch, was ist die "Regel" für die Kohäsion innerhalb von Objekten? Gibt es ein bestimmtes Designmuster, das vielleicht damit umgehen kann?

public class Plane extends Aircraft { 
/* 
* Flight status should only take one of these enum values 
*/ 
private static enum Status { 
    REGISTERED, IN_QUEUE, LANDING, LANDED 
}; 

// Set aircraft status to REGISTERED when created 
private Status status = Status.REGISTERED; 

private double fuelLevelPercentage; 
private int passengerCount; 
private int aircraftNumber; 
private String airlineCompany; 
private String departureAirport; 

// This is used by the constructor to assign a random city to each new Aircraft 
private final String[] cities = { "Rome", "Berlin", "Heathrow", 
     "Edinburgh", "Cardiff", "Dublin", "Stansted" }; 
// Used to set airline companies 
private final String[] airLineCompanies = { "Easyjet", "Ryanair", 
     "British Airways","Flybe","Air Lingus", "Virgin" }; 


// Random number generator used by the constructor 
private Random rand; 

// Thread for this instance of Aircraft 
private Thread aircraftThread; 

// Radius of path when flying in circle (km?) 
private final double FLIGHT_RADIUS = 10; 
// Time taken to complete one complete loop (ms) 
private final double FLIGHT_PERIOD = 120000; 
// Angular frequency omega (rad/s) 
private double OMEGA = 2 * Math.PI/FLIGHT_PERIOD; 
// Time taken between being directed to land, and landing (ms) 
private int TIME_TAKEN_TO_LAND = 30000; 

// Time take to use one percent of fuel (ms) 
private double time_taken_to_use_one_percent_of_fuel = 30000; 

// variable to keep track of time since instantiated (ms) 
private int time = 0; 
// The aircraft Thread sleeps for TIME_STEP between updating 
private final int TIME_STEP = 20; 

private int time_when_called_to_land; 

private int hour_of_arrival; 
private int minute_of_arrival; 

/* 
* Set coordinates at time zero 
*/ 
private double x_coord = 0; 
private double y_coord = FLIGHT_RADIUS; 
private double altitude = 1000; 

/* 
* Used to calculate path to airport 
*/ 
private double x_coord_when_called; 
private double y_coord_when_called; 
private double altitude_when_called; 

Calendar calendar = Calendar.getInstance(); 

/** 
* This constructor sets the following fields to random values Dummy Data - 
* should have a better way to do this 
*/ 
public Plane() { 
    rand = new Random(); 

    this.fuelLevelPercentage = rand.nextInt(100); 
    this.departureAirport = cities[rand.nextInt(cities.length)]; 
    this.passengerCount = rand.nextInt(500); 
    this.aircraftNumber = rand.nextInt(50000000); 
    this.airlineCompany = airLineCompanies[rand.nextInt(airLineCompanies.length)]; 
} 

/** 
* this fly method will call on a different method depending on the status 
* of the Aircraft 
*/ 
public void fly() { 
    if (status == Status.REGISTERED) { 
     useFuel(); 
    } else if (status == Status.IN_QUEUE) { 
     flyInCircle(); 
     useFuel(); 
    } else if (status == Status.LANDING) { 
     flyToAirport(); 
     useFuel(); 
    } else if (status == Status.LANDED) { 

    } 
} 

public void flyInCircle() { 
    x_coord = FLIGHT_RADIUS * (Math.cos(OMEGA * (time))); 
    y_coord = FLIGHT_RADIUS * (Math.sin(OMEGA * (time))); 
} 

public void flyToAirport() { 
    if (!(x_coord < 1 && x_coord > -1 && y_coord < 1 && y_coord > -1 
      && altitude < 1 && altitude > -1)) { 
     x_coord -= x_coord_when_called * TIME_STEP/TIME_TAKEN_TO_LAND; 
     y_coord -= y_coord_when_called * TIME_STEP/TIME_TAKEN_TO_LAND; 
     altitude -= altitude_when_called * TIME_STEP/TIME_TAKEN_TO_LAND; 
    } else { 
     System.out.println("Aircraft landed"); 
     status = Status.LANDED; 
     hour_of_arrival = calendar.get(Calendar.HOUR_OF_DAY); 
     minute_of_arrival = calendar.get(Calendar.MINUTE); 
    } 

} 

/** 
* This method changes the flight status to IN_QUEUE - simulates telling the 
* plane to join queue 
*/ 
public void directToJoinQueue() { 
    setFlightStatus(Status.IN_QUEUE); 
} 

/** 
* This method changes the flight status to LANDING - simulates telling the 
* plane to land 
*/ 
public void directToflyToAirport() { 
    setFlightStatus(Status.LANDING); 
    time_when_called_to_land = time; 
    x_coord_when_called = x_coord; 
    y_coord_when_called = y_coord; 
    altitude_when_called = altitude; 
} 

/** 
* This method reduces fuel level according to fuel usage 
*/ 
private void useFuel() { 
    if (this.fuelLevelPercentage - TIME_STEP 
      /time_taken_to_use_one_percent_of_fuel > 0) { 
     this.fuelLevelPercentage -= TIME_STEP 
       /time_taken_to_use_one_percent_of_fuel; 
    } else { 
     this.fuelLevelPercentage = 0; 
    } 
} 

/** 
* this method sets the flight status 
*/ 
private void setFlightStatus(Status status) { 
    this.status = status; 
} 

public double getfuelLevelPercentage() { 
    return fuelLevelPercentage; 
} 

public int getPassengerCount() { 
    return passengerCount; 
} 

public void setPassengerCount(int passengerCount) { 
    this.passengerCount = passengerCount; 
} 

public int getAircraftNumber() { 
    return aircraftNumber; 
} 

public String getDepartureAirport() { 
    return departureAirport; 
} 

public void stop() { 
    ; 
} 

public String getAirlineCompany() { 
    return airlineCompany; 
} 

public void setAirlineCompany(String airlineCompany) { 
    this.airlineCompany = airlineCompany; 
} 

@Override 
public String toString() { 
    if (status == Status.LANDED) { 
     return String 
       .format("Flight %-8d | Fuel %-4.1f | Passengers %-3d | From %-10s | %-8s at %d:%d ", 
         aircraftNumber, fuelLevelPercentage, 
         passengerCount, departureAirport, status, 
         hour_of_arrival, minute_of_arrival); 
    } else { 
     return String 
       .format("Flight %-8d | Fuel %-4.1f | Passengers %-3d | From %-10s | %-8s | Coords (%-3.2f,%-3.2f) | Altitude %-4.2f", 
         aircraftNumber, fuelLevelPercentage, 
         passengerCount, departureAirport, status, x_coord, 
         y_coord, altitude); 
    } 

} 

public void start() { 
    aircraftThread = new Thread(this, this.getClass().getName()); 
    aircraftThread.start(); 
} 

@Override 
public void run() { 

    try { 

     while (true) { 
      calendar = Calendar.getInstance(); 
      fly(); 
      Thread.sleep(TIME_STEP); 
      time += TIME_STEP; 

     } 

     // System.out.println("aircraft number "+aircraftNumber+" safely landed"); 

    } catch (InterruptedException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
    } 

} 
} 

Antwort

5

Zusammenhalt ist ein schwieriges Konzept:

package model; 

public class Plane { 

    private int id; 
    public void save() { 
     // persist the state of this 
     // INSERT INTO PLANE(id) VALUES(?) 
    } 
} 

ich eine DAO-Schnittstelle in einem separaten Paket haben würde. Trotz der flotten Antworten der anderen Antwort hängt die wahre Antwort sehr davon ab, was Ihr System tut und wie es funktioniert. Lassen Sie uns zum Beispiel den Warteschlangenmechanismus untersuchen. Reagiert ein Flugzeug in Ihrem System anders auf Befehle in einer Warteschlange? Wenn dies der Fall ist, sollte die Tatsache, dass es sich in einer Warteschlange befindet, integraler Bestandteil des Flugzeugs sein. Reagiert es in verschiedenen Warteschlangen unterschiedlich? Wenn dies der Fall ist, sollte die Warteschlange selbst integraler Bestandteil des Flugzeugs sein. Wenn jedoch der Flughafen anders reagiert, weil sich das Flugzeug in einer Schlange befindet, sollte der Flughafen die Warteschlange kontrollieren und das Flugzeug sollte nichts darüber wissen - es sollte einfach eine Flugroute am Flughafen (oder am Flughafen) bekommen Kontrollturm am Flughafen, abhängig von der Auflösung Ihres Modells).

Zusammenhalt ist nicht Ihr einziges Problem hier. Einkapselung ist auch ein großes Problem. Sie lassen andere Objekte auf Ihren internen Status zugreifen. Um dies vollständig zu modellieren, sollten Sie das CQRS-Muster verwenden. Wenn Sie auch DDD-Techniken (Domain Driven Design) in Erwägung ziehen und zunächst Ihre beschränkten Kontexte und aggregierten Routen identifizieren, werden Sie wahrscheinlich ein korrektes Design ableiten.

+0

"Flippant"? Ich sehe hier nichts Tolles. – duffymo

+1

@duffymo - nehmen Sie es nicht schwer. Ich denke, Sie haben die Frage beantwortet, ohne dem OP zu erklären, wie er es selbst beantworten soll. Ich glaube nicht, dass deine Antwort falsch war, aber ich denke nicht, dass es sehr hilfreich war. –

+0

Es ist nicht schwer. Ich glaube, ich habe etwas gelehrt - ich habe Code geschrieben, um zu zeigen, was ich vorgeschlagen habe. Ich hätte Eric Evans vielleicht nicht zitiert, aber die Empfehlung, die Warteschlange zu trennen, kam zuerst von mir. – duffymo

4

Es gibt keinen "Standard" für Java oder eine andere Sprache.

Ich habe ein "Ebene" -Objekt, das alle Informationen, die ich brauche, zu sortiere das Flugzeug in eine Warteschlange und an eine Datenbank übergeben. Alle wichtigen Informationen sind im Objekt enthalten, aber ich habe auch die Koordinaten für jede Ebene enthalten.

Ich denke, Ihr Plane-Modell-Objekt tut zu viel.

Ich sehe nicht, warum es wissen sollte, ob es in einer Warteschlange ist oder nicht. Ich hätte ein separates Objekt, das die Warteschlange besitzt, die Regeln kennen.

Ist die Warteschlange eine In-Memory-Sammlung oder eine Nachrichtenwarteschlange? Ist es dir wichtig?

Modellobjekte, die sich selbst erhalten, sind ein Diskussionspunkt. Ich denke, es ist einfacher, Persistenz in ein separates Datenzugriffsobjekt zu trennen, so dass es einfacher ist zu testen.

Ihr Modell könnte wie folgt aussehen:

package persistence; 

public interface PlaneDAO { 
    void save(Plane p); 
} 
+0

Ich stimme zu, dass das Objekt nicht sehen sollte, ob es in der Warteschlange ist oder nicht. Ich werde das ändern. Was meinst du mit einem Objekt, das sich selbst hält? und durch die Trennung von Persistenz? danke – Zanarou

1

Es gibt keinen Java-Standard bezüglich der Objektkohäsion.(Ich wiederhole nicht die Ratschläge von Duffymo, ich stimme allen zu).

Eine Sache im Auge zu behalten, wenn Sie ein Objektmodell der reale Welt abbildet erarbeiten, ist zu versucheneine Klasse Mapping ein Konzept der realen Welt zu haben.

Zur Veranschaulichung, in Ihrem Beispielcode haben Sie mindestens 2 Unterscheidungen Konzepte: Plane und Flight und so können Sie sie in zwei separate Klassen mit einer Eins-zu-viele-Beziehung zwischen ihnen aufteilen.

2

Kohäsion kann als degree to which the elements of a module belong together definiert werden.

Visualisierung hilft. Stellen Sie sich die Attribute einer Klasse und die Methoden vor. Wenn Ihre Klasse zusammenhängend ist, bedeutet dies, dass die Methoden viele der Attribute verwenden, und umgekehrt werden die Attribute von vielen der Methoden verwendet. Dies ist die "klebende" Vorstellung von Zusammenhalt. Ich mag die folgende Darstellung, die von NDepend's placemat kommt:

enter image description here

Wie andere darauf hingewiesen, die Methoden, die das Flugzeug lenken (zB directToX) sind möglicherweise außerhalb des „Thema“ von dem, was ein Flugzeug ist , aber sie sind nicht unverschämt falsch. Diese Elemente (Verantwortlichkeiten) könnten in einer anderen Klasse besser sein, sagen wir AirTrafficController. In Wirklichkeit entscheiden Flugzeuge nicht viel darüber, wie sie fliegen. Ihre Piloten müssen den Anweisungen vom Boden folgen.

Ich würde argumentieren, dass das Thread-Zeug (start, run) definitiv außerhalb des Themas eines Flugzeugs ist. Diese Methoden verwenden kaum etwas, das Teil einer Plane ist (sie sind Ablenkungen von seinem Thema). Sie könnten use an anonymous inner class to handle the processing in a thread aus der main und Ihre Ebene wäre noch wiederverwendbar (und zusammenhängend).

Ein zusammenhängendes Objekt bekommt die Essenz der Sache, die es modelliert. Dies bedeutet, dass es leichter in einer anderen Anwendung (oder sogar einer anderen OO-Sprache) wieder verwendet werden könnte. Alles, was sich außerhalb des eigentlichen Themas Ihres Konzepts entwickelt, wird es wahrscheinlich schwieriger machen, das Konzept in einer anderen Anwendung wieder zu verwenden. Die "Ablenkungen" machen in einer anderen Anwendung keinen Sinn mehr.

Wenn Sie ein Kamikaze Projekt entwickeln (eines, bei dem Sie es einfach nur zum Laufen bringen wollen und es nicht nach Wiederverwendung geht), ist es völlig in Ordnung, den Zusammenhalt (und andere Designelemente) zu vergessen. Design-Entscheidungen sind Kompromisse. Sie könnten Ihre Plane-Klasse umgestalten, um sie zusammenhängender zu machen, aber wenn Sie sie nie in einer anderen Anwendung wiederverwenden, haben Sie vielleicht Ihre Zeit verschwendet. Auf der anderen Seite ist Design ein Lernprozess; Selbst wenn Sie etwas für eine Anwendung überarbeiten, haben Sie vielleicht etwas für die nächste gelernt.

Schließlich sind alle Designaspekte schwer zu quantifizieren und daher gibt es wenige Standards. Es ist bekannt, dass einige Unternehmen (willkürliche) Standards für Metriken wie LCOM in ihren Entwicklungsprozessen festlegen. Ich habe von Team-Standards gelesen, die sagen, wenn eine Klasse einen schlechten Wert für LCOM hat, dann muss sie refaktoriert werden, bis ihr Wert niedrig genug wird (ihre Kohäsion ist stärker). Leider LCOM can be a bad measure of cohesion (especially in classes that have lots of get/set methods).

Verwandte Themen