我编写了一个库,它使用泛型和函数来实现特性切换系统,而不需要使用if语句。
这主要是为了磨练我的技能,因为我还是这个职业的新手。我非常感谢您对如何改进代码的任何评论。
它可以在以下git回购中找到
主类的摘录如下所示
namespace FeatureToggle.Classes
{
using System;
using Enums;
using Interfaces;
public class FeatureToggle <T> : IFeatureToggle <T>
{
private ToggleStatus Status(bool active)
{
return active ?
ToggleStatus.Active :
ToggleStatus.Inactive;
}
public ToggleStatus GetToggleState(IConfigParser parser, string toggleKey)
{
return Status(parser.GetToggleStatus(toggleKey));
}
public void ExecuteMethodIfToggleOn(Action methodToRun, IConfigParser configParser, string keyName)
{
var response = GetToggleState(configParser, keyName);
if (response == ToggleStatus.Active)
{
methodToRun();
}
}
public void ExecuteMethodIfToggleOn(Action methodToRun, string keyName)
{
IConfigParser configParser = new ConfigParser();
ExecuteMethodIfToggleOn(methodToRun, configParser, keyName);
}
public T ExecuteMethodIfToggleOn(Func<T> methodToRun, string keyName)
{
IConfigParser configParser = new ConfigParser();
return ExecuteMethodIfToggleOn(methodToRun, configParser, keyName);
}
public T ExecuteMethodIfToggleOn(Func<T> methodToRun, IConfigParser configParser, string keyName)
{
var response = GetToggleState(configParser, keyName);
if (response == ToggleStatus.Active)
{
return methodToRun();
}
return default(T);
}
}
}特征切换接口
namespace FeatureToggle.Interfaces
{
using System;
using Enums;
public interface IFeatureToggle <T>
{
ToggleStatus GetToggleState(IConfigParser parser, string toggleKey);
void ExecuteMethodIfToggleOn(Action methodToRun, string keyName);
T ExecuteMethodIfToggleOn(Func<T> methodToRun, string keyName);
}
}配置分析器类
namespace FeatureToggle.Classes
{
using System;
using System.Collections.Specialized;
using System.Configuration;
using Interfaces;
public class ConfigParser : IConfigParser
{
private readonly NameValueCollection _toggles;
public ConfigParser()
{
if (ToggleConfigTagExists())
{
_toggles = ConfigurationManager.GetSection("Toggles") as NameValueCollection;
}
}
public bool ToggleConfigTagExists()
{
var toggleSection = ConfigurationManager.GetSection("Toggles");
return toggleSection != null;
}
public bool GetToggleStatus(string toggle)
{
return ParseBoolValueFromConfig(_toggles.GetValues(toggle)?[0]);
}
public bool ParseBoolValueFromConfig(string status)
{
if (status == "1" || status.ToLower() == "true")
{
return true;
}
if (status == "0" || status.ToLower() == "false")
{
return false;
}
else
{
throw new ArgumentOutOfRangeException();
}
}
}
}配置分析器接口
namespace FeatureToggle.Interfaces
{
public interface IConfigParser
{
bool GetToggleStatus(string toggle);
bool ParseBoolValueFromConfig(string status);
}
}集成测试
namespace FeatureToggleTests.Integration
{
using System;
using FeatureToggle.Classes;
using FeatureToggle.Enums;
using FeatureToggle.Interfaces;
using NUnit.Framework;
[TestFixture]
public class FeatureToggleIntegrationTests
{
[Test]
public void TestToggleStatusActiveIsReturnedWhenParsingAnItemThatIsToggledOn()
{
IConfigParser configParser = new ConfigParser();
IFeatureToggle<bool> featureToggle = new FeatureToggle<bool>();
var toggleStatus = featureToggle.GetToggleState(configParser, "ButtonToggle");
Assert.AreEqual(ToggleStatus.Active, toggleStatus);
}
[Test]
public void TestToggleStatusInactiveIsReturnedWhenParsingAnItemThatIsToggledOff()
{
IConfigParser configParser = new ConfigParser();
IFeatureToggle<bool> featureToggle = new FeatureToggle<bool>();
var toggleStatus = featureToggle.GetToggleState(configParser, "NotFinished");
Assert.AreEqual(ToggleStatus.Inactive, toggleStatus);
}
[Test]
public void TestOutOfRangeExceptionIsReturnedWhenParsingAnItemThatIsToggledAsdf()
{
var configParser = new ConfigParser();
IFeatureToggle<bool> featureToggle = new FeatureToggle<bool>();
Assert.Throws<ArgumentOutOfRangeException>(() => featureToggle.GetToggleState(configParser, "asdf"));
}
[Test]
public void TestNullReferenceExceptionIsReturnedWhenParsingAnItemThatDoesNotExist()
{
var configParser = new ConfigParser();
IFeatureToggle<bool> featureToggle = new FeatureToggle<bool>();
Assert.Throws<NullReferenceException>(() => featureToggle.GetToggleState(configParser, "wewewewewewewewe"));
}
[Test]
public void TestFakeMethodWillNotChangeValueIfConfigItemIsToggledToFalse()
{
var changeMe = "Unchanged";
FakeMethod("FakeFalse", out changeMe);
Assert.AreEqual("Unchanged", changeMe);
}
[Test]
public void TestFakeMethodWillChangeValueIfConfigItemIsToggledToTrue()
{
var changeMe = "Unchanged";
FakeMethod("FakeTrue", out changeMe);
Assert.AreEqual("has been changed", changeMe);
}
[Test]
public void TestActionFakeMethodThatReturnsTrueWillReturnTrueIfConfigItemIsToggledToTrue()
{
IFeatureToggle<bool> featureToggler = new FeatureToggle<bool>();
var result = featureToggler.ExecuteMethodIfToggleOn(FakeMethodThatReturnsTrue, "FakeTrue");
Assert.IsTrue(result);
}
[Test]
public void TestActionFakeMethodThatReturnsTrueWillReturnFalseIfConfigItemIsToggledToFalse()
{
IFeatureToggle<bool> featureToggler = new FeatureToggle<bool>();
var result = featureToggler.ExecuteMethodIfToggleOn(FakeMethodThatReturnsTrue, "FakeFalse");
Assert.IsFalse(result);
}
protected void FakeMethod(string keyName, out string changeMe)
{
IConfigParser configParser = new ConfigParser();
IFeatureToggle<bool> featureToggle = new FeatureToggle<bool>();
var response = featureToggle.GetToggleState(configParser, keyName);
if (response == ToggleStatus.Active)
{
changeMe = "has been changed";
return;
}
changeMe = "Unchanged";
}
protected bool FakeMethodThatReturnsTrue()
{
return true;
}
}
}单元试验
namespace FeatureToggleTests.Unit
{
using System;
using FeatureToggle.Classes;
using FeatureToggle.Enums;
using FeatureToggle.Interfaces;
using NUnit.Framework;
[TestFixture]
public class FeatureToggleTests
{
[Test]
public void TestSuccessfullParseReturnsToggleStatusActive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<bool>();
var toggleResponse = toggle.GetToggleState(testParser, "positive");
Assert.AreEqual(ToggleStatus.Active, toggleResponse);
}
[Test]
public void TestUnSuccessfullParseReturnsToggleStatusInactive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<bool>();
var toggleResponse = toggle.GetToggleState(testParser, "anythingElse");
Assert.AreEqual(ToggleStatus.Inactive, toggleResponse);
}
[Test]
public void TestSuccessfullFuncCallWhenToggleStatusActive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<bool>();
Func<bool> theAction = AlwaysReturnTrue;
var toggleResponse = toggle.ExecuteMethodIfToggleOn(theAction, testParser, "positive");
Assert.AreEqual(true, toggleResponse);
}
[Test]
public void TestUnSuccessfullFuncCallWhenToggleStatusInactive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<bool>();
Func<bool> theAction = AlwaysReturnTrue;
var toggleResponse = toggle.ExecuteMethodIfToggleOn(theAction, testParser, "anythingElse");
Assert.AreEqual(false, toggleResponse);
}
[Test]
public void TestSuccessfullFuncStringCallWhenToggleStatusActive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<string>();
Func<string> theAction = AlwaysReturnFire;
var toggleResponse = toggle.ExecuteMethodIfToggleOn(theAction, testParser, "positive");
Assert.AreEqual("Fire", toggleResponse);
}
[Test]
public void TestUnSuccessfullFuncStringCallWhenToggleStatusInactive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<string>();
Func<string> theAction = AlwaysReturnFire;
var toggleResponse = toggle.ExecuteMethodIfToggleOn(theAction, testParser, "anythingElse");
Assert.AreNotEqual("Fire", toggleResponse);
}
[Test]
public void TestSuccessfullFuncTestDataTypeCallWhenToggleStatusActive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<TestDataType>();
Func<TestDataType> theAction = AlwaysReturnNewTestDataType;
var toggleResponse = toggle.ExecuteMethodIfToggleOn(theAction, testParser, "positive");
Assert.AreEqual(new TestDataType().HappynessIs, toggleResponse.HappynessIs);
}
[Test]
public void TestUnSuccessfullFuncTestDataTypeCallWhenToggleStatusInactive()
{
IConfigParser testParser = new ConfigParserTestDouble();
var toggle = new FeatureToggle<TestDataType>();
Func<TestDataType> theAction = AlwaysReturnNewTestDataType;
var toggleResponse = toggle.ExecuteMethodIfToggleOn(theAction, testParser, "anythingElse");
Assert.IsNull(toggleResponse);
}
protected bool AlwaysReturnTrue()
{
return true;
}
protected string AlwaysReturnFire()
{
return "Fire";
}
protected TestDataType AlwaysReturnNewTestDataType()
{
return new TestDataType();
}
}
public class TestDataType
{
public string HappynessIs = "Happy";
}
internal class ConfigParserTestDouble : IConfigParser
{
public bool ToggleConfigTagExists()
{
throw new System.NotImplementedException();
}
public bool GetToggleStatus(string toggle)
{
return toggle == "positive";
}
public bool ParseBoolValueFromConfig(string status)
{
throw new System.NotImplementedException();
}
}
}Enum
namespace FeatureToggle.Enums
{
public enum ToggleStatus
{
Active,
Inactive
}
}FeatureToggle类使用泛型类型,并注入一个configParser。当您想在主库中切换一个方法时,您可以调用FeatureToggle.ExecuteMethodIfToggleOn()。由于它使用泛型类型,因此如果“切换”状态是打开的,它将以方法输出返回类型/do内容。
ConfigParser类解析来自app.config的真值或假值。尽管它正在实现IConfigParser,所以我可以扩展行为,从数据库或文本文件中读取数据,或者其他我可能需要从其中读取值的内容。
虽然我认为这已经结束了杀戮和嘘声,但它的存在是为了明确地指示什么东西是打开还是关闭的。
集成和单元测试是为了证明这是可行的,并将收集任何我在任何进一步的修改中引入的bug。这些测试只是实现一些简单行为的快速测试双倍,以确保我能够根据预期值进行断言。
发布于 2018-05-11 19:55:52
在这里,我发布了另一种你可以尝试的方法。
不用这个你可以做一个插件系统,
优点
缺点
一个插件实现了:
public interface IPlugin
{
void Do(IWorkspace workspace);
}它在其上执行操作的工作区(这里非常简单):
public interface IWorkspace
{
void ShowMessage(string message);
}插件的两个例子:
public sealed class Plugin1 : IPlugin
{
public void Do(IWorkspace workspace)
{
workspace.ShowMessage("Plugin1 here !");
}
}
public sealed class Plugin2 : IPlugin
{
public void Do(IWorkspace workspace)
{
workspace.ShowMessage("Plugin2 here !");
}
}工作区的一个示例(应该在您的应用程序中):
using System.Windows;
internal sealed class Workspace : IWorkspace
{
public void ShowMessage(string message)
{
MessageBox.Show(message);
}
}这里显示这些插件(WPF)的迷你应用程序:
XAML
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu x:Name="Menu" />
</Grid>
</Window>代码背后:
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
// get the plugins in assembly
var plugins = Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(s => typeof(IPlugin).IsAssignableFrom(s))
.Where(s => s.IsClass)
.Where(s => s.GetConstructor(Type.EmptyTypes) != null)
.Select(s => (IPlugin) Activator.CreateInstance(s))
.ToArray()
;
// populate plugins in menu
var root = new MenuItem {Header = "Plugins"};
foreach (var plugin in plugins)
{
var item = new MenuItem {Header = plugin.GetType().Name};
item.Click += (sender, args) => { plugin.Do(Workspace); };
root.Items.Add(item);
}
Menu.Items.Add(root);
}
private IWorkspace Workspace { get; } = new Workspace();
}
}

IMHO的优点肯定大于缺点,虽然一开始有点困难,但最终事情是分开的,你的应用程序不太可能成为一个巨大的混乱。
其中一个强有力的论点是,你的插件是针对公共界面的,不会看到/处理你的应用程序的任何(私人)血淋淋的细节。
接下来你该做什么:
这仅仅是一个例子,促使你跳出框框,把它改变成你的需要,但我认为它解决了一个非常重要的方面:在你的应用程序中不再有意大利面逻辑。
https://codereview.stackexchange.com/questions/194024
复制相似问题