Как лучше всего реализовать постоянный объект? - PullRequest
1 голос
/ 04 июня 2010

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

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

Допустим, у вас есть класс:

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

Obvisouly теперь, когда я хочу использовать один из 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 ]

5 голосов
/ 04 июня 2010

Если вы хотите, чтобы значения были постоянными, вам не понадобятся сеттеры, иначе код может просто изменить значения в ваших константах, делая их не очень постоянными. В C ++ вы можете просто объявить экземпляры const, хотя я бы все же избавился от сеттеров, поскольку кто-то всегда мог выбросить 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 с закрытым конструктором, который принимает имя файла и все остальное, что ему нужно для инициализации, и создать static const членов в MyClass для каждого экземпляра, со значениями, назначенными новому экземпляру MyClass, созданному используя приватный конструктор.

РЕДАКТИРОВАТЬ: Но теперь я вижу сценарий, я не думаю, что это хорошая идея, имея статические значения. Если типы ActivityLevel являются основополагающими для вашего приложения, вы можете перечислить другой тип уровня активности в качестве констант, например, перечисление java или string, но они просто заполнители. Фактические экземпляры ActivityDescription должны исходить от уровня доступа к данным или какого-либо поставщика.

например.

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, или, что еще лучше, Map of ActivityLevel к ActivityDescription, который вы загружаете из файла, выбираете из весеннего конфига и т. Д. Главное, что при использовании Интерфейс для извлечения фактического описания для заданного ActivityLevel отделяет код вашего приложения от механики того, как эти описания создаются в системе. Это также позволяет имитировать реализацию интерфейса при тестировании пользовательского интерфейса. Вы можете подчеркнуть пользовательский интерфейс фиктивной реализацией способами, которые невозможны с фиксированным статическим набором данных.

1 голос
/ 04 июня 2010

(я снова слишком медленный, вы уже приняли ответ, но здесь он все равно ...)

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

Ваш первоначальный пример имеет открытый конструктор, который нарушает (b)

Я бы использовал подход Фабрики, поэтому Фабрика - единственное, что может создавать экземпляры, а класс не предоставляет никаких установщиков, поэтому он неизменен.

В зависимости от того, какую гибкость вы хотите в будущем, вы можете поместить фабрику и класс в один и тот же пакет и ограничить область таким образом, или вы можете сделать MyClass внутренним классом внутри фабрики. Вы также можете рассмотреть возможность сделать MyClass интерфейсом отдельно от его реализации.

Файл свойств можно использовать для настройки самой фабрики. Файл свойств (например, "foo.properties") может выглядеть примерно так:

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

Я использую «Foo» вместо «MyClass» в (Java) примерах ниже.

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

Вы можете разрешить только свои предопределенные экземпляры, которые нельзя изменить во время выполнения, но вы можете настроить их по-другому для другого запуска в более позднее время.

1 голос
/ 04 июня 2010

Теперь давайте скажем, что вы заранее знаете, что когда-нибудь будет только 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();
0 голосов
/ 04 июня 2010

Общий подход заключается в использовании карты:

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

Возможно, использование строк в качестве ключей - не лучший вариант, но вы поняли.

0 голосов
/ 04 июня 2010

Во-первых, вы действительно должны ограничивать использование этих экземпляров в коде. Используйте их как можно меньше мест. Учитывая, что это имена файлов, я ожидаю, что вам нужны три экземпляра класса, которые обращаются к файлам. Сколько классов требуется, зависит от того, что вы хотите с ними делать? Посмотрите на шаблон Singleton для этих классов.

Теперь вам не нужны константы, но у вас может быть вспомогательный класс, который будет считывать файл, содержащий имена файлов, и передавать их классу чтения. Код для поиска имени также может быть методом, вызываемым статическим инициализатором Singleton.

0 голосов
/ 04 июня 2010

Ваш первый метод кажется мне лучшим и наименее склонным к гниению кода. Я не впечатлен идеей создания подкласса объекта просто для изменения имени файла, содержащего данные, которые будут использоваться для его построения.

Конечно, вы могли бы улучшить свою первоначальную идею, обернув все это во внешний класс, который обеспечивает некоторый доступ к перечислению. Другими словами, коллекция MyClass's. Но я думаю, что вы должны отказаться от этой идеи подкласса.

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