对象凝聚力的Java标准是什么?对象中的太多信息是否意味着糟糕的设计? - 见例子

时间:2014-04-30 13:57:45

标签: java object design-patterns cohesion

我正在创建一个模拟机场着陆系统的项目。我有一个plane对象,它存储了将plane排序到queue并存储在数据库中所需的所有信息。所有重要信息都包含在对象中,但我还包括每个平面的坐标。我的问题是它可能不被认为具有凝聚力,因为每个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 个答案:

答案 0 :(得分:5)

凝聚力是一个困难的概念。尽管其他答案是轻率的反应,但真正的答案在很大程度上取决于您的系统的功能和工作方式。例如,让我们检查一下队列机制。在您的系统中,飞机在队列中是否会以不同的方式响应命令?如果是这样,那么它在队列中的事实应该是飞机的组成部分。在不同的队列中它会有不同的反应吗?如果是这样,那么队列本身应该是飞机的组成部分。然而,如果由于飞机在队列中而机场响应不同,那么机场应控制队列,飞机应该对此一无所知 - 它应该只是由机场(或由机场)给出飞行路径。在机场的控制塔,取决于你的模型的分辨率。)

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

答案 1 :(得分:4)

没有&#34;标准&#34;用于Java或任何其他语言。

  

我有一架飞机&#34;存储我需要的所有信息的对象   将平面排序到队列中并传递给数据库。一切至关重要   信息包含在对象中,但我也包含了   每架飞机的坐标。

我认为您的Plane模型对象做得太多了。

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

队列是内存中的集合还是消息队列?对你有用吗?

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

您的模型可能如下所示:

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

答案 2 :(得分:2)

凝聚力可以​​定义为degree to which the elements of a module belong together

可视化它有帮助。想象一下类和方法的属性。如果您的类具有内聚性,则意味着方法将使用许多属性,相反,许多方法将使用这些属性。这就是&#34;粘在一起&#34;凝聚力的概念。我喜欢来自NDepend's placemat的以下可视化:

enter image description here

正如其他人所指出的那样,指导飞机的方法(例如, directToX )可能超出了&#34;主题&#34;什么是飞机,但他们并不是公然错误的。那些元素(责任)可能在另一个类中更好,比如AirTrafficController。实际上,飞机不会决定他们如何飞行。他们的飞行员必须遵循地面指示。

我认为线程的东西(startrun)绝对不在飞机的主题之内。这些方法几乎不使用属于Plane的任何内容(它们是分散注意力的主题)。你可以从main use an anonymous inner class to handle the processing in a thread和你的飞机更加可重复使用(并且具有凝聚力)。

一个有凝聚力的对象会进入它所建模的东西的本质。这意味着它可能更容易在另一个应用程序(甚至另一种OO语言)中重复使用。任何开始超出概念真实主题的东西都可能使得在另一个应用程序中重用这个概念变得更加困难。 &#34;分心&#34;在另一个应用程序中不再有意义。

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

最后,所有设计方面都难以量化,因此标准很少。众所周知,一些公司在其开发过程中为LCOM等指标设定(任意)标准。我已经读过团队标准,说如果一个类对LCOM有不好的价值,那么它必须重构,直到它的值足够低(它的凝聚力更强)。不幸的是,LCOM can be a bad measure of cohesion (especially in classes that have lots of get/set methods)

答案 3 :(得分:1)

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

当您详细描述映射现实世界的对象模型时,要记住的一件事是尝试使用一个类映射一个真实世界的概念

举例来说,在您的示例代码中,您至少有两个不同的概念:PlaneFlight,因此您可以将它们分成两个独立的类,它们之间具有一对多的关系