2017-01-01 2 views
6

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

Сервисный интерфейс что-то вроде этого:

public interface Service {...} 

Затем базовый класс, который добавляет общую ссылку на интерфейс Service, где T расширяет службу, но затем общий базовый класс также реализует интерфейс. Что-то вроде этого:

public class ServiceBase<T extends Service> implements Service {...} 

Зачем вам это нужно? Я замечаю, что на практике расширение ServiceBase всегда использует то же имя класса, что и T, как тот, который объявляется; поэтому здесь нет никакой волшебной полиморфной выгоды. Что-то вроде этого:

public class MyService extends ServiceBase<MyService> {...} 

и класс MyService никогда не контейнер для общего (например, я не считаю, что это сигнализация какой-то самостоятельной содержащий список, где MyService может содержать список MyServices).

Любые идеи/мысли о том, почему кто-то это сделает?

+1

Возможно, если бы вы могли опубликовать больше из тел этих классов, было бы легче увидеть обоснование этого. – manouti

+0

Делегирует 'ServiceBase'' Service' в каком-то экземпляре 'T'? – weston

+0

Это похоже на вопрос, почему класс Enum объявлен следующим образом: Enum > ': [здесь] (http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106) Если ServiceBase сделал что-то вроде реализации 'Comparable ', который требует подклассов, это может иметь смысл. – Calculator

ответ

4

Зачем вам это нужно? Я замечаю, что на практике расширение ServiceBase всегда использует то же имя класса, что и T, которое объявляется ; так что на самом деле нет никакого волшебного полиморфного преимущества .

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

В вашем случае предположим, что класс ServiceBase является абстрактным и имеет метод process(), который необходимо создать при каждом вызове новый экземпляр конкретного класса, который мы объявляем в параметризованном виде.
Мы называем этот абстрактный метод createService().

Без использования дженериков мы можем объявить способ public abstract ServiceBase createService().

ServiceBase без дженериков

public abstract class ServiceBase implements Service { 

    public abstract ServiceBase createService(); 

    @Override 
    public void process() { 
     createService().process(); 
    } 

} 

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

Например, следующий дочерний класс будет скомпилирован, потому что мы не вынуждены изменять возвращаемый тип createService() на текущий объявленный тип.

MyService без дженериков

public class MyService extends ServiceBase { 

    @Override 
    public ServiceBase createService() {  
     return new AnotherService(); 
    } 

} 

Но если я использую генериков в базовом классе:

ServiceBase с дженериков

public abstract class ServiceBase<T extends Service> implements Service { 

    public abstract T createService(); 

    @Override 
    public void process() { 
     createService().process(); 
    } 

} 

Класс бетона не имеет выбора, это принудительно изменить возвращаемый тип createService() с номиналом объявлен.

MyService дженериков

public class MyService extends ServiceBase<MyService> { 

    @Override 
    public MyService createService() { 
     return new MyService(); 
    } 

} 
0

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

Класс ServiceBase реализует Service. Это говорит о том, что он реализует контракт (методы) Service (точнее, подклассы его могут выступать в качестве реализации).

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

Путь я прочитал следующее объявление

public class ServiceBase<T extends Service> implements Service {...} 

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

Эти два аспекта полностью независимы.

2

я сделал пример, используя ваши объявления классов и интерфейсов (кроме того, что я сделал ServiceBase абстрактную), который должен проиллюстрировать использование родовых типов:

public interface Service { 
    int configure(String cmd); 
} 

public abstract class ServiceBase<T extends Service> implements Service { 
    private ServiceManager manager;  

    public ServiceBase(ServiceManager manager){ 
     this.manager = manager; 
    } 

    public final void synchronize(T otherService){ 
     manager.configureEnvironment(otherService.configure("syncDest"), configure("syncSrc")); 
     synchronizeTo(otherService); 
    } 

    protected abstract void synchronizeTo(T otherService); 
} 

public class ProducerService extends ServiceBase<ConsumerService> { 

    public ProducerService(ServiceManager manager) { 
     super(manager); 
    } 

    @Override 
    protected void synchronizeTo(ConsumerService otherService) { 
     /* specific code for synchronization with consumer service*/ 
    }   

    @Override 
    public int configure(String cmd) { ... } 
} 

public class ConsumerService extends ServiceBase<ProducerService> { 

    public ConsumerService(ServiceManager manager) { 
     super(manager); 
    } 

    @Override 
    protected void synchronizeTo(ProducerService otherService) { 
     /* specific code for synchronization with producer service */   
    } 

    @Override 
    public int configure(String cmd) { ... } 

} 

Представьте, что у нас есть службы, управляемые ServiceManager, которые могут настраивать среду сервисов, чтобы они были готовы к синхронизации друг с другом. То, как интерпретируется команда configure, зависит от конкретной службы. Поэтому в нашем интерфейсе содержится заявка configure().

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

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

Теперь дженерики входят в игру. ServiceBase также не знает, к какому типу обслуживания он может синхронизироваться. Он также должен делегировать это решение своему подклассу.Он может сделать это, используя конструкцию T extends Service

Учитывая эту структуру теперь представьте себе два конкретных подклассов ServiceBase: ProducerService и ConsumerService; Потребительское обслуживание может только синхронизироваться с услугой производителя и наоборот. Поэтому два класса указывают в своем объявлении ServiceBase<ConsumerService> соответственно ServiceBase<ProducerService>.

Заключение

Так же, как абстрактные методы могут быть использованы суперкласса делегировать реализацию функциональности их подклассов, параметры универсального типа могут быть использованы суперкласса делегировать «реализацию» заполнителей типа их подклассов ,

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