Запретить зависимость от класса в Java / Kotlin, но разрешить расширение - PullRequest
0 голосов
/ 08 июня 2019

Я хочу создать полезный базовый Java-класс с несколькими защищенными методами и хуками, чтобы подклассы могли быть легко реализованы.

Однако я хочу, чтобы этот класс был ТОЛЬКО доступен для получения подкласса, но недоступенкак зависимость.Причина в том, чтобы некоторые младшие / неосторожные разработчики не связывали свой код с этим базовым классом.

Например, если мой базовый класс называется BaseActivity.java, любой может создать свой собственный

public class MyNewActivity extends BaseActivity

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

public void doSomethingOnBaseActivity(BaseActivity activity);

private BaseActivity someField;

public BaseActivity getActivity();

Есть ли способ выполнить такое ограничение в Java?Может быть, в Kotlin это было бы возможно?

РЕДАКТИРОВАТЬ: Это не дубликат Kotlin: Может ли абстрактный суперкласс иметь абстрактный конструктор? .Я хочу предотвратить зависимость от базового класса, а не просто создание экземпляров.«Абстракт» здесь не помогает.

Ответы [ 2 ]

1 голос
/ 08 июня 2019

Нет, это невозможно. Этот ответ верен для всех типов, абстрактных или нет, интерфейса или класса. Когда вы находитесь в области видимости класса (например, в том же пакете), и этот класс не запечатан, тогда все в этой области могут наследовать его. Пока вы находитесь внутри области действия, вы можете ссылаться на этот тип. Это точка доступа модификаторы. Не имеет смысла разрешать расширение типа, но не ссылаться на него. Это противоречит концепции. Почему вы хотите это сделать? Вы не можете удалить этот базовый класс в любом случае, потому что это нарушит код всех наследников. Нет смысла разрешать расширение, но запрещать ссылки. Что является причиной этого. Может быть, есть другой способ достичь своей цели. В тот момент, когда кто-то наследует от типа, создается зависимость. Эта зависимость называется наследованием. Подтип является супертипом. Вы не можете скрыть этот факт от компилятора.

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

1 голос
/ 08 июня 2019

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

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


Если вы создали базовый класс, то не публикует API, который в первую очередь разрешает этот .Об этом специально была глава в Чистый код .

Если ваш базовый класс расширяет другой базовый класс, у вас проблемы. Вы не можете скрыть уже опубликованный API , если вы расширяете и не инкапсулируете.

Я хочу, чтобы этот класс был ТОЛЬКО доступен для получения подкласса, но не доступен какзависимость.Есть ли способ выполнить такое ограничение в Java?Может быть, в Котлине это было бы возможно?

Нет.Это не мнение, это намеренно.


Может быть сложный способ скрыть методы родительских классов, но не в классе, с которым потребитель взаимодействует (расширяет).

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

Представьте себе:

  • потребительский модуль -> ConsumerActivity extends ExtensibleActivity
  • ваш библиотечный модуль -> ExtensibleActivity extends BaseActivity
  • Ваш базовый библиотечный модуль -> BaseActivity extends Activity
  • Android SDK -> Activity

Модуль потребителя видит только то, что находится внутри «вашего библиотечного модуля».Он знает о ExtensibleActivity, но не может видеть ни один из его супертипов.Потребитель все еще может ссылаться на ExtensibleActivity и его методы.Побочный эффект заключается в том, что суперклассы не известны с точки зрения потребителя, вы не можете передать экземпляр ExtensibleActivity как Activity, потому что система типов не знает, что он расширяет Activity, потому что он не видит промежуточный тип BaseActivity.Вот график того, что видит потребитель:

ConsumerActivity -> ExtensibleActivity -> BaseActivity (doesn't exist) -> ??? (don't know)

В этот момент вы просто должны спросить себя: «Это должно было быть расширено Activity во-первых?».

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


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

Напишите хорошую документацию для вашего кода и руководство по использованию .

И, пожалуйста, позвольте мне разбить вашу чертову библиотеку, если я решу использовать ее неправильно.

У вас есть несколько методов в вашем API?Большой!Никто не помешает мне вызвать их из строя.Вы можете написать в своем руководстве, как это должно использоваться, но в конечном итоге я пишу свою программу, используя вашу библиотеку, и если я делаю это неправильно, то это моя вина, когда она ломается.Это хорошо.

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