5

我正在创建一个模拟机场着陆系统的项目。我有一个plane对象,它存储我需要的所有信息,以将其分类planequeue存储在数据库中。所有重要信息都包含在对象中,但我还包含了每个平面的坐标。我的问题是它可能不被认为是有凝聚力的,因为每个人都plane做了很多不同的事情。

我只是想知道这是否被认为是糟糕的设计,还是有更好的方法来做到这一点?

另外,对象内部凝聚力的“规则”是什么?是否有特定的设计模式可以解决这个问题?

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();
    }

}
}
4

4 回答 4

5

凝聚力是一个困难的概念。尽管其他答案的反应轻率,但真正的答案在很大程度上取决于您的系统做什么以及它是如何工作的。例如,让我们检查队列机制。在您的系统中,飞机在排队时对命令的响应是否不同?如果是这样,那么它在队列中的事实应该是飞机不可或缺的。它在不同队列中的响应是否不同?如果是这样,那么队列本身应该是飞机的一部分。然而,如果是因为飞机在队列中而做出不同反应的机场,那么机场应该控制队列并且飞机应该对此一无所知——它应该简单地由机场(或由机场的控制塔,具体取决于您的模型的分辨率)。

凝聚力不是您唯一的问题。封装也是一个大问题。您正在让其他对象访问您的内部状态。要以完全 OO 的方式对此进行建模,您应该考虑使用 CQRS 模式。如果您还考虑 DDD(领域驱动设计)技术,并从识别有界上下文和聚合路由开始,您将更有可能得出正确的设计。

于 2014-04-30T14:10:22.063 回答
4

Java 或任何其他语言没有“标准”。

我有一个“飞机”对象,它存储了将飞机分类到队列中并传递给数据库所需的所有信息。所有重要信息都包含在对象中,但我还包含了每个平面的坐标。

我认为您的平面模型对象做得太多。

我不明白为什么它应该知道它是否在队列中。我有一个拥有队列的单独对象,知道规则。

队列是内存中的集合还是消息队列?这对你重要吗?

模型对象的持久化是一个争论的焦点。我认为将持久性分离到单独的数据访问对象中更容易,因此更容易测试。

您的模型可能如下所示:

package model;

public class Plane {

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

我会在一个单独的包中有一个 DAO 接口:

package persistence;

public interface PlaneDAO {
    void save(Plane p);
}
于 2014-04-30T14:02:02.363 回答
2

内聚度可以定义为模块的元素属于一起的程度

可视化它会有所帮助。想象一个类的属性和方法。如果您的类是内聚的,则意味着方法将使用许多属性,相反,属性将被许多方法使用。这就是凝聚力的“粘在一起”概念。我喜欢来自NDepend 的 placemat的以下可视化:

在此处输入图像描述

正如其他人指出的那样,指导平面的方法(例如,directToX)可能超出了平面的“主题”,但它们并没有明显错误。这些元素(职责)在另一个类中可能会更好,比如AirTrafficController. 实际上,飞机并不能决定它们如何飞行。他们的飞行员必须听从地面的指示。

我认为 Thread 的东西 ( start, run) 绝对不在飞机的主题范围内。这些方法几乎不使用任何属于 a 的东西Plane(它们分散了主题的注意力)。您可以使用匿名内部类来处理来自的线程中的处理,main并且您的 Plane 将更加可重用(和凝聚力)。

一个有凝聚力的对象会触及它所建模的事物的本质。这意味着它更有可能在另一个应用程序(甚至另一种 OO 语言)中轻松重用。任何开始超出您概念的真正主题的东西都可能会使在另一个应用程序中重用该概念变得更加困难。在另一个应用程序中,“分心”不再有意义。

如果您正在开发一个Kamikaze项目(您只想让它工作而不关心重用),那么忘记内聚(和其他设计元素)是完全可以的。设计选择是权衡。您可以重构您的 Plane 类以使其更具凝聚力,但如果您从未在另一个应用程序中重用它,您可能会浪费您的时间。另一方面,设计是一个学习的过程。即使您为一个应用程序过度设计了某些东西,您也可能为下一个应用程序学到了一些东西。

最后,所有设计方面都难以量化,因此标准很少。众所周知,一些公司在其开发过程中为LCOM等指标设置(任意)标准。我读过团队标准,如果一个类对 LCOM 的价值不高,那么它必须被重构,直到它的价值变得足够低(它的凝聚力更强)。不幸的是,LCOM 可能是一个不好的凝聚力衡量标准(尤其是在有很多 get/set 方法的类中)

于 2014-05-01T12:58:48.433 回答
1

没有关于对象内聚的 java 标准。(我不重复 duffymo 的建议,我完全同意)。

当您详细说明映射现实世界的对象模型时,要记住的一件事是尝试让一个类映射现实世界的一个概念

作为说明,在您的示例代码中,您至少有 2 个不同的概念:Plane因此Flight您可以将它们拆分为 2 个单独的类,它们之间具有一对多的关系。

于 2014-04-30T14:17:39.113 回答