2015-12-08 1 views
3

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

abstract class AbstractCatalog<T extends AbstractCatalog<T>> { 

    public abstract T getOld(); 
} 

class Catalog1 extends AbstractCatalog<Catalog1> { 

    @Override 
    public Catalog1 getOld() { ... } 
} 

class Catalog2 extends AbstractCatalog<Catalog2> { 

    @Override 
    public Catalog2 getOld() { ... } 
} 

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

Например,

abstract class AbstractCatalogHistory<C extends AbstractCatalog<C>, E extends AbstractHistoryEntry<C, E>> { 

    public abstract Set<E> getEntries(); 
} 

abstract class AbstractHistoryEntry<C extends AbstractCatalog<C>, E AbstractHistoryEntry<C, E>> { 

    public abstract E getPrior(); 
} 

class Cat1HistoryEntry extends AbstractHistoryEntry<Catalog1, Cat1HistoryEntry> { 

    @Override 
    public Cat1HistoryEntry getPrior() { ... } 
} 

class Cat2HistoryEntry extends AbstractHistoryEntry<Catalog2, Cat2HistoryEntry> { 

    @Override 
    public Cat2HistoryEntry getPrior() { ... } 
} 

class Catalog1History extends AbstractCatalogHistory<Catalog1, Cat1HistoryEntry> { 

    @Override 
    public Set<Cat1HistoryEntry> getEntries() { ... } 
} 

class Catalog2History extends AbstractCatalogHistory<Catalog2, Cat2HistoryEntry> { 

    @Override 
    public Set<Cat2HistoryEntry> getEntries() { ... } 
} 

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

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

Есть ли способ справиться с таким дженериком?

ответ

2

Ваш пример не делает его совершенно ясно, почему вам нужно иметь отдельные классы для Catalog1 и Catalog2, но давайте предположим, что это установлено.

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

class CatalogHistory<C extends AbstractCatalog<C>> { 
    public Set<HistoryEntry<C>> getEntries(); 
} 

class HistoryEntry<C extends AbstractCatalog<C>> { 
    public HistoryEntry<C> getPrior(); 
} 

Но что, если вы на самом деле делать разные вещи в, например, Cat1HistoryEntry и Cat2HistoryEntry, поэтому вам нужны отдельные классы? В этом случае вы можете явно не обойти, имеющие абстрактный базовый класс и две конкретные реализации, но я не вижу необходимости введения общего типа, а затем прибить их к конкретным типам, как вы делаете:

abstract class AbstractHistoryEntry<C extends AbstractCatalog<C>> { 

    public abstract AbstractHistoryEntry<C> getPrior(); 
} 

class Cat1HistoryEntry extends AbstractHistoryEntry<Catalog1> { 

    @Override 
    public Cat1HistoryEntry getPrior() { ... } 
} 

class Cat2HistoryEntry extends AbstractHistoryEntry<Catalog2> { 

    @Override 
    public Cat2HistoryEntry getPrior() { ... } 
} 

Там здесь происходит несколько вещей. Сначала рассмотрим AbstractHistoryEntry. Если у вас есть один из них, вы работаете на общем уровне и не должны волноваться, что getPrior возвращает тот или иной конкретный подтип - все, что вам нужно знать, это то, что он возвращает другой объект AbstractHistoryEntry, ссылающийся на тот же каталог.

Если у вас есть конкретная ссылка Cat1HistoryEntry, тем не менее, вы все равно можете получить полную безопасность при получении другого Cat1HistoryEntry из getPrior благодаря ковариации возвращаемых типов в Java.

Теперь это становится немного сложнее - Давайте попробуем вытащить тот же трюк с AbstractCatalogHistory:

abstract class AbstractCatalogHistory<C extends AbstractCatalog<C>> { 

    public abstract Set<? extends AbstractHistoryEntry<C>> getEntries(); 
} 

class Catalog1History extends AbstractCatalogHistory<Catalog1> { 

    @Override 
    public Set<Cat1HistoryEntry> getEntries() { ... } 
} 

class Catalog2History extends AbstractCatalogHistory<Catalog2> { 

    @Override 
    public Set<Cat2HistoryEntry> getEntries() { ... } 
} 

Как вы можете видеть, как конкретные подклассы по-прежнему возвращает набор конкретных типов Cat1HistoryEntry и Cat2HistoryEntry. Абстрактный базовый тип теперь должен выражать общий супертип для этих множеств, чтобы вы могли работать с результатом в общем виде. Это делается путем введения ковариации.

Сеттеры

сеттеры усложнять Дело в немного. В принципе, если у вас есть общий контейнер/коллекция, например, List<T> или AbstractCatalogHistory<C>, и вы хотите разрешить как добавление, так и получение элементов, вы больше не можете иметь отклонения в типе элемента, если хотите, чтобы тип безопасности.

Например, если у вас сеттер в AbstractCatalogHistory<C>, которая позволяет добавлять любые AbstractHistoryEntry<C> элементов в историю, то у вас есть проблемы, потому что если ваш AbstractCatalogHistory<C> фактически Catalog1History тогда вы только хотите Cat1HistoryEntry пунктов в там!

Это та же проблема, как и в общих списках: A List<Cat> не List<Mammal>, потому что вы можете добавить слон в List<Mammal>, но вы не должны быть в состоянии добавить слон в List<Cat>.

Если вы настаиваете, что история для Catalog1 должна состоять только из Cat1HistoryEntry пунктов, то решение было бы только добавить сеттер к Catalog1History, и ни к AbstractCatalogHistory<C>. Таким образом, общие классы будут использоваться только для чтения истории, а не для ее записи.

Однако, возвращаясь к началу моего ответа: если вам действительно не нужны двойные конкретные классы, решение остается очень простым. К сожалению, вы все еще не объяснили, почему или вам нужны. Если все, что вам действительно нужно, это различие Catalog1/Catalog2, и на самом деле вам не нужна другая реализация, например. Cat1HistoryEntry и Cat2HistoryEntry, то следующее должно хватить:

class CatalogHistory<C extends AbstractCatalog<C>> { 
    public Set<HistoryEntry<C>> getEntries(); 
    public void addEntry(HistoryEntry<C> entry); 
} 

class HistoryEntry<C extends AbstractCatalog<C>> { 
    public HistoryEntry<C> getPrior(); 
    public void setPrior(HistoryEntry<C> prior); 
} 
+0

Medo42, спасибо! Единственное, что я не могу понять, - как реализовать сеттеры в безопасном типе. –

Смежные вопросы