根据anti-if campaign,最好不要在我们的代码中使用ifs。任何人都可以告诉我是否有可能摆脱这段代码中的if? (切换也不是一个选项,重点是删除条件逻辑,而不是用类似的语言结构替换ifs )
if(s == "foo")
{
Writeln("some logic here");
}
else if(s == "bar")
{
Writeln("something else here");
}
else if(s == "raboof")
{
Writeln("of course I need more than just Writeln");
}
(语言:Java或C#)
答案 0 :(得分:64)
这是一种方式...... :)
delegate void DoStuff();
...
IDictionary<string, DoStuff> dict = new Dictionary<string, DoStuff>();
dict["foo"] = delegate { Console.WriteLine("some logic here"); };
dict["bar"] = delegate { Console.WriteLine("something else here"); };
dict["raboof"] = delegate { Console.WriteLine("of course I need more than just Writeln"); };
dict["foo"]();
答案 1 :(得分:18)
建立关联数据结构。 Java中的Map<String, String>
,C#中的IDictionary<string, string>
。在开始时初始化它,然后......
答案 2 :(得分:17)
用Java术语:
public interface Strategy {
void execute();
}
public class SomeStrategy implements Strategy {
public void execute() {
System.out.println("Some logic.");
}
}
您使用如下:
Map<String, Strategy> strategies = new HashMap<String, Strategy>();
strategies.put("strategyName1", new SomeStrategy1());
strategies.put("strategyName2", new SomeStrategy2());
strategies.put("strategyName3", new SomeStrategy3());
// ...
strategies.get(s).execute();
答案 3 :(得分:12)
使用从抽象基类SomeThingWriter派生的虚方法编写类。
然后从基类派生的每个类都应该实现像writeSomething或任何你想要的函数。
abstract class MyBaseClass
{
public abstract void writeSomething();
}
class DerivedClass1 : MyBaseClass
{
public override void writeSomething()
{
Writeln("something else here 1");
}
}
class DerivedClass2 : MyBaseClass
{
public override void writeSomething()
{
Writeln("something else here 2");
}
}
而不仅仅是打电话
MyBaseClass c = new DeriveClass1();
c.writeSomething();
c = new DerivedClass2();
c.writeSomething();
答案 4 :(得分:12)
观看广告系列时,解释得非常糟糕。 ifs没有任何问题,但在某些情况下它们可以表明你没有充分利用OOP。
广告系列试图推广的是增加多态性的使用,以便将调用代码与其正在查看的对象类型分离。
你会使用一些更聪明的对象,而不是s是一个字符串:
interface I {
public String getName();
public void doSomething();
}
class A implements I {
public String getName() { return "one"; }
public void doSomething() { ...; }
}
class B implements I {
public String getName() { return "two"; }
public void doSomething() { ...; }
}
然后你可以用if:
替换ifsI obj = ...get an A or B from somewhere...;
obj.doSomething();
答案 5 :(得分:7)
在某些情况下,避免使用if结构可能是合法的
在其他人看来,只是为了避免if。
虽然为避免if结构而提供的示例是有效的替代方案,但您应该问自己:
为什么我的代码不必要复杂,以避免简单的if结构? 如果唯一的原因是你必须因为反竞选活动那么它的坏理由
答案 6 :(得分:7)
<强>爪哇强>
使用实现某种方法的枚举。
enum MyEnum{
foo{
public void mymethod(String param1, String param2){
//dostuff...
}
},
bar{
public void mymethod(String param1, String param2){
//dostuff...
}
};
public abstract void mymethod(String param1, String param2);
}
然后在你的课堂上:
MyEnum.valueOf(mystring).mymethod(param1, param2);
答案 7 :(得分:5)
首先,在阅读此类“反”广告系列时,要非常专注。
C#
switch (myStringVar)
{
case "one": doSomething(); break;
case "two": doSomething(); break;
case "three": doSomething(); break;
default: doSomething(); break;
}
最后,它将此代码缩减为 if ...因此,只是为了提高可读性,而不是为了提高性能。
实际上,如果微软认为交换机(在c#中)最好用if替换 - 好的,我将使用(在您描述的具体情况下)开关。
顺便说一句,该广告系列似乎非常明确地回答了您的问题in this example
答案 8 :(得分:4)
你给出的例子我不会改变(虽然我猜你意识到它不需要改变) - 我猜你是用它作为一个代表性的例子。
在Fowler的Refactoring book中,他讨论了Replace Conditional with Polymorphism.这就是我认为替换if / switch语句(如果适用)的好用。
答案 9 :(得分:4)
我想指出,到目前为止,使用代码示例对此问题的每个答案都有一个比原始代码复杂得多的解决方案,并且可能要慢得多。
这是在完全错误的上下文中执行优化的经典案例。在某些情况下,通过正确使用OO,代码将变得更加清晰,例如消除长链类型检查。但是,只是删除所有if语句只是为了删除它们只会混淆代码。
if语句(条件跳转)仍将发生,无论是在您的代码中还是在解释器中。使它们在词汇上保持密切关系具有许多可读性和维护优势,这些优点在过度使用OO时会丢失。必须在本地逻辑与远程逻辑之间取得平衡,但它永远不会溢出到混淆中。
对于手头的问题,避免if
的最清晰的构造可能是包含匿名函数的散列表/关联数组,对于少量的键,它实际上只是一个慢的switch语句。
答案 10 :(得分:3)
我认为您正在寻找Factory Patterns。
答案 11 :(得分:3)
我不认为你在这里进行公平的比较。
从反面来看,Anti-if活动只是为了实现更好的设计方法。
但是在您的情况下,您可以从上面的所有示例中看到,如果无法从曲面移除,并且始终存在于中心的某个位置。
为什么会这样?
那么如果是生活的一般目的。我并不是说如果每个地方都开始编码,但一般情况下如果没有区别,如果带来决定和目的,如果不存在则那么世界上的每个对象都只会执行它的假设甚至不知道除此之外的其他任何东西。很简单,你不会问这个问题。 :)
答案 12 :(得分:1)
您可以设想使用Method调用的地图代替上面的“策略”模式:
public class FooOrBar {
private Map<String, Method> methodMap = new HashMap<String, Method>();
public FooOrBar() {
try {
methodMap.put("foo", this.getClass().getMethod("doFoo", new Class[0]));
methodMap.put("bar", this.getClass().getMethod("doBar", new Class[0]));
} catch (NoSuchMethodException n) {
throw new RuntimeException(n);
}
}
public void doSomething(String str) {
Method m = methodMap.get(str);
try {
m.invoke(this, null);
} catch (Exception n) {
throw new RuntimeException(n);
}
}
public void doFoo() {
System.out.println("foo");
}
public void doBar() {
System.out.println("bar");
}
public static void main(String[] args) {
FooOrBar fb = new FooOrBar();
fb.doSomething("foo");
}
}
答案 13 :(得分:1)
至少在C#中滥用三元运算符:
Action result =
s == "bar" ? (Action)(() => { Console.WriteLine("bar"); }):
s == "foo" ? (Action)(() => { Console.WriteLine("foo"); }) :
(Action)(() => { Console.WriteLine(); });
实际上,我接受了......从来没有这样做过。使用开关。
答案 14 :(得分:1)
我读过http://www.antiifcampaign.com/articles/the-simplest-anti-if-code.html,我认为这种药比疾病更糟糕。更糟糕的是,更糟糕的是。您需要在一些重型OO机器上预先投资,以解决可能的(不可能的?)未来问题。
答案 15 :(得分:1)
派对有点晚了,但结合来自MRFerocius和cletus的C#字典答案给出了bmargulies答案的以下实现:
private Dictionary<string,Action> data = new Dictionary<string, Action> {
{"foo", () => Console.WriteLine("Some logic here")},
{"bar", () => Console.WriteLine("something else here")},
{"raboof", () => Console.WriteLine("of course I need more than just WriteLine")},
}
public static void main(String[] args) {
data["foo"]();
}
可以组成多个动作:
使用多行lambda语法可以多次调用不同的方法:
{"foobar", () => {
data["foo"]();
data["bar"]();
}
由于Action
是委托类型,因此可以将多个方法附加到单个委托实例,并将该委托实例设置为值;调用委托时将按顺序调用它们:
public static void main(String[] args) {
data["foobar"] = data["foo"] + data["bar"];
//This will invoke first data["foo"] then data["bar"]
data["foobar"]();
}
对于未通过字典引用的方法,也可以在集合初始化程序中完成:
{"foobar", (Action)method1 + method2}
答案 16 :(得分:0)
这是我的。使用LINQ和工厂模式:D
class FactoryString
{
static FactoryString()
{
private static Dictionary<string, string> dictionary = new Dictionary<string, string>
{
{"foo", "some logic here"},
{"bar", "something else here"},
{"raboof", "of course I need more than just Writeln"},
};
}
public static string getString(string s)
{
return dictionary.Single(x => x.Key.Equals(s)).Value;
}
}
static void main()
{
Console.WriteLine(FactoryString.getString("foo"));
}
答案 17 :(得分:0)
我对这类问题的一般看法并不是说如果陈述不好,那就是调试数据比调试代码更容易。
这是生产代码中的一个非常重要的例子。乍一看这可能看起来有点复杂,但其核心很简单:根据收费行上的处理代码,我们需要对其相关句子行执行更新。但是我们选择不同的句子行,并针对不同的处理代码对它们执行不同类型的更新。
这是一个相对简单的例子 - 只有五个处理代码,两个测试和两种类型的更新。即便如此,这比它所取代的要简单得多。此外,通过查看代码执行需求说它应该执行的操作,更容易分辨,因为代码中的映射对应于需求文档中的表。 (在我编写这段代码之前,我不得不重写需求文档,以便这些东西都在一个表中定义。原始代码是一团糟,因为要求也是一团糟。重写需求,使它们更清晰暴露在要求也是如此。)
值得强调的是,编写覆盖100%此代码的单元测试非常容易。值得强调的是,此代码的复杂性与它支持的处理代码,谓词和更新的数量呈线性关系;如果使用case或if语句,它将以指数方式扩展。
/// <summary>
/// Update a sentence's status to Completed [401110]
/// </summary>
/// <param name="senRow"></param>
/// <param name="eventDate"></param>
private static void CompleteSentence(DataRow senRow, DateTime eventDate)
{
senRow.SetField("SenStatus", "401110");
senRow.SetField("SenStatusDate", eventDate);
}
/// <summary>
/// Update a sentence's status to Terminated [401120]
/// </summary>
/// <param name="senRow"></param>
/// <param name="eventDate"></param>
private static void TerminateSentence(DataRow senRow, DateTime eventDate)
{
senRow.SetField("SenStatus", "401120");
senRow.SetField("SenStatusDate", eventDate);
}
/// <summary>
/// Returns true if a sentence is a DEJ sentence.
/// </summary>
/// <param name="senRow"></param>
/// <returns></returns>
private static bool DEJSentence(DataRow senRow)
{
return Api.ParseCode(senRow.Field<string>("SenType")) == "431320";
}
/// <summary>
/// Returns true if a sentence is a Diversion sentence.
/// </summary>
/// <param name="senRow"></param>
/// <returns></returns>
private static bool DiversionSentence(DataRow senRow)
{
return Api.ParseCode(senRow.Field<string>("SenType")).StartsWith("43");
}
/// <summary>
/// These are predicates that test a sentence row to see if it should be updated
/// if it lives under a charge disposed with the specified disposition type.
///
/// For instance, if the PDDispositionCode is 413320, any DEJ sentence under the
/// charge should be updated.
/// </summary>
private static readonly Dictionary<string, Func<DataRow, bool>> PDSentenceTests =
new Dictionary<string, Func<DataRow, bool>>
{
{"411610", DiversionSentence}, // diversion successful
{"413320", DEJSentence}, // DEJ successful
{"442110", DiversionSentence}, // diversion unsuccessful
{"442111", DiversionSentence}, // diversion unsuccessful
{"442112", DiversionSentence}, // diversion unsuccessful
{"442120", DEJSentence} // DEJ unsuccessful
};
/// <summary>
/// These are the update actions that are applied to the sentence rows which pass the
/// sentence test for the specified disposition type.
///
/// For instance, if the PDDispositionCode is 442110, sentences that pass the sentence
/// test should be terminated.
/// </summary>
private static readonly Dictionary<string, Action<DataRow, DateTime>> PDSentenceUpdates =
new Dictionary<string, Action<DataRow, DateTime>>
{
{"411610", CompleteSentence}, // diversion successful (completed)
{"413320", CompleteSentence}, // DEJ successful (completed)
{"442110", TerminateSentence}, // diversion unsuccessful (terminated)
{"442111", TerminateSentence}, // diversion unsuccessful (terminated)
{"442112", TerminateSentence}, // diversion unsuccessful (terminated)
{"442120", TerminateSentence} // DEJ unsuccessful (terminated)
};
private void PDUpdateSentencesFromNewDisposition()
{
foreach (DataRow chargeRow in PDChargeRows
.Where(x => PDSentenceTests.ContainsKey(x.Field<string>("PDDispositionCode"))))
{
string disp = chargeRow.Field<string>("PDDispositionCode");
foreach (DataRow s in CHGRows[chargeRow]
.ChildRows("CAS-SUBCRM-CHG-SEN")
.Where(x => PDSentenceTests[disp](x)))
{
PDSentenceUpdates[disp](s, EventDate);
}
}
}