Использовать конструктор или метод установки?

16

Я работаю над кодом пользовательского интерфейса, где у меня есть Actionкласс, что-то вроде этого -

public class MyAction extends Action {
    public MyAction() {
        setText("My Action Text");
        setToolTip("My Action Tool tip");
        setImage("Some Image");
    }
}

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

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
}

Однако он считает, что поскольку setText()метод принадлежит базовому классу, его можно гибко использовать для передачи текста действия везде, где создается экземпляр действия. Таким образом, нет необходимости менять существующий MyActionкласс. Так что его код будет выглядеть примерно так.

MyAction action = new MyAction(); //this creates action instance with the hardcoded text
action.setText("User required new action text"); //overwrite the existing text.

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

zswap
источник
1
Язык, который вы используете, не позволяет перегружать конструкторы?
Мат
1
Я использую Java. Так что да, это позволяет, и я думаю, что это может быть одним из способов справиться с этим
zswap
2
Я хотел бы отметить, что, если у вас нет общедоступного способа установить членов класса после факта, ваш класс является практически неизменным . Благодаря общедоступному установщику ваш класс теперь становится изменчивым , и вам, возможно, придется принять это во внимание, если вы зависите от неизменности.
cbojar
Я бы сказал, что если он должен быть установлен для того, чтобы ваш объект был действительным, поместите его в каждый конструктор ... если он необязательный (имеет разумное значение по умолчанию) и вам не нужна неизменность, поместите его в установщик. Должно быть невозможным создание экземпляра вашего объекта в недопустимом состоянии или после создания экземпляра перевести его в недопустимое состояние, где это возможно.
Билл К

Ответы:

15

Единственное преимущество, которое я вижу с оригинальным кодом, заключается в том, что пользователь может создавать класс Action, не задумываясь о настройке текста.

Это на самом деле не является преимуществом, для большинства целей это недостаток, а в остальных случаях я бы назвал это ничьей. Что если кто-то забудет вызвать setText () после создания? Что, если это так в каком-то необычном случае, возможно, в обработчике ошибок? Если вы действительно хотите, чтобы текст был установлен, вы должны принудительно установить его во время компиляции, поскольку только ошибки времени компиляции на самом деле являются фатальными . Все, что происходит во время выполнения, зависит от того, какой конкретный путь к коду выполняется.

Я вижу два ясных пути вперед:

  1. Используйте параметр конструктора, как вы предлагаете. Если вы действительно хотите, вы можете передать nullили пустую строку, но тогда факт, что вы не назначаете текст, является явным, а не неявным. Легко увидеть существование nullпараметра и увидеть, что, возможно, в него была вложена некоторая мысль, но не так легко увидеть отсутствие вызова метода и определить, было ли отсутствие такого намеренным или нет. Для такого простого случая, как этот, я бы выбрал такой подход.
  2. Используйте шаблон фабрики / строителя. Это может быть излишним для такого простого сценария, но в более общем случае он очень гибок, поскольку позволяет устанавливать любое количество параметров и проверять предварительные условия до или во время создания объекта (если создание объекта является большой операцией и / или класс может использоваться более чем одним способом, это может быть огромным преимуществом). В частности, в Java это также распространенная идиома, и следование установленным шаблонам в используемом языке и среде очень редко является плохой вещью.
CVn
источник
10

Перегрузка конструктора была бы простым и понятным решением:

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
    public MyAction() {
        this("My Action Text");
    }
}

Это лучше, чем звонить .setTextпозже, потому что таким образом ничего не нужно перезаписывать, оно actionTextможет быть намеченным с самого начала.

По мере развития вашего кода, и вам понадобится еще больше гибкости (что, безусловно, произойдет), вы извлечете выгоду из паттерна фабрики / сборки, предложенного другим ответом.

Янош
источник
Что происходит, когда они хотят настроить второе свойство?
Кевин Клайн
3
Для 2-го, 3-го, .. свойства, вы можете применить ту же технику, но чем больше свойств вы хотите настроить, тем громче это становится. В какой-то момент будет более разумно реализовать шаблон фабрики / производителя, как сказал @ michael-kjorling в своем ответе.
Янв
6

Добавьте свободный метод setText:

public class MyAction ... {
  ...
  public MyAction setText(String text) { ... ; return this; }
}

MyAction a = new MyAction().setText("xxx");

Что может быть понятнее этого? Если вы решите добавить другое настраиваемое свойство, нет проблем.

Кевин Клайн
источник
+1, я согласен, и добавил еще один ответ, дополняющий больше свойств. Я думаю, что свободный API яснее понять, когда в качестве примера у вас есть более одного свойства.
Мачадо
Мне нравятся свободные интерфейсы, особенно для строителей, которые создают неизменные объекты! Чем больше параметров, тем лучше это работает. Но, глядя на конкретный пример в этом вопросе, я предполагаю, что setText()он определен в классе Action, от которого наследуется MyAction. Вероятно, он уже имеет тип возврата void.
ГленПетерсон
1

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

Это сделает ваш код более читабельным, и, с моей точки зрения, более простым и, ах , «сексуальным» для написания.

В вашем случае все будет так (извините за опечатку, прошел год с тех пор, как я написал свою последнюю Java-программу):

 public class MyAction extends Action {
    private String _text     = "";
    private String _tooltip  = "";
    private String _imageUrl = "";

    public MyAction()
    {
       // nothing to do here.
    }

    public MyAction text(string value)
    {
       this._text = value;
       return this;
    }

    public MyAction tooltip(string value)
    {
       this._tooltip = value;
       return this;
    }

    public MyAction image(string value)
    {
       this._imageUrl = value;
       return this;
    }
}

И использование будет таким:

MyAction action = new MyAction()
    .text("My Action Text")
    .tooltip("My Action Tool tip")
    .image("Some Image");
Мачадо
источник
Плохая идея, что если они забудут установить текст или что-то важное.
Прахар
1

В общем, совет использовать конструкторов или конструкторов, но, по моему опыту, упускает некоторые ключевые моменты для действий, которые

  1. Возможно, нужно быть интернационализированным
  2. Вероятно, что маркетинг изменится в последнюю минуту.

Я настоятельно рекомендую прочитать имя, всплывающую подсказку, значок и т. Д. Из файла свойств, XML и т. Д. Например, для действия «Открыть файл» можно передать свойства и выполнить поиск

File.open.name=Open
File.open.tooltip=Open a file
File.open.icon=somedir/open.jpg

Этот формат довольно легко перевести на французский, попробовать новую лучшую иконку и т. Д. Без времени программиста или перекомпиляции.

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

user949300
источник
0

Бесполезно вызывать setText (actionText) или setTooltip («Моя подсказка действия») внутри конструктора; это проще (и вы получаете больше производительности), если вы просто инициализируете соответствующее поле напрямую:

    public MyAction(String actionText) {
        this.actionText = actionText;
    }

Если вы измените actionText в течение срока службы соответствующего объекта MyAction, вы должны разместить метод установки; если нет, инициализируйте поле только в конструкторе, не предоставляя метод установки.

Поскольку всплывающая подсказка и изображение являются константами, рассматривайте их как константы; есть поля:

private (or even public) final static String TOOLTIP = "My Action Tooltip";

На самом деле, при проектировании общих объектов (не bean-объектов или объектов, представляющих строго структуры данных) плохая идея предоставлять сеттеры и геттеры, поскольку они как бы нарушают инкапсуляцию.

m3th0dman
источник
4
Любой наполовину компетентный компилятор и JITter должны встроить вызовы setText () и т. Д., Поэтому разница в производительности между вызовом функции для выполнения назначения и наличием этого назначения вместо вызова функции должна быть максимально незначительной и более вероятной чем не ноль.
CVn
0

Я думаю, что это правда, если мы собираемся создать общий класс действий (например, update, который используется для обновления Employee, Department ...). Все зависит от сценария. Если создается определенный класс действия (например, обновить сотрудника) (который часто используется в приложении - Обновить сотрудника) с намерением сохранить один и тот же текст, всплывающую подсказку и изображение в каждом месте приложения (с точки зрения согласованности). Таким образом, жесткое кодирование может быть выполнено для текста, всплывающей подсказки и изображения, чтобы предоставить текст по умолчанию, всплывающую подсказку и изображение. Тем не менее, чтобы дать больше гибкости, чтобы настроить их, он должен иметь соответствующие методы установки. Имея в виду только 10% мест, мы должны это изменить. Принятие текста действия каждый раз от пользователя может вызывать разные тексты каждый раз для одного и того же действия. Например, «Обновить Emp», «Обновить сотрудника», «Изменить сотрудника» или «Редактировать сотрудника».

Сэнди
источник
Я думаю, перегруженный конструктор все равно должен решить проблему. Поскольку во всех случаях «10%» вы сначала создадите действие с текстом по умолчанию, а затем измените текст действия с помощью метода «setText ()». Почему бы не установить соответствующий текст при создании действия.
zswap
0

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

Например, если класс MyAction должен быть неизменным после построения (и, возможно, другой инициализации), у него не должно быть метода установки. Если большую часть времени он будет использовать «Текст моего действия» по умолчанию, должен быть конструктор без параметров, плюс конструктор, который допускает необязательный текст. Теперь пользователю не нужно думать, чтобы использовать класс правильно 90% времени. Если пользователь обычно должен задуматься над текстом, пропустите конструктор без параметров. Теперь пользователь вынужден думать, когда это необходимо, и не может пропустить необходимый шаг.

Если MyActionэкземпляр должен быть изменчивым после полной конструкции, тогда вам нужен установщик для текста. Соблазнительно пропустить установку значения в конструкторе (принцип СУХОГО - «Не повторяй себя»), и, если значение по умолчанию обычно достаточно хорошее, я бы это сделал. Но если это не так, требование текста в конструкторе заставляет пользователя думать, когда он должен.

Обратите внимание, что эти пользователи не глупы . У них просто слишком много реальных проблем, о которых нужно беспокоиться. Думая об «интерфейсе» вашего класса, вы можете не допустить, чтобы он стал реальной проблемой - и ненужной.

RalphChapin
источник
0

В следующем предложенном решении суперкласс является абстрактным и для всех трех членов установлено значение по умолчанию.

У подкласса есть различные конструкторы, так что программист может создать его экземпляр.

Если используется первый конструктор, все члены будут иметь значения по умолчанию.

Если используется второй конструктор, вы даете начальное значение члену actionText, оставляя двум другим членам значение по умолчанию ...

Если используется третий конструктор, вы создаете его экземпляр с новым значением для actionText и toolTip, оставляя imageURl со значением по умолчанию ...

И так далее.

public abstract class Action {
    protected String text = "Default action text";
    protected String toolTip = "Default action tool tip";
    protected String imageURl = "http://myserver.com/images/default.png";

    .... rest of code, I guess setters and getters
}

public class MyAction extends Action {


    public MyAction() {

    }

    public MyAction(String actionText) {
        setText(actionText);
    }

    public MyAction(String actionText, String toolTip_) {
        setText(actionText);
        setToolTip(toolTip_);   
    }

    public MyAction(String actionText, String toolTip_; String imageURL_) {
        setText(actionText);
        setToolTip(toolTip_);
        setImageURL(imageURL_);
    }


}
Тулаинс Кордова
источник