应用设置设计方法

时间:2014-08-23 13:15:56

标签: java oop design-patterns singleton

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

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

第一个。将ApplicationSettings声明为singleton。

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

我们有一个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
    }
}

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

作为一种选择,为了避免使用单例,我们将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
    }
}

我相信第二种方式比单身人士的第一种方式更灵活,产生更易于管理的代码。

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

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

谢谢。

3 个答案:

答案 0 :(得分:3)

第一种方式(单身/工厂)有严重的缺点。单身人士难以测试,单身人士的使用会导致紧密耦合到你的系统。当组件紧密耦合时,很难将不同的组件相互组合,这会显着降低整个产品的灵活性。

第二种方式,当我们将ApplicationSettings作为参数传递给构造函数时,它有自己的缺点,当我们有复杂的继承树时,它会进入阶段。

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

SomeObject课程根本不需要ApplicationSettings。但是ChildSomeObject需要访问应用程序设置。

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

这种方法会污染具有不必要功能的代码,使其不那么优雅和灵活。

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

我使用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);

}

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

}

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

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(" ******* ******* *******");        

    }
}

请注意,我们在ApplicationComponent课程中声明:

@Inject
private SettingsInterface applicationSettings;

并且在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>

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
 ******* ******* *******
------------------------------------------------------------------------

答案 1 :(得分:1)

我相信第二个想法更好,因为它揭示了通过接口的依赖性。其他方法:如果您计划在代码中全局访问应用程序设置,则可以将它们设置为静态。这样的课程可以从任何地方访问。

使用Singleton可能是一个坏主意,因为它通常隐藏模块之间的连接,而不是通过接口显示它们。

虽然使全局可达的静态类可能看起来类似于单例(因为它不需要通过接口显式地显示依赖关系)我认为它仍然比单例更好,因为如果你有一个全局可达的静态类,你可以期待它是全局使用的(它变成了一个容器,或者一个方法工具箱,它可能不需要经常更改。)

答案 2 :(得分:0)

您还可以实现ApplicationSettingsFactory定义方法ApplicationSettingsInterface Create()并将其用作依赖项。因此,课程将更加可测试。

工厂可以合并对象创建策略(单线或非单线)。

最后,为什么不使用IoC容器来定义单声道? IoC容器本身就是工厂,如果你对在代码中的任意位置创建配置对象不感兴趣,我建议将`ApplicationSettingsInterface'的依赖注入作为最佳解决方案。否则,您可以使用自己的工厂。

相关问题