Каков стандарт Java для связности объектов? Означает ли слишком много информации в объекте плохой дизайн? - см. пример

Я создаю проект, который моделирует систему посадки в аэропорту. У меня есть объект 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();
    }

}
}

person Zanarou    schedule 30.04.2014    source источник


Ответы (4)


Сплоченность — сложное понятие. Несмотря на легкомысленные ответы другого ответа, истинный ответ во многом зависит от того, что делает ваша система и как она работает. Например, давайте рассмотрим механизм очереди. В вашей системе самолет по-разному реагирует на команды в очереди? Если так, то тот факт, что он стоит в очереди, должен быть неотъемлемой частью самолета. Отвечает ли он по-разному, когда находится в разных очередях? Если это так, то сама очередь должна быть неотъемлемой частью плоскости. Если, однако, аэропорт реагирует иначе из-за того, что самолет находится в очереди, то аэропорт должен контролировать очередь, а самолет ничего об этом не должен знать — ему просто должен быть предоставлен маршрут полета аэропортом (или диспетчерская вышка в аэропорту, в зависимости от разрешения вашей модели).

Сплоченность — не единственная ваша проблема. Инкапсуляция также является большой проблемой. Вы позволяете другим объектам иметь доступ к вашему внутреннему состоянию. Чтобы смоделировать это полностью объектно-ориентированным способом, вам следует рассмотреть возможность использования шаблона CQRS. Если вы также рассмотрите методы DDD (Domain Driven Design) и начнете с определения ваших ограниченных контекстов и агрегированных маршрутов, вы с большей вероятностью получите правильный дизайн.

person Software Engineer    schedule 30.04.2014
comment
Легкомысленный? Я не вижу здесь ничего легкомысленного. - person duffymo; 30.04.2014
comment
@duffymo - не обижайся. Я думаю, вы ответили на вопрос, не научив ОП, как самому ответить на него. Я не думаю, что ваш ответ был неправильным, но я не думаю, что он был очень полезным. - person Software Engineer; 30.04.2014
comment
Не тяжело. Я думаю, что немного поучил — я фактически разместил код, чтобы показать, что я предложил. Возможно, я не цитировал Эрика Эванса, но рекомендацию отделить очередь исходил от меня. - person duffymo; 30.04.2014

Для Java или любого другого языка не существует «стандарта».

У меня есть объект «самолет», в котором хранится вся информация, необходимая для сортировки самолета в очередь и передачи в базу данных. Вся важная информация включена в объект, но я также включил координаты для каждого самолета.

Я думаю, что ваш объект модели 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);
}
person duffymo    schedule 30.04.2014
comment
Я согласен, что объект не должен видеть, в очереди он или нет. Я изменю это. Что вы подразумеваете под объектом, сохраняющим себя? и отделив настойчивость? Благодарность - person Zanarou; 30.04.2014

Связность можно определить как степень, в которой элементы модуля связаны друг с другом.

Визуализация помогает. Представьте себе атрибуты класса и методы. Если ваш класс связан, это означает, что методы будут использовать многие атрибуты, и наоборот, атрибуты будут использоваться многими методами. Это понятие «склеивания» сплоченности. Мне нравится следующая визуализация из салфетки NDepend:

введите здесь описание изображения

Как указывали другие, методы, которые управляют плоскостью (например, directToX), возможно, не относятся к «теме» того, что такое плоскость, но они не являются явно неправильными. Эти элементы (обязанности) могут быть лучше в другом классе, скажем, AirTrafficController. На самом деле самолеты мало что решают, как они летают. Их пилоты должны следовать инструкциям с земли.

Я бы сказал, что материал Thread (start, run) определенно выходит за рамки темы Plane. Эти методы почти не используют ничего, что является частью Plane (они отвлекают от его темы). Вы можете использовать анонимный внутренний класс для обработки обработки в потоке из main, и ваш Plane будет еще более пригодным для повторного использования ( и сплоченный).

Связный объект достигает сущности того, что он моделирует. Это означает, что его, скорее всего, можно будет легко повторно использовать в другом приложении (или даже на другом объектно-ориентированном языке). Все, что начинает выползать за пределы истинной темы вашей концепции, скорее всего, затруднит повторное использование концепции в другом приложении. «Отвлекающие факторы» больше не имеют смысла в другом приложении.

Если вы разрабатываете проект Kamikaze (в котором вы просто хотите заставить его работать и не заботитесь о повторном использовании), совершенно нормально забыть о связности (и других элементах дизайна). Выбор дизайна — это компромисс. Вы можете реорганизовать свой класс Plane, чтобы сделать его более целостным, но если вы никогда не используете его повторно в другом приложении, возможно, вы зря потратили время. С другой стороны, дизайн — это процесс обучения; даже если вы перепроектируете что-то для одного приложения, вы, возможно, выучите что-то для следующего.

Наконец, все аспекты дизайна трудно поддаются количественной оценке, и поэтому существует мало стандартов. Известно, что некоторые компании устанавливают (произвольные) стандарты для показателей, таких как LCOM. в процессе разработки. Я читал о командных стандартах, в которых говорится, что если класс имеет плохую ценность для LCOM, то он должен подвергаться рефакторингу до тех пор, пока его ценность не станет достаточно низкой (его сплоченность станет сильнее). К сожалению, LCOM может быть плохой мерой связности (особенно в классах с большим количеством методов get/set).

person Fuhrmanator    schedule 01.05.2014

Не существует стандарта Java в отношении связности объектов. (советы duffymo не повторяю, со всеми согласен).

Когда вы разрабатываете объектную модель, отображающую реальный мир, следует помнить одну вещь: постарайтесь иметь один класс, отображающий одно понятие реального мира.

В качестве иллюстрации, в вашем примере кода у вас есть как минимум 2 различных понятия: Plane и Flight, и поэтому вы можете разделить их на 2 отдельных класса с отношением "один ко многим" между ними.

person ben75    schedule 30.04.2014