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

Я отвечу на этот вопрос в Go, потому что в стандартной библиотеке Go нет большого количества коллекций.

Так как стек очень прост в реализации, я подумал, что попробую использовать два стека для создания двусторонней очереди. Чтобы лучше понять, как я пришел к своему ответу, я разделил реализацию на две части. Надеемся, что первую часть легче понять, но она неполна.

type IntQueue struct {
    front       []int
    back        []int
}

func (q *IntQueue) PushFront(v int) {
    q.front = append(q.front, v)
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[0]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        q.back = q.back[1:]
    }
}

func (q *IntQueue) PushBack(v int) {
    q.back = append(q.back, v)
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[0]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        q.front = q.front[1:]
    }
}

Это в основном два стека, где мы позволяем друг другу манипулировать основанием стеков. Я также использовал соглашения об именах STL, в которых традиционные операции стека push, pop, peek имеют префикс front / back независимо от того, ссылаются ли они на начало или конец очереди.

Проблема с приведенным выше кодом заключается в том, что он не очень эффективно использует память. На самом деле, он растет бесконечно, пока вы не исчерпаете пространство. Это действительно плохо. Решением этой проблемы является простое повторное использование нижней части пространства стека, когда это возможно. Мы должны ввести смещение, чтобы отследить это, так как срез в Go не может расти впереди после сокращения.

type IntQueue struct {
    front       []int
    frontOffset int
    back        []int
    backOffset  int
}

func (q *IntQueue) PushFront(v int) {
    if q.backOffset > 0 {
        i := q.backOffset - 1
        q.back[i] = v
        q.backOffset = i
    } else {
        q.front = append(q.front, v)
    }
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[q.backOffset]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        if len(q.back) > 0 {
            q.backOffset++
        } else {
            panic("Cannot pop front of empty queue.")
        }
    }
}

func (q *IntQueue) PushBack(v int) {
    if q.frontOffset > 0 {
        i := q.frontOffset - 1
        q.front[i] = v
        q.frontOffset = i
    } else {
        q.back = append(q.back, v)
    }
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[q.frontOffset]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        if len(q.front) > 0 {
            q.frontOffset++
        } else {
            panic("Cannot pop back of empty queue.")
        }
    }
}

Это много маленьких функций, но из 6 функций 3 из них являются просто зеркалами другой.

5
задан Jakub 1 July 2009 в 10:36
поделиться

6 ответов

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

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

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

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

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

Хорошо, тогда вы заблаговременное планирование и создание расширяемого кода. Это хорошая практика ОО.

5
ответ дан 14 December 2019 в 13:44
поделиться

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

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

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

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

1
ответ дан 14 December 2019 в 13:44
поделиться

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

Или использовать Tuple или класс Pair в качестве ключа, который содержит два идентификатора и реализует hashCode () и equals () - «самое чистое» объектно-ориентированное решение.

0
ответ дан 14 December 2019 в 13:44
поделиться

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

Я бы предложил примерно следующее:

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 методам, использующим эту информацию.

0
ответ дан 14 December 2019 в 13:44
поделиться

Вы можете создать класс, который реализует Map , и внутренне делегировать его карте контейнера:

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

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

РЕДАКТИРОВАТЬ : Как указано ниже, у этого есть обратная сторона, требующая реализации множества методов, которые не делают ничего, кроме делегирования базовой коллекции

-1
ответ дан 14 December 2019 в 13:44
поделиться

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

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

Как получить и сохранить данные? Если он хранится в базе данных, то, возможно, вы можете использовать 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);
    }
}
1
ответ дан 14 December 2019 в 13:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: