比 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 的机会。尽管这条规则远非普遍适用,而且在组合方面也有不足,但它有一定道理,尤其是当你看到相同的条件在多个 if
s 中出现在不同的方法中时相同 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();
}
您想在运行时根据配置设置以不同方式分派代码。条件语句和多态性是实现此目的的两种方式。
条件
在运行时,使用 if
、switch
或其他查找方法检查值。你已经在做这些了。
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);
}
}
}
我正在尝试创建一种机制,允许应用程序决定(在运行时)是否执行某些功能。
"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 的机会。尽管这条规则远非普遍适用,而且在组合方面也有不足,但它有一定道理,尤其是当你看到相同的条件在多个 if
s 中出现在不同的方法中时相同 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();
}
您想在运行时根据配置设置以不同方式分派代码。条件语句和多态性是实现此目的的两种方式。
条件
在运行时,使用 if
、switch
或其他查找方法检查值。你已经在做这些了。
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);
}
}
}