Я хочу следовать принципу разделения интерфейса, но класс закрыт. Является ли обертка правильный путь?
Несколько раз я обнаружил, что сам работаю с закрытым классом (я не могу его изменить), который я хотел бы реализовать в виде приятного узкого интерфейса, соответствующего моим потребностям. Предполагается, что мой клиентский код владеет интерфейсом, но я не знаю механизма, чтобы объявить, что этот закрытый класс является реализацией моего суженного интерфейса.
Я пытаюсь разрешить передачу этого класса (добавление зависимости) в мой код (композицию), но также и всему остальному, что может поддерживать суженный интерфейс. На других языках печатание утки делает это тривиальным. Я нахожусь в Java, поэтому я ожидаю написать целый другой класс, чтобы обернуть закрытый класс, чтобы это произошло. Есть ли способ, по которому я скучаю?
Спасибо
РЕДАКТИРОВАТЬ по адресу dupe:
Принцип сегрегации интерфейса не содержит упоминания о проблеме закрытого класса, которая является предметом этого вопроса. Пожалуйста, пересмотрите маркировку как обман этого конкретного вопроса.
Этот вопрос: Принцип разделения интерфейса - Программа для интерфейса, имеет хороший пример принципа разделения интерфейса:
class A {
method1()
method2()
// more methods
method10()
}
class B {
A a = new A()
}
станет
interface C {
method1()
method2()
}
class A implements C{
method1()
method2()
// more methods
method10()
}
class B {
C c = new A()
}
Но обратите внимание, как это требует изменения класса A. Если A закрыт для модификации, как мне выполнить то же самое чисто?
3 ответа
В зависимости от ситуации, одна возможность состоит в том, чтобы обернуть все классы в класс-обертку, который предоставляет указанный интерфейс, я имею в виду что-то вроде этого:
public class UnmodifyableObject {
public void method1();
public void method2();
public void method3();
public void method4();
}
Тогда вы хотите, чтобы интерфейс выглядел так:
public interface MyInterface {
public void method1();
public void method2();
}
В качестве решения вы можете обернуть UnmodifyableObject
в WrappedUnmodifyableObject
:
public class WrappedUnmodifyableObject implements MyInterface {
private final UnmodifyableObject unmodifyableObject;
public WrappedUnmodifyableObject(final UnmodifyableObject unmodifyableObject) {
this.unmodifyableObject = Objects.requireNonNull(unmodifyableObject, "unmodifyableObject");
}
@Override
public void method1() {
unmodifyableObject.method1();
}
@Override
public void method2() {
unmodifyableObject.method2();
}
public void method3() {
unmodifyableObject.method3();
}
public void method4() {
unmodifyableObject.method4();
}
}
Он не делает ничего, кроме делегирования всех методов, и, конечно, реализует интерфейс.
Несколько важных моментов, на которые следует обратить внимание:
- Вы должны использовать композицию вместо наследования, может быть проще расширить класс, но вы не контролируете этот код, он может удалять методы или даже final
, - Это значит, что вы должны сделать довольно много работы.
- Если вы не хотите выполнять работу самостоятельно, вам, возможно, придется поискать инструменты для изменения байт-кода либо перед выполнением, либо перед загрузкой класса.
Использование этого объекта будет через:
UnmodifyableObject unmodifyableObject = someExternalMethodCall();
MyInterface myInterfaceObject = new WrappedUnmodifyableObject(unmodifyableObject);
Марк Питерс предложил просто расширить закрытый класс, чтобы избежать обертки. Поскольку он не опубликовал ответ, основанный на нем, я напишу его.
Я обычно избегаю наследования в пользу композиции как реакции коленного рефлекса, но в этом случае это действительно имеет смысл. Основная причина написания оболочки - это просто перекладывание греха кодирования в реализацию из метода клиента в оболочку. Таким образом, единственное, что вы получили, это уровень косвенности. Если это все, что у нас есть, и мы можем найти более простой способ, почему бы и нет?
Вот некоторый код, который накладывает очень узкий ролевой интерфейс на очень популярный и очень закрытый библиотечный класс: ArrayList.
public class MainClass {
public static void main(String[] args) {
OpenArrayList<String> ls = new OpenArrayList<String>();
ShowClient client = new ShowClient(ls);
ls.add("test1");
client.show();
ls.add("test2");
client.show();
}
}
//Open version of ArrayList that can implement roleinterfaces for our clients
public class OpenArrayList<E> extends ArrayList<E> implements ShowState {
private static final long serialVersionUID = 1L;
}
//Roleinterface for ShowClient
public interface ShowState {
public int size();
public String toString();
}
//Show method programmed to a roleinterface narrowed specifically for it
public class ShowClient {
private ShowState ss;
ShowClient(ShowState ss) {
this.ss = ss;
}
void show() {
System.out.println( ss.size() );
System.out.println( ss.toString() );
}
}
Итак, если вы собираетесь выполнять сегрегацию интерфейса при использовании закрытого для модификации класса, есть ли причина этого не делать?
Используйте один или несколько адаптеров. Адаптеры - это тип оболочки, который изменяет интерфейс обернутого класса без необходимости менять его источник.
class A {
method1()
method2()
// more methods
method10()
}
interface SegregatedI1 {
method1();
}
interface SegregatedI2 {
method2();
method3();
}
class ASegregatedI1Adapter implements SegregatedI1 {
private final A a;
AI1Adapter(A a){
this.a = a;
}
public void method1(){
a.method1();
}
}
Обратите внимание, что A
может быть интерфейсом или final
учебный класс. Также обратите внимание, что адаптер может реализовывать более одного из отдельных интерфейсов, или вы можете иметь отдельные адаптеры для каждого (я бы предпочел, чтобы последний оставался встроенным с единственной ответственностью).