首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >切换应用程序的特性

切换应用程序的特性
EN

Code Review用户
提问于 2018-05-09 16:08:24
回答 1查看 883关注 0票数 10

我编写了一个库,它使用泛型和函数来实现特性切换系统,而不需要使用if语句。

这主要是为了磨练我的技能,因为我还是这个职业的新手。我非常感谢您对如何改进代码的任何评论。

它可以在以下git回购中找到

主类的摘录如下所示

代码语言:javascript
复制
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);
        }
    }
}

特征切换接口

代码语言:javascript
复制
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);
    }
}

配置分析器类

代码语言:javascript
复制
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();
            }
        }
    }
}

配置分析器接口

代码语言:javascript
复制
namespace FeatureToggle.Interfaces
{
    public interface IConfigParser
    {
        bool GetToggleStatus(string toggle);

        bool ParseBoolValueFromConfig(string status);
    }
}

集成测试

代码语言:javascript
复制
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;
        }
    }
}

单元试验

代码语言:javascript
复制
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

代码语言:javascript
复制
namespace FeatureToggle.Enums
{
    public enum ToggleStatus
    {
        Active,
        Inactive
    }
}

类解释:

FeatureToggle类使用泛型类型,并注入一个configParser。当您想在主库中切换一个方法时,您可以调用FeatureToggle.ExecuteMethodIfToggleOn()。由于它使用泛型类型,因此如果“切换”状态是打开的,它将以方法输出返回类型/do内容。

ConfigParser类解析来自app.config的真值或假值。尽管它正在实现IConfigParser,所以我可以扩展行为,从数据库或文本文件中读取数据,或者其他我可能需要从其中读取值的内容。

虽然我认为这已经结束了杀戮和嘘声,但它的存在是为了明确地指示什么东西是打开还是关闭的。

集成和单元测试是为了证明这是可行的,并将收集任何我在任何进一步的修改中引入的bug。这些测试只是实现一些简单行为的快速测试双倍,以确保我能够根据预期值进行断言。

EN

回答 1

Code Review用户

发布于 2018-05-11 19:55:52

在这里,我发布了另一种你可以尝试的方法。

不用这个你可以做一个插件系统,

优点

  • 应用程序没有潜在的切换代码。
  • 关注点是分开的,应用程序做管道,功能是委托的。
  • 插件对经过深思熟虑的/定义良好的公共接口起作用。
  • 最终测试可能更容易(粗略的猜测)
  • (不管我还忘了什么)

缺点

  • 一开始就涉及到一些管道。
  • 最后是更多的类型
  • (不管我还忘了什么)

一个插件实现了:

代码语言:javascript
复制
public interface IPlugin
{
    void Do(IWorkspace workspace);
}

它在其上执行操作的工作区(这里非常简单):

代码语言:javascript
复制
public interface IWorkspace
{
    void ShowMessage(string message);
}

插件的两个例子:

代码语言:javascript
复制
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 !");
    }
}

工作区的一个示例(应该在您的应用程序中):

代码语言:javascript
复制
using System.Windows;

internal sealed class Workspace : IWorkspace
{
    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }
}

这里显示这些插件(WPF)的迷你应用程序:

XAML

代码语言:javascript
复制
<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>

代码背后:

代码语言:javascript
复制
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的优点肯定大于缺点,虽然一开始有点困难,但最终事情是分开的,你的应用程序不太可能成为一个巨大的混乱。

其中一个强有力的论点是,你的插件是针对公共界面的,不会看到/处理你的应用程序的任何(私人)血淋淋的细节。

接下来你该做什么:

  • 您的工作区/插件系统以自己的程序集结束,它将被应用程序、插件和单元测试引用。
  • 将插件编码到单独的程序集中,根据需要组织它们。
  • 创建一个动态dis/en-able插件并更新菜单的系统,就像Visual中的一些扩展

这仅仅是一个例子,促使你跳出框框,把它改变成你的需要,但我认为它解决了一个非常重要的方面:在你的应用程序中不再有意大利面逻辑。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/194024

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档