实现常量对象的最佳方法是什么?

时间:2010-06-03 23:13:41

标签: java c++ design-patterns coding-style

首先,我应该说“恒定对象”一词可能不太正确,可能已经意味着与我的想法完全不同,但这是我能想到的最好的术语来形容我我在说什么。

所以基本上我正在设计一个应用程序而且我遇到了似乎可能存在现有设计模式的东西,但我不知道它是什么或搜索什么,所以我将描述它是什么我正在尝试做什么,我正在寻找有关实施它的最佳方式的建议。

让我们说你有一个班级:

public class MyClass {
    private String name;
    private String description;
    private int value;

    public MyClass(String name, String description, int value) {
        this.name = name;
        this.description = description;
        this.value = value;
    }

    // And I guess some getters and setters here.
}

现在让我们说你提前知道只会说这个类的3个实例,而且数据也是预先知道的(或者至少会在运行时读取文件,确切的文件名是事先知道)。基本上我得到的是数据在运行期间不会被更改(一旦设置好)。

起初我以为我应该在某处声明一些静态常量,例如

public static final String INSTANCE_1_DATA_FILE = "path/to/instance1/file";
public static final String INSTANCE_2_DATA_FILE = "path/to/instance2/file";
public static final String INSTANCE_3_DATA_FILE = "path/to/instance3/file";
public static final MyClass INSTANCE_1 = new MyClass(getNameFromFile(INSTANCE_1_DATA_FILE), getDescriptionFromFile(INSTANCE_1_DATA_FILE), getValueFromFile(INSTANCE_1_DATA_FILE));
public static final MyClass INSTANCE_2 = new MyClass(getNameFromFile(INSTANCE_2_DATA_FILE), getDescriptionFromFile(INSTANCE_2_DATA_FILE), getValueFromFile(INSTANCE_2_DATA_FILE));
public static final MyClass INSTANCE_3 = new MyClass(getNameFromFile(INSTANCE_3_DATA_FILE), getDescriptionFromFile(INSTANCE_3_DATA_FILE), getValueFromFile(INSTANCE_3_DATA_FILE));

现在很明显,每当我想使用3个实例中的一个时,我就可以直接引用常量。

但是我开始认为可能有一种更清洁的方法来处理这个问题,我想到的下一件事就是:

public MyClassInstance1 extends MyClass {
    private static final String FILE_NAME = "path/to/instance1/file";

    public String getName() {
        if (name == null) {
            name = getNameFromFile(FILE_NAME);
        }
        return name;
    }

    // etc.

}

现在,只要我想使用MyClass的实例,我就可以使用我想要的那个。

private MyClass myInstance = new MyClassInstance2();

或者甚至更好的做法是让他们成为单身者而且只做:

private MyClass myInstance = MyClassInstance3.getInstance();

但我不禁想到这也不是处理这种情况的正确方法。我是否在思考这个问题?我应该在某处使用switch语句,例如。

public class MyClass {
    public enum Instance { ONE, TWO, THREE }

    public static String getName(Instance instance) {
        switch(instance) {
        case ONE:
            return getNameFromFile(INSTANCE_1_DATA_FILE);
            break;
        case TWO:
            etc.
        }
    }
}

有谁能告诉我实现这个的最佳方法?请注意,我已经用Java编写了示例代码,因为这是我最强的语言,但我可能会用C ++实现该应用程序,所以目前我更多的是寻找与语言无关的设计模式(或者只是为了让某人告诉我选择我已提到的一个简单的解决方案。

6 个答案:

答案 0 :(得分:5)

如果您希望值保持不变,那么您将不需要setter,否则代码可以简单地更改常量中的值,使它们不是很常量。在C ++中,你可以声明实例const,虽然我仍然摆脱了setter,因为有人总是可以抛弃const。

模式看起来没问题,尽管每次请求创建一个新实例时,常量都不常见。

在java中,您可以创建“智能”的枚举,例如

public enum MyClass {
    ONE(INSTANCE_1_DATA_FILE),
    TWO(INSTANCE_2_DATA_FILE),
    //etc...

    private MyClass(String dataFile)
    {
       this(getNameFromDataFile(dataFile), other values...)
    }

    private MyClass(String name, String data, etc...)
    {
       this.name = name;
       // etc..
    }

    public String getName()
    {
        return name;
    }
}

在C ++中,您将使用私有构造函数创建MyClass,该构造函数接受初始化所需的文件名和其他内容,并在MyClass中为每个实例创建static const个成员,并为值分配新的实例MyClass使用私有构造函数创建。

编辑:但是现在我看到这个场景我不认为这是一个好主意,有静态值。如果ActivityLevel的类型是您的应用程序的基础,那么您可以将不同类型的活动级别枚举为常量,例如一个java或字符串枚举,但它们只是占位符。实际的ActivityDescription实例应来自某种数据访问层或提供者。

e.g。

enum ActivityLevel { LOW, MED, HIGH  }

class ActivityDescription
{
    String name;
    String otherDetails;
    String description; // etc..
    // perhaps also
    // ActivityLevel activityLevel;

    // constructor and getters
    // this is an immutable value object
}

interface ActivityDescriptionProvider
{
    ActivityDescription getDescription(ActivityLevel activityLevel);
}

如果需要,您可以使用静态实现提供程序,或者使用ActivityDescription实例的枚举,或者更好的是从文件加载的ActivityLevel到ActivityDescription的Map,从spring配置中获取等等。主要的一点是使用获取给定ActivityLevel的实际描述的接口将应用程序代码与系统中如何生成这些描述的机制分离。它还可以在测试UI时模拟界面的实现。您可以使用模拟实现来强调UI,这是使用固定静态数据集无法实现的。

答案 1 :(得分:1)

  

现在让我们说你事先知道只会说这个类的3个实例,并且数据也是预先知道的(或者至少会在运行时读取文件,并且确切的文件名是事先知道的)。基本上我得到的是数据在运行期间不会被更改(一旦设置好了)。

我会使用enum。然后是这种味道:

public enum MyEnum {

    ONE("path/to/instance1/file"),
    TWO("path/to/instance2/file"),
    THREE("path/to/instance3/file");

    private String name;

    private MyEnum(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

可以使用如下:

MyEnum one = MyEnum.ONE;
String name = one.getName();

答案 2 :(得分:1)

(我再一次太慢了,你已经接受了答案,但无论如何这里......)

您希望(a)阻止更改MyClass对象中保存的数据,并且(b)仅允许存在一组固定的MyClass对象,这意味着运行时代码不应该能够创建MyClass的新实例。

您的初始示例有一个公共构造函数,它违反了(b)

我使用Factory方法,因此Factory是唯一可以创建实例的东西,并且该类不提供任何setter,因此它是不可变的。

根据您对未来的灵活性,您可以将工厂和类放在同一个包中并以此方式限制范围,或者您可以使MyClass成为工厂内部的内部类。您也可以考虑将MyClass与其实现分开。

属性文件可用于配置工厂本身。 属性文件(例如“foo.properties”)看起来像

one=/path/to/datafile1
two=/another/path/to/datafile2
three=/path/to/datafile3

我在下面的(Java)示例中使用“Foo”而不是“MyClass”。

public class FooFactory
{
    /** A place to hold the only existing instances of the class */
    private final Map<String, Foo> instances = new HashMap<String, Foo>();

    /** Creates a factory to manufacture Foo objects */
    // I'm using 'configFile' as the name of a properties file,
    // but this could use a Properties object, or a File object.
    public FooFactory(String configfile)
    {
        Properties p = new Properties();
        InputStream in = this.getClass().getResourceAsStream();
        p.load(in); // ignoring the fact that IOExceptions can be thrown

        // Create all the objects as specified in the factory properties
        for (String key : p.keys())
        {
            String datafile = p.getProperty(key);
            Foo obj = new Foo(datafile);
            instances.put(key, obj);
        }
    }

    public Foo getFoo(String which)
    {
        return instances.get(which);
    }

    /** The objects handed out by the factory - your "MyClass" */
    public class Foo
    {
        private String name;
        private String description;
        private int value;

        private Foo(String datafile)
        {
            // read the datafile to set name, description, and value
        }
    }
}

您已设置为仅允许在运行时无法更改的预定义实例,但您可以在以后为另一次运行设置不同的实例。

答案 3 :(得分:0)

在我看来,你的第一种方法就是最好的,最不容易出现代码腐烂。对于仅仅为了更改包含将用于构建它的数据的文件名而对子对象进行子类化的想法并没有给我留下深刻的印象。

当然,您可以通过将所有这些包装在提供某种枚举访问的外部类中来改进您的原始想法。换句话说,是MyClass的集合。但我认为你应该抛弃这个子类化的想法。

答案 4 :(得分:0)

首先,您应该限制在代码中使用这些实例的位置。在尽可能少的地方使用它们。鉴于这些是文件名,我希望你想要三个访问文件的类实例。需要多少课程取决于你想要用它们做什么?查看这些类的Singleton模式。

现在你不需要常量,但是可以有一个帮助类,它将读取包含文件名的文件并将它们提供给reader类。查找然后名称的代码也可以是Singleton的静态初始化程序调用的方法。

答案 5 :(得分:0)

常用方法是使用地图:

private static final Map<String, YouClass> mapIt = 
     new HashMap<String, YouClass>(){{
         put("one", new YourClass("/name", "desc", 1 )),
         put("two", new YourClass("/name/two", "desc2", 2 )),
         put("three", new YourClass("/name/three", "desc", 3 ))
    }}


public static YourClass getInstance( String named ) {
   return mapIt.get( named );
}

下次需要时:

 YouClass toUse = YourClass.getInstance("one");

可能使用字符串作为键不是最好的选择,但你明白了。