Авторы платформы сами считают ApplicationException бесполезным:
http://blogs.msdn.com/kcwalina/archive/2006/06/23/644822.aspx
с хорошим продолжением здесь:
http://blogs.msdn.com/kcwalina/archive/2006/07/05/657268.aspx
, Когда в сомнении, я следую их книжному Руководству по проектированию Платформы.
http://www.amazon.com/Framework-Design-Guidelines-Conventions-Development/dp/0321246756
тема сообщения в блоге далее обсуждена там.
армированный пластик
Решение от developpez.com Да, вы можете оптимизировать его и сделать его намного чище. Вы не можете использовать такую «Цепочку ответственности» с Factory:
public class ProcessFactory {
private ArrayList<Process> processses = null;
public ProcessFactory(){
super();
processses = new ArrayList<Process>();
processses.add(new ProcessC1());
processses.add(new ProcessC2());
processses.add(new ProcessC3());
processses.add(new ProcessC4());
processses.add(new ProcessC5(6));
processses.add(new ProcessC5(22));
}
public Process getProcess(int var, int subvar){
for(Process process : processses){
if(process.canDo(var, subvar)){
return process;
}
}
return null;
}
}
Тогда так же, как ваши процессы реализуют интерфейсный процесс с canXXX, вы можете легко использовать:
new ProcessFactory().getProcess(var,subvar).launch();
Возможно, вам нужна генерация кода?
#! /usr/bin/python
first = [1, 2, 3]
second = ['a', 'b', 'c']
def emit(first, second):
result = "switch (var)\n{\n"
for f in first:
result += " case {0}:\n switch (subvar)\n {{\n".format(f)
for s in second:
result += " case {1}:\n process {1}{0};\n".format(f,s)
result += " }\n"
result += "}\n"
return result
print emit(first,second)
#file("autogen.c","w").write(emit(first,second))
Это, конечно, довольно сложно читать, и вам действительно может понадобиться более хороший язык шаблонов для выполнения вашей грязной работы, но это облегчит некоторые части вашей задачи.
Похоже, что вам нужна «Машина конечных состояний» , где с помощью этих случаев вы можете активировать различные процессы или «состояния». В C это обычно делается с помощью массива (матрицы) указателей на функции.
Таким образом, вы, по сути, создаете массив и помещаете правильные указатели функций в правильные указатели, а затем используете свою 'var' в качестве индекса справа ' процесс ', а затем вы его вызываете. Вы можете сделать это на большинстве языков. Таким образом, разные входы в машину активируют разные процессы и переводят ее в разные состояния. Это очень полезно для множества приложений; Я сам постоянно использую его при разработке MCU.
Edit: Валя указала, что мне, наверное, стоит показать базовую модель:
stateMachine[var1][var2](); // calls the right 'process' for input var1, var2
Это неплохое решение, но, как всегда, есть альтернативы. Например, вы можете избавиться от оператора switch и использовать интерпретируемый обработчик для строк. Это похоже на список указателей на функции, но вам не нужно обновлять список, чтобы добавить новое поведение; об этом позаботится простое добавление новой функции в обработчик.
$array = array(
"something" => "itsasecret",
"somethingelse" => "i can't tell you",
);
class Handler {
static function something($value) {
printf("something: %s\n", $value);
}
static function somethingelse($value) {
printf("somethingelse: %s\n", $value);
}
}
$handler = new Handler();
foreach($array as $key => $value) {
$handler->$key($value);
}
Возможно, вам понадобится код для очистки входных строк и обеспечения существования метода в вашем обработчике, но это может дать вам некоторые идеи.
да, возможно, проблема. По мере увеличения числа переменных и уровня начинается комбинаторный взрыв, а также формат оператора switch имеет тенденцию распространять точку ветвления и связанные с ним значения на длинном вертикальном участке. В этом случае таблица с 3 (или более) измерениями, инициализированная с помощью функции fct. указатели объединяют значения ветвления и функцию, которая должна быть вызвана, в одной строке.// This is positively more compact / readable
...
FctMap[1][4][0] = fctAlphaOne;
FctMap[1][4][1] = fctAlphaOne;
..
FctMap[3][0][0] = fctBravoCharlie4;
FctMap[3][0][1] = NULL; // impossible case
FctMap[3][0][2] = fctBravoCharlie4; // note how the same fct may serve in mult. places
И относительно простого фрагмента, в котором должны быть вызваны функции:
if (FctMap[cond1][cond2][cond3]) {
retVal = FctMap[cond1][cond2][cond3](Arg1, Arg2);
if (retVal < 0)
DoSomething(); // anyway we're leveraging the common api to these fct not the switch alternative ....
}
Случай, который может вызвать один НЕ с использованием вышеприведенного решения, - это , если комбинация пространство относительно мало заполнено (многие «ветви» в «дереве» переключателя не используются) или , если некоторые функции требуют другого набора параметров ; Для обоих этих случаев я хотел бы подключить решение, предложенное Джоэлом Гудвином первым здесь , которое по сути объединяет различные ключи для нескольких уровней в один более длинный ключ (с разделителем, если необходимо), по существу сглаживает проблему до длинного, но одноуровневого оператора переключения .
Настоящая дискуссия должна быть о , зачем нам такое отображение / дерево решений в первую очередь . К сожалению, чтобы ответить на этот вопрос, необходимо понять истинную природу основного приложения. Конечно, я не говорю, что это признак плохого дизайна. В некоторых приложениях может иметь смысл большой раздел диспетчеризации. Однако даже с языком C (который, по-видимому, авторы французского сайта не допустили к объектно-ориентированному дизайну), можно принять объектно-ориентированную методологию и шаблоны. В любом случае, я расходлюсь ...) Возможно, что приложение в целом будет лучше обслуживаться с помощью альтернативных шаблонов проектирования, где «информационное дерево о том, что и когда вызывать» был распределен в нескольких модулях и / или нескольких объектах.
Прошу прощения, если говорить об этом в довольно абстрактных терминах, это просто отсутствие специфики приложения ... Суть остается: оспорить идею о том, что нам нужно это большое дерево диспетчеризации; подумайте об альтернативных подходах к применению в целом.
Alors, bonne Chance! ; -)
Идея указателя на функцию, вероятно, лучшая (согласно mjv, Shhnap). Но если код в каждом случае довольно мал, он может быть излишним и привести к большему запутыванию, чем предполагалось. В этом случае я мог бы реализовать что-нибудь быстрое и быстро читаемое, например следующее:
string decision = var1.ToString() + var2.ToString() + var3.ToString();
switch(decision)
{
case "1aa":
....
case "1ab":
....
}
Незнаком с вашим конкретным сценарием, поэтому, возможно, предыдущие предложения более подходят.
В зависимости от языка, некоторая форма хэш-карты с парой (var, subvar)
в качестве ключа и первоклассными функциями в качестве значений (или независимо от вашего языка предлагает наилучшее приближение, например, экземпляры классов, расширяющих некоторый правильный интерфейс в Java), вероятно, обеспечат максимальную производительность - и полную лаконичность выборки соответствующей функции (или чего-то еще ;-) с карты на основе ключа и выполнения это обеспечивает удобство чтения для читателей, знакомых с языком и такими функциональными идиомами.
I had exactly the same problem once, albeit for an immanent mess of a 5-parameter nested switch. I figured, why type all these O(N5) cases myself, why even invent 'nested' function names if the compiler can do this for me. And all this resulted in a 'nested specialized template switch' referring to a 'specialized template database'.
It's a little complicated to write. But I found it worth it: it results in a 'knowledge' database that is very easy to maintain, to debug, to add to etc... And I must admit: a sense of pride.
// the return type: might be an object actually _doing_ something
struct Result {
const char* value;
Result(): value(NULL){}
Result( const char* p ):value(p){};
};
Some variable types for switching:
// types used:
struct A { enum e { a1, a2, a3 }; };
struct B { enum e { b1, b2 }; };
struct C { enum e { c1, c2 }; };
A 'forward declaration' of the knowledge base: the 'api' of the nested switch.
// template database declaration (and default value - omit if not needed)
// specializations may execute code in stead of returning values...
template< A::e, B::e, C::e > Result valuedb() { return "not defined"; };
The actual switching logic (condensed)
// template layer 1: work away the first parameter, then the next, ...
struct Switch {
static Result value( A::e a, B::e b, C::e c ) {
switch( a ) {
case A::a1: return SwitchA<A::a1>::value( b, c );
case A::a2: return SwitchA<A::a2>::value( b, c );
case A::a3: return SwitchA<A::a3>::value( b, c );
default: return Result();
}
}
template< A::e a > struct SwitchA {
static Result value( B::e b, C::e c ) {
switch( b ) {
case B::b1: return SwitchB<a, B::b1>::value( c );
case B::b2: return SwitchB<a, B::b2>::value( c );
default: return Result();
}
}
template< A::e a, B::e b > struct SwitchB {
static Result value( C::e c ) {
switch( c ) {
case C::c1: return valuedb< a, b, C::c1 >();
case C::c2: return valuedb< a, b, C::c2 >();
default: return Result();
}
};
};
};
};
And the knowledge base itself
// the template database
//
template<> Result valuedb<A::a1, B::b1, C::c1 >() { return "a1b1c1"; }
template<> Result valuedb<A::a1, B::b2, C::c2 >() { return "a1b2c2"; }
This is how it can be used.
int main()
{
// usage:
Result r = Switch::value( A::a1, B::b2, C::c2 );
return 0;
}
Да, определенно есть более простой способ сделать это, как быстрее , так и проще . Идея в основном такая же, как и у Алекса Мартелли. Вместо того, чтобы рассматривать вашу проблему как двумерную, рассматривайте ее как некую одномерную таблицу поиска.
Это означает объединение переменных, подварок, подварок и т. Д. Для получения одного уникального ключа и использования его в качестве точки входа в вашу таблицу поиска.
Способ сделать это зависит от используемого языка. С python, объединяющим var, subvar и т. Д., Чтобы построить кортеж и использовать его в качестве ключа в словаре, достаточно.
С C или подобным обычно проще преобразовать каждый ключ в перечисления, а затем объединить их с помощью логических операторов, чтобы получить только один число, которое вы можете использовать в своем переключателе (это также простой способ использовать переключатель вместо сравнения строк с каскадными if). Вы также получаете еще одно преимущество. Обычно несколько обработок в разных ветвях начального переключателя одинаковы. С начальной формой это довольно сложно сделать очевидным. Вероятно, у вас будет несколько вызовов одних и тех же функций, но в разных точках кода. Теперь вы можете просто сгруппировать идентичные случаи при написании переключателя.
Я использовал такое преобразование несколько раз в производственном коде, и его легко выполнять и поддерживать.
В общем, вы можете получить что-то вроде этого ... функция смешивания очевидно, зависит от особенностей вашего приложения.
switch (mix(var, subvar))
{
case a1:
process a1;
case b1:
process b1;
case c1:
process c1;
case a2:
process a2;
case b2:
process b2;
case c2:
process c2;
case a3:
process a3;
case b3:
process b3;
case c3:
process c3;
}
Если C ++ - вариант, я бы попробовал использовать виртуальную функцию и, возможно, двойная отправка. Это могло бы сделать его намного чище. Но это, вероятно, окупится только в том случае, если у вас будет еще много дел.
Эта статья на DDJ.com может быть хорошей записью.
Если вы просто пытаетесь исключить двухуровневые операторы switch / case (и сэкономить немного места по вертикали), вы можете закодировать два значения переменных в одно значение, а затем включить его :
// Assumes var is in [1,3] and subvar in [1,3]
// and that var and subvar can be cast to int values
switch (10*var + subvar)
{
case 10+1:
process a1;
case 10+2:
process b1;
case 10+3:
process c1;
//
case 20+1:
process a2;
case 20+2:
process b2;
case 20+3:
process c2;
//
case 30+1:
process a3;
case 30+2:
process b3;
case 30+3:
process c3;
//
default:
process error;
}
Если ваш язык - C #, и ваш выбор достаточно короткий и не содержит специальных символов, вы можете использовать отражение и сделать это с помощью всего нескольких строк кода. Таким образом, вместо того, чтобы вручную создавать и поддерживать массив указателей на функции, используйте тот, который предоставляет платформа!
Примерно так:
using System.Reflection;
...
void DispatchCall(string var, string subvar)
{
string functionName="Func_"+var+"_"+subvar;
MethodInfo m=this.GetType().GetMethod(fName);
if (m == null) throw new ArgumentException("Invalid function name "+ functionName);
m.Invoke(this, new object[] { /* put parameters here if needed */ });
}
void Func_1_a()
{
//executed when var=1 and subvar=a
}
void Func_2_charlie()
{
//executed when var=2 and subvar=charlie
}