2015-07-08 13 views
1

В моем проекте у меня есть абстрактный класс Model, который определил метод getId() и другие классы, расширяющие модель. Для того, чтобы иметь возможность каждый класса определить тип поля ID, как это необходимо, я сделал класс Model родовой:Как создать общий класс, который имеет общий класс как тип?

public abstract class Model<T> { 
    public abstract T getId(); 
} 

Так что я могу продлить его сейчас:

public class Person extends Model<String> { 
    @Override 
    public String getId() { 
     return getName(); 
    } 
} 

То есть ОК.

Теперь я хотел бы создать класс, который действует как набор изменений, где я могу управлять обновленными и удаленными моделями. В этом классе я хотел бы управлять обновленными моделями с использованием карты, являющейся идентификатором модели, ключевым и самой моделью. (У набора изменений будет только один тип, например, ChangeSet будет содержать только человека, ChangeSet будет содержать только автомобили и т. Д.).

Я хотел бы создать его экземпляр, как это:

ChangeSet<Person> changeSet = new ChangeSet<Person>(); 

Это имеет смысл для меня, что можно сделать вывод о том, что идентификатор будет строка, потому что Person расширяет модель.

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

public class ChangeSet<M extends Model, T>{ 
    private final Map<T, M> updated = new HashMap<>(); 
... 
} 

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

public class ChangeSet<M extends Model<T>, T>{ 
    private final Map<T, M> updated = new HashMap<>(); 
... 
} 

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

ChangeSet<String, Person> changeSet = new ChangeSet<Person<String>, String>(); 

Это не так изящно и дублируется «String» неловко.

Есть ли способ, которым я могу получить (рядом) мою желаемую декларацию?

+0

какая версия JDK вы используете? –

+0

Я использую JDK 1.7 – JSBach

+0

С 1.7, я думаю, что это самое ближайшее, что мы можем получить. В Java 8 вы сможете записать это как «ChangeSet changeSet = new ChangeSet ();' без дублирования. Который может быть далее записан как «ChangeSet changeSet = new ChangeSet <>();' с алмазным оператором. Я думаю, что это возможно с помощью ввода типа - https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html –

ответ

2

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

Вот что я предлагаю - Я на самом деле реализовать это, и это работает прекрасно:

Обновление на основе комментариев от OP

Интерфейс Модель может быть «обобщен», чтобы ограничить типы возвращаемых:

package org.example; 

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public interface Model<T extends Identifier> 
{ 
    T getIdentifier(); 
} 

Реализовать класс модели, которая использует конкретный тип идентификатора:

package org.example; 



/** 
* Created by prahaladd on 08/07/15. 
*/ 
public class Person implements Model<StringIdentifier> 
{ 

    private final String name; 
    private final String id; 

    public Person(String id, String name) 
    { 
     this.id = id; 
     this.name = name; 
    } 
    @Override 
    public StringIdentifier getIdentifier() 
    { 
     return new StringIdentifier(id); 
    } 

    public String getName() 
    { 
     return name; 
    } 
} 

Внедрение ChangeSet теперь немного изменяет для имитации интерфейса карты, как показано ниже. Это в-то, в настоящее время занимает в типе идентификаторов, которые будут храниться в качестве ключей:

package org.example; 

import java.util.Map; 

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public class ChangeSet<T extends Identifier, M extends Model<T>> 
{ 
    //Refer to PECS - http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs 

    private Map<? super Identifier, M> changeMap; 

    public void addChange(M element) 
    { 
     changeMap.put(element.getIdentifier(),element); 
    } 

    public M getChangedElementForId(T id) 
    { 
     return changeMap.get(id); 
    } 
} 

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

package org.example; 

public class Main { 

    public static void main(String[] args) 
    { 
     Person p1 = new Person("1", "Tom"); 
     Person p2 = new Person("2", "Jerry"); 

     //change set is instantiated without any redundant generic parameters 
     ChangeSet<StringIdentifier, Person> changes = new ChangeSet<StringIdentifier,Person>(); 

     //assume that there were some changes and you want to add them to the changeset. 
     changes.addChange(p1); 
     changes.addChange(p2); 

     //retrieve element from the changeset for an id 

     p1= changes.getChangedElementForId(new StringIdentifier("1")); 
     p2 = changes.getChangedElementForId(new StringIdentifier("2")); 


    } 
} 

Альтернативное решение

Во-первых - определить интерфейс, который инкапсулирует идентификатор. Это не перебор; при условии, что у вас есть различные типы идентификаторов с помощью интерфейса, чтобы определить контракт на идентификатор будет идти долгий путь, чтобы сделать ваш код чистым и расширяемым:

package org.example; 

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public interface Identifier<T> 
{ 
    T getIdentifier(); 
} 

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

package org.example; 

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public class StringIdentifier implements Identifier<String> 
{ 

    private final String identifier; 

    public StringIdentifier(String id) 
    { 
     identifier = id; 
    } 

    @Override 
    public String getIdentifier() 
    { 
     return "someId"; 
    } 
} 

Теперь определим интерфейс модели. В идеале интерфейс модели не должен иметь дело с каким-либо идентификационным типом, он должен просто знать, что он должен вернуть идентификатор (как и ваш прецедент).

package org.example; 

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public interface Model 
{ 
    Identifier getIdentifier(); 
} 

Теперь предоставьте реализацию интерфейса модели. Напр. ниже - класс Person, упомянутый в вашем запросе:

package org.example;

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public class Person implements Model 
{ 

    private final String name; 
    private final String id; 

    public Person(String id, String name) 
    { 
     this.id = id; 
     this.name = name; 
    } 
    @Override 
    public Identifier getIdentifier() 
    { 
     return new StringIdentifier(id); 
    } 

    public String getName() 
    { 
     return name; 
    } 
} 

Теперь определите ChangeSet. ChangeSet должен знать только, что он хранит сопоставление между объектами ID и соответствующей моделью. Он действительно не знает о типе объектов ID. Это делает класс ChangeSet чрезвычайно гибким, чтобы даже поддерживать гетерогенную коллекцию в дополнение к однородным, которые вы хотите.

package org.example; 

import java.util.Map; 

/** 
* Created by prahaladd on 08/07/15. 
*/ 
public class ChangeSet<M extends Model> 
{ 
    //Refer to PECS - http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs 

    private Map<? super Identifier, M> changeMap; 

    private Class identifierType; 

    public void addChange(M element) 
    { 
     //prahaladd - update : save the identifier type for a later check. 
     if(identifierType != null) 
     { 
      identifierType = element.getIdentifier.getClass(); 
     } 
     changeMap.put(element.getIdentifier(),element); 
    } 

    public M getChangedElementForId(Identifier id) 
    { 
     //prahaladd updated - verify that the type of the passed in id 
     //is the same as that of the changeset identifier type. 
     if(!id.getClass().equals(identifierType)) 
     { 
       throw new IllegalArgumentException(); 
     } 
     return changeMap.get(id); 
    } 
} 

Теперь тяжелая работа окупается. Взгляните на приведенную ниже клиентскую реализацию:

package org.example; 

public class Main { 

    public static void main(String[] args) 
    { 
     Person p1 = new Person("1", "Tom"); 
     Person p2 = new Person("2", "Jerry"); 

     ChangeSet<Person> changes = new ChangeSet<Person>(); 

     //assume that there were some changes and you want to add them to the changeset. 
     changes.addChange(p1); 
     changes.addChange(p2); 

     //retrieve element from the changeset for an id 

     p1= changes.getChangedElementForId(new StringIdentifier("1")); 
     p2 = changes.getChangedElementForId(new StringIdentifier("2")); 
    } 
} 

Точно, как вы предполагали! Как вы можете видеть, нет ничего необычного в том, что здесь было сделано. Простые объектно-ориентированные концепции и продуманная комбинация интерфейса и дженериков.

Надеюсь, что это поможет!

+0

Hi Prahalad, очень продуманный ответ и отличное объяснение. Но ваша модель класса стоит дорого. Она вводит идентификатор класса-оболочки для идентификатора и дополнительный класс реализации (StringIdentifier). Для вызова ChangeSet # getChangedElementForId() вам нужен временный объект Идентификатор. И это не тип: учитывая переменную с именем change of type ChangeSet , вы можете вызвать change.getChangedElementForId (новый IntIdentifier (1)), который из курение s не имеет смысла (всегда возвращает null), но также не замечен компилятором (по желанию автора вопроса). – wero

+0

@ Prahalad, это очень приятное решение, есть ли у вас какие-либо предложения по поводу проблемы, вызванной «find by id»? – JSBach

+0

@JSBach Я предоставляю обновление для своего решения и сохраню исходный ответ ради альтернативной ссылки. Чтобы ответить на комментарий wero, не очень хороший способ сделать это - ввести некоторый тип проверки в классе ChangeSet для getChangedElementForId, который проверяет, что переданный в идентификаторе тот же тип, что и идентификатор, который ожидает карта (обновлено мое предыдущее решение с комментарием). Набор изменений может сделать это, не зная основного типа. Тем не менее, обновленное решение справляется с этим естественно, и я более склонен к нему –

0

Если обновленная карта деталь реализации набора изменений, вы можете просто написать:

public class ChangeSet<M extends Model<?>> { 
    private final Map<Object,M> updated = new HashMap<>(); 

    public void add(M model) { 
     updates.put(model.getId(), model); 
    } 
} 

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

+0

Это именно та проблема: у меня есть метод addModel, который проверяет, существует ли модель уже и «сливается», если это произойдет. Это то, что вызвало проблему в первую очередь :( – JSBach

+0

Каковы параметры вашего метода addModel? – wero

+0

Извините за задержку ... public void addModel (окончательная модель M) { if (! Map.contains (model .getId())) { ..... } – JSBach

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