Java OO Design помощь - как абстрагироваться от метода сохранения? - PullRequest
3 голосов
/ 30 июля 2011

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

Проблема заключается в том, что я хотел бы включить savePreference в класс, чтобы тот, кто его использует, мог просто переопределитьэтот метод, будь то база данных, простой файл и т. д. Ключ в том, что этот модуль просто не хочет заботиться.Проблема в том, что это не абстрактный класс, поэтому я не могу переопределить статические методы , и даже если бы это было так, я не хочу создавать миллион экземпляров, потому что я не хочу загружать настройкикаждый раз.И я не могу создать абстрактный синглтон .

Поэтому я не уверен, что делать.Вот фрагмент кода того, что я хотел бы сделать с комментариями:

// Please ignore the missing Generics, etc.
public class Preference
{
  private static HashMap preferences = new HashMap();

  public static ... 

  // Some preferences are objects, such as images, etc.
  public static setPreference(String name, Object value)
  {
    .. some helper code
    preferences.put(name, value); // ignoring issues with if it already exists ;)
    savePreference(name, value); // saves to database, flatfile, etc.
  }
}

Это был базовый класс / код, который используют различные системы.Теперь я хотел бы сказать, что в веб-приложении, настольном приложении и т. Д. Я могу использовать этот класс в своем коде, например:

public someFunction(...)
{
  .. do some cool code
  Preference.savePreference("logoImage", image);
}

, а метод savePreference() непросто сохраните настройки в памяти, но также сохраните их во внешнем источнике.В противном случае везде, где у меня есть savePreference(), я должен следовать за ним с помощью вызова db savePreferenceToDB(), вызова FlatFile, такого как savePreferenceToFlatFile(), и так далее.Это очень подвержено ошибкам, кто-то где-то забудет сохранить его.Кроме того, на самом деле нет смысла распространять код сохранения в постоянное хранилище повсюду с этим типом кода, когда это действительно нужно сделать только один раз.Также помните, что основной модуль не знает, является ли постоянное хранилище базой данных, файлом XML, простым файлом и т. Д.

Подсказка: если бы я сделал Preference.getInstance().savePreference(), это не сработало бы, потому что Вы не можете абстрагировать синглтон .И я не могу создать статический метод savePreference (), потому что невозможно переопределить статический метод .

Единственные варианты, которые я вижу, - это создать какой-то сложный шаблон Фабрики, но мне это кажется излишним.Поэтому любые предложения будут с благодарностью.

Ответы [ 4 ]

7 голосов
/ 30 июля 2011

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

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


Редактировать : Позвольте мне привести пример внедрения зависимости без каркаса. Возьмите следующий набор классов:

public class Preference
{
    private String userName;

    public Preference(String userName)
    {
        this.userName = userName;
    }

    public void savePreference()
    {
        // Default implementation saves it to the screen. ;-)
       System.out.println(userName);
    }
}

public class Foo
{
    private Preference p;

    public Foo(Preference p)
    {
        this.p = p;
    }
}

public class Bar
{
    private Preference p;

    public Bar(Preference p)
    {
        this.p = p;
    }
}

public class Main
{
    public static void main(String[] args)
    {
        Preference p = new Preference("Mike");

        Foo f = new Foo(p);
        Bar b = new Bar(p);
    }
}

Это упрощенный пример, но он удовлетворяет вашим требованиям:

  • Экземпляр Preference создается только один раз
  • Класс Preference может быть расширен любым, кто реализует класс Main, для создания экземпляра любого подкласса Preference, который они хотят, если они хотят сохранить его в реляционной базе данных (или как угодно)

Во-первых, избегая вызовов static, вы также позволяете вашему примеру someFunction() быть модульным тестированием, не задействуя потенциально большую и сложную систему предпочтений. Скорее, кто-то реализует фиктивный подкласс Preference и передает его в класс, который запускает someFunction(). Таким образом, ваш код будет намного более тестируемым.

3 голосов
/ 30 июля 2011

@ Майк говорит:

... Я думаю, вы должны отказаться от использования статического

@ Стефан отвечает:

... что является основной проблемой статических методов?

Это не просто статические методы. Это также экземпляр singleton.

В основном они негибкие:

  • Они затрудняют выполнение действий альтернативными способами, что иллюстрируется вашей проблемой. Если вы не использовали статический метод и частный одноэлементный экземпляр, вы могли бы создать интерфейс Preferences и / или абстрактный базовый класс вместе с реализациями, которые загружают и сохраняют настройки в памяти различными способами.

  • Статические экземпляры обычно усложняют тестирование. Например, если у вас был пользовательский интерфейс предпочтений, в котором использовался ваш класс Preferences, вы не могли бы модульно протестировать классы пользовательского интерфейса, используя «фиктивную» версию Preferences. (Или, по крайней мере, это будет намного сложнее).

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

  • Статика не OO. По сути это не плохо, но это означает, что вы не можете использовать хорошие свойства ОО ... такие как переопределение и полиморфизм ... при использовании статики.

Если в вашем приложении имеется значительное количество этих статических методов / статических объектов, DI-фреймворк является хорошим решением. Но, как говорит @Mike, использование методов Factory и передача объектов в конструкторах во многих случаях будет работать так же хорошо.


Вы прокомментировали:

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

  1. Для этого не требуется использовать статический экземпляр.

  2. С помощью DI (или явного связывания экземпляров с помощью конструкторов) вы не создаете объект Preferences более одного раза. Вы создаете его один раз, а затем вводите столько раз, сколько требуется.

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

public interface Preferences {
    // Preferences API
}

public abstract class PreferencesBase implements Preferences {
    // Implement as much if the API as makes sense
}

public class FileBackedPreferences extends PreferencesBase {
    // Implement (protected) persistence methods.
}

public class DatabaseBackedPreferences extends PreferencesBase {
    // Implement (protected) persistence methods.
}

public class ApplicationPreferences {
    private static Preferences instance;

    private ApplicationPreferences() { }

    public Preferences getInstance() { return instance; }

    // Call this once during application startup with the
    // Preferences instance to be used by the application.
    public void initPreferences(Preferences instance) {
        if (this.instance != null) {
            throw new IllegalStateException(...);
        }
        this.instance = instance;
    }
}
2 голосов
/ 30 июля 2011

Я думаю, что это может потребовать некоторой переделки вашего дизайна (к сожалению, у меня пока нет приличной доски в моей квартире, поэтому я не могу легко сделать набросок, чтобы соответствовать), но я сразу подумал Стратегияpattern как только вы сказали это:

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

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

1 голос
/ 30 июля 2011
  1. Создайте интерфейс для вашего класса управления настройками:

    открытый интерфейс PreferenceHandler { void savePreference (); void readPreference (); }

  2. Передайте экземпляр типа PreferenceHandler вашему классу со всеми статическими методами.

  3. Вызовите методы этого класса в вашем классе.

Хотя не люблю все эти статические методы. Вероятно, поэтому у вас здесь так много проблем. Создайте фабрику, которая дает вам копию класса, если вы не хотите создавать много ее копий. Но статические методы действительно препятствуют повторному использованию и расширению кода. Или, возможно, использовать такую ​​среду, как Spring, для управления такими классами.

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