比 if(something) Do It() else Don't() 更好的架构

A better architecture then if(something) DoIt() else Dont()

我正在尝试创建一种机制,允许应用程序决定(在运行时)是否执行某些功能。

"Some Functionality"可以是任何东西,可以是几个dll中的几个类中包含的c#代码,可以是UI,可以是数据库查询执行等.

最重要的是,它应该适合我现有的基础架构,我无法从头开始重新设计和构建。

我想得越多,似乎我可以使用的唯一解决方案就是保存一些 table,这将是 "functionality repository",它会告诉(通过唯一键)如果功能开启/关闭。

然后在代码中,我必须在处理此类功能的每个位置放置一个 if else 语句。

例如

If(functionalityEnabled)?
DoFunctionality()
Else
DoTheUsusal()

有没有更好的实现方式或者更好的设计?我想让解决方案尽可能简单,但另一方面,这个解决方案真的很难看,最终会使我的代码看起来像意大利面条代码。

我们将不胜感激您的想法, 我将 c# 与 sql 服务器、web api 一起用于 Web 服务。

编辑:

我想说,我很感谢大家花时间和精力回答我的问题,你们提出了一些非常有趣的想法。 我最终标记了@dasblinkenlight 答案,因为它最适合需要,尽管这里的其他答案非常好,可能对其他人有用。

谢谢。

如果只是单一条件,那么您别无选择,只能使用 if else 并且非常适合单一条件。

如果你有超过1个条件,你可能会想到使用Switch语句。

如果你担心你的代码会因为 if else 语句而显得复杂,请将你的代码放在函数中,

if(condition)
{
  DoThis();
}
else
{
  DoSomethingElse();
}

如果您有两个实现相同接口的 class,您的应用程序可以调用 class 的功能(方法、属性)而无需确切知道它是在调用基本功能还是替代功能:

IFunctionalityX {
  DoIt();
}

class BasicFunctionalityX: IFunctionalityX {
  public DoIt() {
    // Default behaviour goes here
  }
}

class PluginFunctionalityX: IFunctionalityX {
  public DoIt() {
    // Alternative functionality.
  }
}

如果 PluginFunctionalityX 与 BasicFunctionalityX 共享其部分实现,您可以从另一个继承它,但是否这样做并不重要。只要能用接口就行,不管class有没有关系,都可以用这个方法

在你的程序初始化时,你可以做出一次决定并创建一个正确的实例class。您可以将此 class 存储在某个包含所有功能的容器中。 FunctionalityX 是接口 IFunctionalityX 的 属性,您可以为其他功能创建其他接口(和属性)。

if (functionalityXEnabled) {
  FunctionalityContainer.FunctionalityX = new PluginFunctionality();
} else {
  FunctionalityContainer.FunctionalityX = new BasicFunctionality();
}

然后,在您的应用程序的其余部分,您可以通过以下方式调用您的功能:

FunctionalityContainer.FunctionalityX.DoIt();

您可以使用 依赖注入 库,而不是从头开始实施,例如 Unity。这还允许您在需要时更轻松地获取正确功能的实例,而不必在程序开始时创建它们,也无需为所有功能编写详尽的构造函数代码。

OO 程序员中曾经流行的一个俏皮话是,代码中的每个条件都表明错过了子 class 的机会。尽管这条规则远非普遍适用,而且在组合方面也有不足,但它有一定道理,尤其是当你看到相同的条件在多个 ifs 中出现在不同的方法中时相同 class.

像这样处理 if 的一种常见方法是使用继承和组合的某种组合,并将决策移至创建对象的单个位置。

继承方式是这样的:

interface Doer {
    void doSomething();
}
class BasicDoer implements Doer {
    public void doSomething() {
        ...
    }
}
class EnhancedDoer extends BasicDoer {
    public void doSomething() {
        base.doSomething();
        ...
    }
}

// At construction time:

Doer doer;
if (someCondition)
    doer = new BasicDoer();
else
    doer = new EnhancedDoer();

组合方式是这样的:

interface Doer {
    void doSomething();
}
// Create several implementations of Activity, then...

// At construction time:
List<Doer> doers = new ArrayList<>();
if (someCondition1)
    doers.add(new SomeKindOfDoer());
if (someCondition2)
    doers.add(new AnotherKindOfDoer());
if (someCondition3)
    doers.add(new YetAnotherKindOfDoer());

现在,您可以这样做,而不是 if

for (Doer d : doers) {
    d.doSomething();
}

您想在运行时根据配置设置以不同方式分派代码。条件语句和多态性是实现此目的的两种方式。

条件

在运行时,使用 ifswitch 或其他查找方法检查值。你已经在做这些了。

if (configFile.cloudAccount == null) {
    saveFileToDisk();
} else saveFileToCloud();

优势

  • 它们是有条件的,你真的无法避免在任何重要的开发项目中的某个时候必须做一个

缺点

  • 在应用程序的每个 点执行它们会很痛苦。因此,最好将它们与其他策略结合使用,以尽量减少它们的使用

多态性

加载应用程序时,通读配置文件并相应地构建应用程序的组件:

interface IFileSaver { /* Used to save files in your application */ }

class DiskSaver : IFileSaver { /* The default file saving class */ }

class CloudSaver : IFileSaver { /* If they've configured a cloud account */ }

// EXAMPLE USE

int Main (...) {
    // Setup your application, load a config file. 
    // You'll need to check the config with a conditional 
    // here (uh oh) but other components of your application 
    // will just use the IFileSaver interface
    if (configFile.cloudAccount != null) {
        YourApplication.FileSaver = new CloudSaver(configFile.cloudAccount);
    } else {
        YourApplication.FileSaver = new DiskSaver();
    }
}

// Somewhere else in your application
void SaveCurrentDocument() {
    // No if's needed, it was front loaded when initialising
    // the application
    YourApplication.FileSaver.Save();
}

优势

  • 非常适合面向对象的设计
  • 您所有的配置检查都是预先加载的。在加载正确的 classes 后,您的程序的其余部分将使用它们,而不会理会它们的实际实现。因此,您不需要在整个代码中进行 if 检查。
  • 编译器将能够静态检查您的方法中的类型错误

缺点

  • 只有 class 的界面灵活。也许您希望使用 CloudSaver 进行一些额外的步骤和检查,它们最好适合预先存在的界面;否则,它们不会发生。

长话短说 - 条件让您可以在需要时明确执行检查,因此原则上您可以获得很大的程序灵活性。例如,也许 SaveAs 例程需要与 Save 例程稍微 不同地保存文件。但是,正如您所确定的那样,这会导致长时间的重复代码。在这些情况下,构建您的代码以使用多态性可能会有所帮助。

无论哪种方式,只要您的应用程序具有灵活性,您几乎肯定需要一些 数量的条件检查。

注意:还有 许多 其他方法可以实现运行时配置检查,我只是指出最常见的(而且通常很简单)

如果功能不需要与对象数据进行大量交互(尽管交互是可能的),那么类似于 strategy design pattern(行为封装)的东西可能会使其更易于管理。优点:可读可扩展代码,缺点:大量代码。

namespace SomethingLikeStrategy
{
  public interface Behaviour {
    void doThis();
    void changeM(ref int m);
    void doThat();
  }

  public class BehaviourOriginal : Behaviour {
    public void doThis() {
      Console.WriteLine("foo");
    }
    public void changeM(ref int m) {
      m = 20;
    }
    public void doThat() {
      throw new Exception("not implemented");
    }
  }

  public class BehaviourSpecial : Behaviour {
    public void doThis() {
      Console.WriteLine("bar");
    }
    public void changeM(ref int m) {
      m = 10;
    }
    public void doThat() {
      throw new Exception("not implemented");
    }
  }

  public class MyClass {

    Behaviour mBehaviour;
    int mM = 0;

    public MyClass() {
      mBehaviour = new BehaviourOriginal();
    }

    public void setSpecialBehaviour(bool special) {
      if (special) {
        mBehaviour = new BehaviourSpecial();
      } else {
        mBehaviour = new BehaviourOriginal();
      }
    }

    public void doThis() {
      mBehaviour.doThis();
    }

    public void doThat() {
      mBehaviour.doThat();
    }

    public void changeM() {
      mBehaviour.changeM(ref mM);
    }

    public void printM() {
      Console.WriteLine(mM);
    }

  }

  class Program
  {
    public static void Main(string[] args)
    {
      MyClass myClass = new MyClass();
      myClass.doThis();
      myClass.setSpecialBehaviour(true);
      myClass.doThis();

      myClass.setSpecialBehaviour(false);
      myClass.printM();
      myClass.changeM();
      myClass.printM();
      myClass.setSpecialBehaviour(true);
      myClass.changeM();
      myClass.printM();

      Console.Write("Press any key to continue . . . ");
      Console.ReadKey(true);
    }
  }
}