0

Вот мой сценарий.Как абстрагироваться от зависимостей в коде библиотеки Android?

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

public interface ITimeDataServer { 
    TimeRecord[] get(int userID); 
    void save(TimeRecord record); 
} 

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

ITimeDataServer myServer; 
int myUserID; 

void loadRecords() { 
    TimeRecord[] records = myServer.get(myUserID); 
    // etc... 
} 

Моя трудность в том, как я могу гарантировать, что myServer получает набор?

Это кажется общей проблемой, но я не могу найти решение.

Моя первая мысль заключалась в том, что myServer будет передан через конструктор, но действия Android на самом деле не создаются с помощью конструкторов.

Я придумал несколько решений, но они все неприглядные в некотором роде:

Icky Решение 1

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

private static ITimeDataSource theDataSource; 
public static void launch(Activity currentActivity, ITimeDataSource dataSource) { 
    theDataSource = dataSource; 
    Intent intent = new Intent(currentActivity, MainActivity.class); 
    currentActivity.startActivity(intent); 
} 

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

Icky Решение 2

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

public class TimeDataSourceProvider { 

    private static ITimeDataSource myDataSource = null; 

    public void initialize(ITimeDataSource dataSource) { 
     myDataSource = dataSource; 
    } 

    public ITimeDataSource get() { 
     if (myDataSource == null) 
      throw new NullPointerException("TimeDataSourceProvider.initialize() must be called before .get() can be used."); 
     else 
      return myDataSource; 
    } 
} 

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

Icky раствор 3

Как вариант # 2, создать статический IODependencyProvider класс, который должен быть инициализирован со всеми зависимостями при запуске приложения.

public class IODependencyProvider { 

    static ITimeDataSource myTimeData; 
    static IScheduleDataSource myScheduleData; // etc 

    public static void initialize(ITimeDataSource timeData, IScheduleDataSource scheduleData /* etc */) { 
     myTimeData = timeData; 
     myScheduleData = scheduleData; 
     //etc 
    } 

    public static ITimeDataSource getTimeData() { 
     if (myTimeData == null) 
      throw new NullPointerException("IODependencyProvider.initialize() must be called before the getX() methods can be used."); 
     else 
      return myTimeData; 
    } 

    // getScheduleData(), etc 
} 

Это, кажется, превосходит # 1 и # 2, так как неспособность инициализации будет гораздо труднее красться, но она также создает взаимозависимости между типами данных, которые в противном случае не должно существовать.

... и другие нехорошие вариации на эту тему.

общих темы, которые делают эти решения дерьмовым:

  • необходимо использовать статические поля для передачи несериализуемой информации к деятельности
  • отсутствия возможности принудительной инициализации этих статических полей (и последующее Стихия)
  • неспособность четко идентифицировать зависимости какого-либо процесса (в связи с зависимостью от статики)

Что нужно сделать разработчику Android от nooby?

+0

Если бы аннотацию для статической инициализации (), чтобы сказать: «Этот метод ДОЛЖЕН быть вызван во время запуска приложения», что облегчит, по крайней мере, вторую точку. –

ответ

1

До тех пор, пока эти зависимости реализуют Parcelable правильно, вы должны добавить их в свои намерения, а затем разобрать их как ITimeDataServer и получить правильный класс.

+0

Даже если интерфейс действительно реализовал Parcelable (не тривиальный), нет никакого способа ЗАПРОСИТЬ, чтобы он включался в него при запуске этой операции, правильно? (Это одна из вещей, которые мне не нравятся в деятельности Android.) –

+0

Правильно, многие шаблоны, которые мы хотели бы использовать, если бы мы не были ограничены каркасом, не работают на Android, где мы не имеют контроль над структурой. Используя parcelable, вы имеете доступ к обычным методам Android для передачи объектов между действиями, которые звучат как то, что вы пытаетесь сделать, и могут оказаться лучше и проще, чем продвигать собственное решение. –

+0

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

0

Я нашел хорошее решение here, в наименее любимом ответе.

Я определяю деятельность библиотеки, как абстрактные и конструктора не по умолчанию, а конструктор, который принимает интерфейс, например, так:

public abstract class TimeActivity extends AppCompatActivity { 

    private ITimeDataSource myTimeDataSource; 
    public TimeActivity(@NonNull ITimeDataSource dataSource) { 
     myTimeDataSource = dataSource; 
    } 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_time); 

     // do stuff with myTimeDataSource! 
    } 
} 

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

Это позволяет вам абстрагироваться и вводить все виды сумасшедшего поведения! Woooo!

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

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