开发者

Java OO Design help - how to abstract out a save method?

I have a Preference class (module) that's used across several different apps. Basically it's a cache of the preferences so that the systems don't have to call the backend all the time. It's similar to a cache but with some additional niceties such as isPreferenceSelected, has some helper methods, etc.

The issue is that I'd like to include a savePreference within the class so that whoever uses it can just override that method, be it to a database, to a flat file, etc. The key is that this module just doesn't want to care. The issue is that it's not an abstract class so I can't override the static methods and even if it was, I don't want to create a million instances because I don't want to load the preferences each time. And I can't create a abstract singleton either.

Therefore I'm not sure what to do. Here is a code snippet of what I'd like to do with comments:

// 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.
  }
}

That was the core class/code that the different systems leverage. Now what I'd like to do is say in a webapp, a desktop app, etc., be able to use this class in my co开发者_JAVA百科de such as:

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

And have the savePreference() method not just save the in-memory preferences, but also save it to the external source. Otherwise everywhere I have savePreference() I have to follow it by a db call savePreferenceToDB(), a FlatFile call such as savePreferenceToFlatFile(), and so on. This is very error prone, someone somewhere will forget to save it. Plus it really makes no sense to sprinkle the save to permanent storage code everywhere with this type of code when it should really only be done once. Also remember that the main module has no idea if the permanent storage is a database, an xml file, a flat file, etc.

Hint: If I did Preference.getInstance().savePreference() that wouldn't work because you can't abstract a singleton. And I can't create a static method savePreference() because it's not possible to override a static method.

The only options I can see is to create some kind of complex Factory pattern, but that seems like a lot of overkill to me. Therefore any suggestions would be greatly appreciated.


This sounds like something that your dependency injection (DI) container should be handling, not a complex factory pattern.

That is, I think you should ditch the usages of static, have whatever creates the other applications inject an instance of Preference into your applications. You can do this without a DI framework if you just take the Preference as a parameter in your constructor for whatever other classes depend on it.


Edit: Let me give you an example of dependency injection without a framework. Take the following set of classes:

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);
    }
}

This is a simplistic example, but it satisfies your requirements:

  • The Preference instance is only created once
  • The Preference class can be extended by whoever implements the Main class to instantiate whatever kind of Preference subclass they want to, if they wanted to persist it in a relational database (or whatever)

By avoiding having static calls in the first place you also make it possible for your someFunction() example to be unit tested without pulling in a potentially big, complicated preferences framework. Rather, someone implements a mock Preference subclass and passes it into the class that runs someFunction(). Your code will be much more testable that way.


@Mike says:

... I think you should ditch the usages of static

@Stephane responds:

... what is the major issue with static methods?

It is not just static methods. It is also the singleton instance.

Basically, they are inflexible:

  • They make it difficult to do things in alternative ways, as illustrated by your problem. If you didn't use a static method and a private singleton instance, you could create a Preferences interface and/or abstract base class, together with implementations that load and save the in-memory preferences in different ways.

  • Static instances tend to make testing harder. For instance, if you had a preferences UI that made use of your Preferences class, you couldn't unit test the UI classes using a "mock" version of Preferences. (Or at least, it would be a lot harder to do.)

  • Statics tend to make it difficult to reuse your code because of the hard dependencies on specific named classes and specific implementations.

  • Statics are non-OO. This is not intrinsically a bad thing, but it does mean that you can't make use of the nice properties of OO ... like overriding and polymorphism ... when you use statics.

If you have a significant number of these static methods / static objects in your application, a DI framework is a good solution. But as @Mike says, using Factory methods and passing objects in constructors will work just as well in many cases.


You commented:

One of the reasons I have it as a static class is because the preferences are loaded at startup. After that they stay in memory in the one static object. With DI, each time I create the object, I'd have to reload the information into memory from the data source. This defeats the whole purposes of having a Preferences Object (that pretty much acts like a cache with benefits).

  1. This does not require you to use a static instance.

  2. With DI (or explicitly wiring instances via constructors), you don't create the Preferences object more than once. You create it once, and then inject it as many times as required.

There is a halfway between your current approach with a static method that wraps a static instance of a hard-wired class and full DI. That is a what can best be described as a static holder; e.g.

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;
    }
}


I think it might take some rework of your design (unfortunately, I don't have a decent whiteboard in my apartment yet, so I can't easily sketch things out to conform), but I immediately thought Strategy pattern as soon as you said this:

The issue is that I'd like to include a savePreference within the class so that whoever uses it can just override that method, be it to a database, to a flat file, etc. The key is that this module just doesn't want to care.

You might have an abstract Preferences class that has every method but saving (and loading) implemented. In the sense of the pattern, this would be the Strategy interface. Your different types of saving and loading would be handled by the concrete implementations.


  1. Create an interface for your preference manipulation class:

    public interface PreferenceHandler { void savePreference(); void readPreference(); }

  2. Pass an instance of type PreferenceHandler to your class with all the static methods.

  3. Invoke the methods on that class within your class.

Though, not lovin' all those static methods. It's probably why you're having so many issues here. Create a factory that gives you a copy of the class if you don't want to be creating lots of copies of it. But static methods really impede code re-use and extension. Or perhaps use a framework like Spring to manage classes of this sort.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜