Наследование и инкапсуляция классов коллекций в Java

StackOverflow https://stackoverflow.com/questions/1068410

Вопрос

Предположим, у меня есть следующие типы данных:

class Customer {
  String id; // unique
  OtherCustData someOtherData;
}

class Service {
  String url; // unique
  OtherServiceData someOtherData;
}

class LastConnection {
  Date date;
  OtherConnData someOtherData; // like request or response
}

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

Map<Customer, Map<Service, LastConnection>> lastConnections;

Или, чтобы иметь возможность искать по идентификаторам и не нужно было писать все equal() и hashCode():

Map<String, Map<String, LastConnection>> lastConnections;

Теперь я мог получить доступ к данным LastConnection с помощью

LastConnection connection = lastConnections.get(custId).get(srvUrl);

Все это кажется уродливым, особенно из-за того, что мне приходится передавать это в качестве параметров десяткам методов, ожидающих map of maps of LastConnections, поэтому я подумываю о создании своих собственных классов, которые выглядели бы примерно так:

class CustomerConnections extends HashMap<String, LastConnection> {
}

class AllConnections extends HashMap<String, CustomerConnections> {
    public LastConnection get(String custId, String srvUrl) {
        return get(custId).get(srvUrl);
    }
}

Хорошо, я уже узнал, что наследование - это 3v1l, так что давайте попробуем композицию:

class CustomerConnections {
    Map<String, LastConnection> customerConnections;
    LastConnection get(String srvUrl) { 
        return customerConnections.get(srvUrl);
    }
    ... // all other needed operations;
}

class AllConnections {
    Map<String, CustomerConnections> allConnections;
    public LastConnection get(String custId, String srvUrl) {
        return get(custId).get(srvUrl);
    }
    public CustomerConnection get(String custId) {
        return allConnections.get(custId);
    }
    ... // all other needed operations;
}

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

Это было полезно?

Решение

Создание классов, которые ничего не делают кроме расширения уже существующих коллекций, похоже на умножение сущностей сверх необходимости

Я бы изменил extend на encapsulate.Вы скрываете детали того, как хранится эта информация.Клиентам вашего класса не обязательно знать, как вы предоставляете им историю подключений клиентов.Я думаю, что это хорошая идея, поскольку вы можете изменить базовую модель без того, чтобы клиенты api меняли свой код.

но это сделало бы мой код более понятным

Это здорово и является веской причиной для того, чтобы сделать это.YourClass.getCustomerConnection(cId) намного понятнее, чем yourCollection.get(id).get(id).getConnection().Вам нужно облегчить жизнь людям, использующим этот код, даже если вы и есть этот человек.

(Особенно когда есть следующие уровни - например, Карта всех подключений по месяцам и так далее)

Хорошо, тогда вы планируете заранее и делаете свой код расширяемым.Что является хорошей практикой OO.По моему мнению, сотрясение мозга, которого вы достигли самостоятельно, - это то, что я бы сделал.

Другие советы

Я бы создал специальный объект для хранения этой информации.То, что вы создаете, - это менеджер объект, а не простая коллекция.

Я не стал бы сделайте его производным от Map или другого хорошо известного класса коллекции, поскольку семантика того, как вы храните эту информацию, может измениться в будущем.

Вместо этого реализуйте класс, который связывает воедино клиента и его соединение, и внутри этого класса используйте соответствующий класс коллекции (который вы можете изменить позже, не затрагивая интерфейс и остальную часть вашего кода)

Ваш класс customer / connection manager - это нечто большее, чем простой контейнер.Он может хранить метаданные (например,когда были установлены эти отношения).Он может выполнять поиск по подключениям, указанным в информации о клиенте (если вам требуется).Он может обрабатывать дубликаты так, как вам требуется, а не так, как их обрабатывает базовый класс коллекции и т.д.и т.д.Вы можете легко включить отладку / ведение журнала / мониторинг производительности, чтобы легко понять, что происходит.

Я думаю, что вам также следует подумать о том, как будут использоваться коллекции и как будут получены данные:

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

Как вы получаете и сохраняете свои данные?Если он хранится в базе данных, то, возможно, вы можете использовать SQL для выбора LastConnections по клиенту и / или сервису и / или месяцу, вместо того, чтобы самостоятельно поддерживать структуры данных (и просто возвращать простые списки или карты подключений или количество подключений).Или, может быть, вы не хотите запускать запрос для каждого запроса, поэтому вам нужно сохранить всю структуру данных в памяти.

Однако инкапсуляция, как правило, полезна, особенно потому, что она может помочь вам придерживаться Закон Деметры - возможно, вы сможете перенести некоторые операции, которые вы будете выполнять с коллекциями, обратно в класс AllConnections (который фактически является DAO).Это часто может помочь в модульном тестировании.

Кроме того, почему расширение HashMap считается здесь злом, учитывая, что вы хотите добавить только тривиальный вспомогательный метод?В вашем коде для AllConnections AllConnections всегда будет вести себя так же, как HashMap - он является полиморфно заменяемым.Конечно, вы потенциально блокируете себя использованием HashMap (а не TreeMap и т.д.), Но это, вероятно, не имеет значения, поскольку оно имеет те же общедоступные методы, что и Map.Однако, действительно ли вы хотели бы это сделать, действительно зависит от того, как вы собираетесь использовать коллекции - я просто не думаю, что вам следует автоматически сбрасывать со счетов наследование реализации как всегда плохо (просто так обычно и бывает!)

class AllConnections extends HashMap<String, CustomerConnections> {
    public LastConnection get(String custId, String srvUrl) {
        return get(custId).get(srvUrl);
    }
}

Почему бы не использовать custId+"#"+srvurl как ключ к простому Map<String, LastConnection>?

Или использовать Кортеж или соедините класс с ключом, который содержит два идентификатора и реализует hashCode() и equals() - самый "чистый" раствор OO.

Почему вы поддерживаете отношения между объектами за пределами этих объектов?

Я бы предложил что-то вроде следующего:

public class Customer 
{
  public List<LastConnection> getConnectionHistory() 
  {
    ... 
  }

  public List<LastConnection> getConnectionHistory(Service service) 
  {
    ...
  }

  public List<LastConnection> getConnectionHistory(Date since) 
  {
    ...
  }
}

public class LastConnection
{
  public Date getConnectionTime() 
  {
    ...
  }

  public Service getService()
  {
    ...
  }
}

Затем вы проходите мимо List<Customer> к методам, которые используют эту информацию.

Вы могли бы создать класс, который орудия труда Map<K,V>, и внутренне делегировать контейнерную карту:

class CustomerConnections implements Map<String,LastConnection> {
    private Map<String, LastConnection> customerConnections;

    @Override
    public LastConnection get(Object srvUrl) { 
        return customerConnections.get(srvUrl);
    }
    // all other needed operations;
}

Самое приятное в таком подходе то, что вы можете передавать Map который имеет стандартный, четко определенный контракт, но вы избегаете расширения библиотечных классов, что часто вызывает неодобрение.

Редактировать :Как указано ниже, у этого есть недостаток, заключающийся в том, что вам требуется реализовать множество методов, которые не делают ничего, кроме делегирования базовой коллекции

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top