1

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

Допустим, у меня есть класс Base и класс расширения. Расширение класса занимает в экземпляре класса Base, а не наследование от него, если мы используем состав, и мы хотим, чтобы ввести наши зависимости:

public class Base 
{ 
    private ISomeDependency dependency; 

    public Base(ISomeDependency dependency) 
    { 
     this.dependency = dependency; 
    } 

    public ISomeDependency getDependency() 
    { 
     return dependency; 
    } 
} 
public class Extending 
{ 
    private Base base; 

    public Extending(Base base) 
    { 
     this.base = base; 
    } 
} 

Базовый класс принимает в некоторой зависимости, мы можем использовать в Расширение класса с помощью метода getDependency().

Однако, если мы хотим специализировать, какой подтип ISomeDependency нам нужен в расширяющемся классе.

Если бы мы использовали наследование, мы могли бы просто передать подтип в конструктор суперкласса, как так:

public class Extending extends Base 
{ 
    private ISomeDependencySubtype dependency; 

    public Extending(ISomeDependencySubtype dependency) 
    { 
     super(dependency); 
     this.dependency = dependency; 
    } 
} 

Но если мы используем состав, то это становится все труднее. Я думал несколько вариантов:

Имея метод инициализации вместо конструктора в базе

public class Extending 
{ 
    private ISomeDependencySubtype dependency; 
    private Base base; 

    public Extending(Base base, ISomeDependencySubtype dependency) 
    { 
     base.init(dependency); 
     this.base = base; 
     this.dependency = dependency; 
    } 
} 

Эта опция скрывает тот факт, что расширяющий класс использует метод Init() на базе класс, поэтому интерфейс становится неясным. При использовании класса Extended другой программист может предположить, что экземпляр Base должен иметь init(), вызываемый на нем, прежде чем он будет передан в экземпляр расширения.

Добавление общий аргумент

public class Base<T extends ISomeDependency> 
{ 
    private T dependency; 

    public Base(T dependency) 
    { 
     this.dependency = dependency; 
    } 

    public T getDependency() 
    { 
     return dependency; 
    } 
} 

public class Extending 
{ 
    private ISomeDependencySubtype dependency; 
    private Base<ISomeDependencySubtype> base; 

    public Extending(Base<ISomeDependencySubtype> base) 
    { 
     this.base = base; 
     this.dependency = base.getDependency(); 
    } 
} 

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

инстанцировании базы внутри Расширение

public class Extending 
{ 
    private ISomeDependencySubtype dependency; 
    private Base base; 

    public Extending(ISomeDependencySubtype dependency) 
    { 
     this.base = new Base(dependency); 
     this.dependency = dependency; 
    } 
} 

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

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

+1

Ваши примеры иногда содержат ложную 'extends Base'. – 5gon12eder

+0

Хорошо пятнистый. Будет редактировать. – Jonathan

ответ

0

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

В дополнение к вашим решениям, можно также передать только тип Base и экземпляр ISomeDependency конструктору Extending «s и экземпляр Base объекта динамически.

public class Extending { 

    // Keep an additional copy (ugly and probably unsafe). 
    final dependendcy dependendcy; 
    final Base base; 

    public Extending(final Class<? extends Base> cls, 
        final ISomeDependencySubtype dependendcy) throws Exception { 
     this.dependency = dependency; 
     this.base = cls.getConstructor(ISomeDependency.class).newInstance(dependency); 
    } 
} 

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

Если вы предпочитаете, вы также можете принять конструктор по умолчанию (может показаться более простым требованием), а затем вызвать метод init с зависимостью. [Мне не нравятся init методов.] Таким образом вы устраните двусмысленность, должен ли инициализироваться Base вызывающим.

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

assert this.dependency == this.base.getDependency(); 

вполне может потерпеть неудачу. Конечно, это не какой-либо безопаснее обратное приведение ссылки, возвращаемой getDependency так по той же причине,

assert this.base.getDependency() instanceof ISomeDependencySubtype; 

, возможно, не так хорошо.

Даже если Extending extends Base (что вы говорите, что вы не хотите), эта проблема не улучшается.

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

Поскольку я не думаю, что идеальное решение существует, оно, вероятно, во многом зависит от фактического случая использования, которое зло принять.

+0

Спасибо, оставит немного дольше, прежде чем я выберу правильный ответ на всякий случай, но я думаю, что вы правы, нет волшебного решения. – Jonathan