应用程序设置设计方法

5 java oop singleton design-patterns

假设有一个复杂的应用程序,我们在其中存储和检索一组应用程序设置。

应用程序设置用于许多应用程序类,有两种方法可以解决此任务。

第一个。将 ApplicationSettings 声明为单例。

public class ApplicationSettings {

    private static final ApplicationSettings instance = new ApplicationSettings();

    public static ApplicationSettings getInstance() {
        return instance;
    }


    // ... public methods to set and retrieve settings information, save settings, etc.


    private ApplicationSettings() {

        // loading the application settings in the private constructor
    }
}
Run Code Online (Sandbox Code Playgroud)

我们有一个Application类(以及许多其他类),我们在其中使用应用程序设置。

public class Application {

    public Application() {

        // .... initialization code 
    }


    public void doSomething() {

        ApplicationSettings applicationSettings = ApplicationSettings.getInstance();

        someMethod1(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY1));     
        someMethod2(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY2));     

        // etc
    }
}
Run Code Online (Sandbox Code Playgroud)

它可能看起来很方便,但是使用单例有一些严重的缺点,例如,单例类很难测试。使用单例类会降低类的灵活性和可管理性。

作为一种选择,为了避免使用单例,我们声明ApplicationSettings为常规类,而不是单例。并将ApplicationSettings实例作为参数传递给用于ApplicationSettings内部活动的类的构造函数。

第二种方式:

public interface ApplicationSettingsInterface {

    // ... public methods to set and retrieve settings information, save settings, etc.

}


public class ApplicationSettings implements ApplicationSettingsInterface {


    private ApplicationSettings() {

        // loading the application settings in the private constructor
    }


    // ... public methods to set and retrieve settings information, save settings, etc.

}



public class Application {

    private final ApplicationSettingsInterface applicationSettings;


    public Application(ApplicationSettingsInterface applicationSettings) {

        this.applicationSettings = applicationSettings;

        // .... initialization code 
    }


    public void doSomething() {

        someMethod1(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY1));     
        someMethod2(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY2));     

        // etc
    }
}
Run Code Online (Sandbox Code Playgroud)

我相信第二种方式比第一种单例方式更灵活,并且生成更易于管理的代码。

我的问题:第二种方法假设我们需要将对应用程序设置的引用存储到每个使用 app 的类实例中。设置并获取应用程序。设置对象作为构造函数参数。

是否可以将这些应用程序设置引用存储到多个应用程序中?对象实例中,或者有更好的第三种方法来处理此任务?

谢谢。

小智 4

第一种方式(使用单例/工厂)有严重的缺点。单例很难测试,并且单例的使用会导致与系统的紧密耦合。当组件紧密耦合时,很难将不同的组件相互组合,这大大降低了整个产品的灵活性。

第二种方法,当我们ApplicationSettings作为参数传递给构造函数时,有它自己的缺点,当我们有复杂的继承树时就会出现这种缺点。

想象一下我们有课程:GrandParentOfSomeObject-> SomeObject->ChildSomeObject

而类根本SomeObject不需要。ApplicationSettingsChildSomeObject需要访问应用程序设置。

在这种情况下我们需要将ApplicationSettings对象引用传递给SomeObject构造函数,即使SomeObject不需要这个对象,也有必要传递它。稍后提供给ChildSomeObject.

这种方法会用不必要的功能污染代码,并使其不太优雅和灵活。

第三种首选方法是使用依赖项注入方法,在需要时注入应用程序设置对象。

我使用Google Guice来演示这种方式。

下面是带有我的评论的代码。


SettingsInterface.java- 代表应用程序设置功能的界面。

package com.mycompany.settingstest;

public interface SettingsInterface {


    int getFirstSetting();
    void setFirstSetting(int value);

    String getSecondSetting();
    void setSecondSetting(String value);

    boolean getThirdSetting();
    void setThirdSetting(boolean value);

}
Run Code Online (Sandbox Code Playgroud)

ApplicationSettings.java- 类的具体实现SettingsInterface

package com.mycompany.settingstest;

import javax.inject.Singleton;


@Singleton
public class ApplicationSettings implements SettingsInterface{

    private int firstSetting = -1234567890;
    private String secondSetting = "some default string value";
    private boolean thirdSetting = true;

    public ApplicationSettings() {

    }


    @Override
    public int getFirstSetting() {
        return this.firstSetting;
    }

    @Override
    public void setFirstSetting(int value) {
        this.firstSetting = value;
    }

    @Override
    public String getSecondSetting() {
        return this.secondSetting;
    }

    @Override
    public void setSecondSetting(String value) {
        this.secondSetting = value;
    }

    @Override
    public boolean getThirdSetting() {
        return this.thirdSetting;
    }

    @Override
    public void setThirdSetting(boolean value) {
        this.thirdSetting = value;
    }

}
Run Code Online (Sandbox Code Playgroud)

ApplicationSettingsModule.java- 声明必要绑定的模块,Google Guice 在依赖注入时可以使用这些绑定。

package com.mycompany.settingstest;

import com.google.inject.AbstractModule;
import com.google.inject.Scopes;

public class ApplicationSettingsModule extends AbstractModule {

    @Override
    protected void configure() {
        //bind the service to implementation class
        bind(SettingsInterface.class).to(ApplicationSettings.class).in(Scopes.SINGLETON);//this is lazy singleton
        //bind(SettingsInterface.class).to(ApplicationSettings.class).asEagerSingleton();//this is eager one
    }
}
Run Code Online (Sandbox Code Playgroud)

MainApplication.java- 主应用程序类,还声明了一个应用程序组件类来演示应用程序设置的功能。

package com.mycompany.settingstest;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class MainApplication {

    public static void main(String[] args) {

        Injector applicationSettingsInjector = Guice.createInjector(new ApplicationSettingsModule());

        SettingsInterface applicationSettings = applicationSettingsInjector.
                                         getInstance(SettingsInterface.class);

        System.out.println(" ******* Main Application *******");
        System.out.println(" Initial setting values ");

        System.out.println("first setting: " + applicationSettings.getFirstSetting());
        System.out.println("second setting: " + applicationSettings.getSecondSetting());
        System.out.println("third setting: " + applicationSettings.getThirdSetting());
        System.out.println(" ******* ******* *******");


        System.out.println(" ******* We're changing settings *******");        
        applicationSettings.setFirstSetting(789);
        applicationSettings.setSecondSetting("another custom string");
        applicationSettings.setThirdSetting(false);

        System.out.println(" Settings were changed. Check changes below: ");
        System.out.println("first setting: " + applicationSettings.getFirstSetting());
        System.out.println("second setting: " + applicationSettings.getSecondSetting());
        System.out.println("third setting: " + applicationSettings.getThirdSetting());
        System.out.println(" ******* ******* *******");

        System.out.println(" Now composing the application component and injecting application settings to it");        
        ApplicationComponent applicationComponent = applicationSettingsInjector.getInstance(ApplicationComponent.class);
        applicationComponent.execute();
    }
}

class ApplicationComponent {

    @Inject
    private SettingsInterface applicationSettings;


    public ApplicationComponent() {

    }

    public void execute() {

        System.out.println(" ******* Application component *******");
        System.out.println("Application settings instance is already injected");

        System.out.println("first setting: " + applicationSettings.getFirstSetting());
        System.out.println("second setting: " + applicationSettings.getSecondSetting());
        System.out.println("third setting: " + applicationSettings.getThirdSetting());
        System.out.println(" ******* ******* *******");        

    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,在ApplicationComponent课堂上我们声明:

@Inject
private SettingsInterface applicationSettings;
Run Code Online (Sandbox Code Playgroud)

execute()方法中我们已经构造了ApplicationSettings对象并且可以使用它,并且我们不将此对象引用传递给ApplicationComponent构造函数。

稍后,我们可以决定使用 的另一个实现SettingsInterface来代替ApplicationSettings. 例如,在第一个版本中,我们将应用程序设置存储到文本文件中。在我们产品的下一个版本中,我们希望使用一些复杂的集中设置提供程序机制。

在这种情况下,我们不需要全局更改我们的代码。我们只需更改 中的绑定以ApplicationSettingsModule将另一个实现绑定到SettingsInterface,就足以为我们系统的所有组件提供新的应用程序设置机制。


更新

忘记提及pom.xml内容和输出。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany</groupId>
    <artifactId>SettingsTest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>3.0</version>
        </dependency>
    </dependencies>    
</project>
Run Code Online (Sandbox Code Playgroud)

output

--- exec-maven-plugin:1.2.1:exec (default-cli) @ SettingsTest ---
 ******* Main Application *******
 Initial setting values 
first setting: -1234567890
second setting: some default string value
third setting: true
 ******* ******* *******
 ******* We're changing settings *******
 Settings were changed. Check changes below: 
first setting: 789
second setting: another custom string
third setting: false
 ******* ******* *******
 Now composing the application component and injecting application settings to it
 ******* Application component *******
Application settings instance is already injected
first setting: 789
second setting: another custom string
third setting: false
 ******* ******* *******
------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)