Одна техника для использования должна разработать код модульного теста с C++ xUnit платформа (и компилятор C++) при поддержании источника для целевой системы как C модули.
Удостоверяются, что Вы регулярно компилируете свой источник C в соответствии с Вашим кросс-компилятором, автоматически с Вашими модульными тестами, если это возможно.
Вот лучшая из множества миров реализация.
// Generic interface for when a client doesn't care
// about the return value of a command.
public interface Command {
// The interfaces themselves take a String[] rather
// than a String... argument, because otherwise the
// implementation of AbstractCommand<T> would be
// more complicated.
public void execute(String[] arguments);
}
// Interface for clients that do need to use the
// return value of a command.
public interface ValuedCommand<T> extends Command {
public T evaluate(String[] arguments);
}
// Optional, but useful if most of your commands are ValuedCommands.
public abstract class AbstractCommand<T> implements ValuedCommand<T> {
public void execute(String[] arguments) {
evaluate(arguments);
}
}
// Singleton class with utility methods.
public class Commands {
private Commands() {} // Singleton class.
// These are useful if you like the vararg calling style.
public static void execute(Command cmd, String... arguments) {
cmd.execute(arguments);
}
public static <T> void execute(ValuedCommand<T> cmd, String... arguments) {
return cmd.evaluate(arguments);
}
// Useful if you have code that requires a ValuedCommand<?>
// but you only have a plain Command.
public static ValuedCommand<?> asValuedCommand(Command cmd) {
return new VoidCommand(cmd);
}
private static class VoidCommand extends AbstractCommand<Void> {
private final Command cmd;
public VoidCommand(Command actual) {
cmd = actual;
}
public Void evaluate(String[] arguments) {
cmd.execute(arguments);
return null;
}
}
}
С этой реализацией клиенты могут говорить о Команде
, если им все равно о возвращаемом значении и ValuedCommand
, если требуется команда, возвращающая определенное значение.
Единственная причина, по которой не следует сразу использовать Void
, - это все неприглядные возвращают null;
утверждения, которые вам придется вставить.
Я бы предпочел явно использовать Void
. Легко увидеть, что происходит, без участия другого класса. Было бы неплохо, если бы вы могли переопределить возврат Void
с помощью void
(и Integer
с помощью int
и т. Д.), Но это не приоритет.
Не изменяйте интерфейс: вот почему Void находится в стандартной библиотеке. До тех пор, пока все, что вызывает Команду, ожидает возврата значений NULL
И да, NULL - единственное значение, которое вы можете вернуть для Void.
В течение последних нескольких лет я избегал Void
как возвращаемых типов, за исключением случаев, когда речь идет об отражении. Я использовал другой шаблон, который, на мой взгляд, более явный , а избегает нулей. То есть у меня есть тип успеха, который я называю Ok
, который возвращается для всех команд, таких как OP. Это очень хорошо сработало для моей команды, а также распространилось на другие команды.
public enum Ok { OK; }
public class SideEffectCommand implements Command<Ok> {
@Override
public Ok execute(String... args) {
...
return Ok.OK; // I typically static import Ok.OK;
}
В вашем примере интересно использование параметризованных типов. Обычно у вас будет
interface Command<T> {
public T execute(T someObject);
}
В вашем случае у вас будет только T
в качестве возвращаемого значения. Тем не менее использование Void в этом случае - хорошее решение. Возвращаемое значение должно быть null
.
Эта проблема не та обычная, но и не такая уж редкая ... Думаю, я видел обсуждение этого некоторое время назад, о вызываемых объектах, которые ничего не возвращают.
Я согласен с другими авторами в том, что это хорошее решение, намного лучше, чем использование Object или другого фиктивного заполнителя.
Мне кажется, это нормально. Как говорили другие, Void
изначально был разработан для механизма отражения, но теперь он довольно часто используется в Generics для описания ситуаций, подобных вашей.
Еще лучше: Google в своей структуре GWT использует та же идея в их примерах для обратного вызова, возвращающего пустоту (пример здесь ). Я говорю: если Гугл это делает, должно быть как минимум ОК .. :)
Это не обычная проблема. Проблема, которую вам нужно решить, - это ожидания вашего интерфейса. Вы комбинируете поведение интерфейса без побочных эффектов с интерфейсом, допускающим побочные эффекты.
Учтите следующее:
public class CommandMonitor {
public static void main(String[] args) {
Command<?> sec = new SideEffectCommand();
reportResults(sec);
}
public static void reportResults(Command<?> cmd){
final Object results = cmd.execute("arg");
if (results != null ) {
System.out.println(results.getClass());
}
}
}
Нет ничего плохого в использовании
в качестве типа шаблона, но разрешение его смешивания с реализациями для «Command
» означает, что некоторые клиенты интерфейса могут не ожидать недействительного результата. Не меняя интерфейс, вы позволили реализации создать неожиданный результат.
Когда мы передаем наборы данных с помощью классов Collection, моя команда согласилась никогда никогда не возвращать null, даже если синтаксически это хорошо. Проблема заключалась в том, что классы, которые использовали возвращаемые значения, должны были постоянно проверять пустоту, чтобы предотвратить NPE. Используя приведенный выше код, вы увидите это повсюду:
if (results != null ){
Потому что теперь есть способ узнать, действительно ли реализация имеет объект или имеет значение null. В конкретном случае, конечно, вы знаете, потому что вы знакомы с реализацией. Но как только вы начнете их агрегировать или они выйдут за пределы вашего горизонта кодирования (используются в качестве библиотеки, для дальнейшего обслуживания и т. Д.), Возникнет проблема с нулевым значением.
Затем я попробовал следующее:
public class SideEffectCommand implements Command<String> {
@Override
public String execute(String... args) {
return "Side Effect";
}
}
public class NoSideEffectCommand implements Command<Void>{
@Override
public Void execute(String... args) {
return null;
}
}
public class CommandMonitor {
public static void main(String[] args) {
Command<?> sec = new SideEffectCommand();
Command<?> nsec = new NoSideEffectCommand();
reportResults(sec.execute("args"));
reportResults(nsec.execute("args")); //Problem Child
}
public static void reportResults(Object results){
System.out.println(results.getClass());
}
public static void reportResults(Void results){
System.out.println("Got nothing for you.");
}
}
Overloading didn ' t работают, потому что второй вызов reportResults по-прежнему вызывает версию, ожидающую Object (конечно). Я подумывал перейти на
public static void reportResults(String results){
, но это проиллюстрировало корень проблемы: ваш клиентский код начинает знать детали реализации. Предполагается, что интерфейсы помогают изолировать зависимости кода, когда это возможно. Добавление их в этот момент кажется плохим дизайном.
Суть в том, что вам нужно использовать дизайн, который ясно дает понять, когда вы ожидаете, что команда будет иметь побочный эффект, и продумать, как вы будете обрабатывать набор команд, то есть массив неизвестных команд.
Это может быть случай дырявых абстракций .
Что, если бы вместо интерфейса типа:
public interface Command<T> {
T execute(String... args);
}
у вас было бы:
public interface Command<T> {
void execute(String... args);
T getResult();
bool hasResult();
}
Тогда вызывающие абоненты сделали бы:
public void doSomething(Command<?> cmd) {
cmd.execute(args);
if(cmd.hasResult()) {
// ... do something with cmd.getResult() ...
}
}
Вы также можете создать интерфейс VoidCommmand, который расширяет Command, если вы вроде.
Это кажется мне самым чистым решением.