c# - 这是构建灵活实现的合理方法吗

标签 c# design-patterns dependency-injection uwp

关闭。这个问题是opinion-based .它目前不接受答案。












想改进这个问题?更新问题,以便 editing this post 可以用事实和引用来回答它.

5年前关闭。




Improve this question




我是数以千计的开发人员使用的几个 C# 库的作者。我经常被要求自定义实现以启用边缘情况。我使用了以下方法,每种方法都有其优点。请允许我列出它们,如果没有其他原因,那么对可扩展性感兴趣的新手开发人员可能会开始看到可供他们使用的模式。

继承 .我用 abstractvirtual开发人员可以继承的非密封类中的方法和override用他们的逻辑。

public class DefaultLibrary
{
    public virtual void MyMethod()
    {
        // default logic
    }
}

public class CustomLibrary : DefaultLibrary
{
    public override void MyMethod()
    {
        // custom logic
    }
}

警告> 有时你写的类必须是 sealed .事实上,sealed当您编写库时,它是您的类的一个很好的默认值。在这种情况下,您需要考虑其他类似...

构造函数注入(inject) .我用过optional类中的构造参数使开发人员能够传入自定义逻辑。
public interface IService
{
    void Process();
}

public class DefaultLibrary
{
    IService _service;
    public DefaultLibrary(IService service)
    {
        _service = service;
    }
    public virtual void MyMethod()
    {
        _service.Process();
    }
}

警告> 有时,您正在编写的类需要维护一个内部状态,要求它们是单例(维护单个 static 实例)。在这种情况下,您需要考虑其他类似...

房产注入(inject) .我使用了类似工厂的属性,开发人员可以用他们自己的逻辑覆盖类的默认实现。
public interface IService
{
    void Process();
}

public class DefaultLibrary
{
    public IService Service { get; set; }

    public virtual void MyMethod()
    {
        Service.Process();
    }
}

警告> 属性注入(inject)很好,但它的行为很像构造函数注入(inject),因为它需要 interface以及该 interface 的实现在混凝土中class .有时您只是想让开发人员覆盖一个小的实现(一个或两个方法),就像继承(上图)但不需要基础。

这是我试图解决的问题。

我想要一种让开发人员感觉更轻量级的方法,并且不会引入一堆新的移动部件。所以,我已经登陆了。我想提出这种方法。我从未使用过它,也无法捍卫它的优点或缺陷。出于这个原因,我在问这个问题。 这种模式是合理的、明智的、有问题的还是一个绝妙的想法? 看起来不错。

这种模式可能已经有了名字。我不知道。这是要点:
public class CustomLibrary
{
    private void CallMyMethod()
    {
        MyMethod?.Invoke();
    }
    public Action MyMethod { get; set; }
}

这是一个完整的示例实现:
private async void CallSaveAsync(string value)
{
    if (RaiseBeforeSave(value))
    {
        await SaveAsync?.Invoke();
        RaiseAfterSave(value);
    }
}

private Func<Task> _saveAsync;
public Func<Task> SaveAsync
{
    get { return _saveAsync ?? DefaultSaveAsync; }
    set { _saveAsync = value; }
}

private async Task DefaultSaveAsync()
{
    await Task.CompletedTask;
}

不足之处?方法是开发人员可以覆盖的属性。

从 API 表面来看,确实没有任何变化。开发者仍调用await class.SaveAsync()它像宣传的那样工作。但是,开发人员现在可以选择使用 class.SaveAsync = MyNewMethod不会中断使用前后事件包装方法的内部逻辑。

我马上看到的可接受的缺点:
  • 我不能使用 ref参数
  • 我不能使用 optional参数
  • 我不能使用 params参数
  • 我不能使用方法覆盖

  • 除此之外,我看不出这种方法有什么严重的问题。何时适合需要 ref 的方法或 optional我将不得不改变模式。但是为什么不用完全像这样的所有候选方法来编写我的库呢?当然,这对我来说是更多的代码。但我不介意。

    感谢您抽出宝贵时间。

    最佳答案

    这是一个有效的选择,但它有一些缺点(正如你已经提到的)。

    除了你提到的那些,还有一个事实是你的方法不能是通用的。 (例如,您不能有 Func<T><string, T>)。

    不过,我不建议使用属性,当不同的客户端开始写入属性时,这可能会变得困惑。它创建了很难排除故障的共享状态。

    我宁愿为这些方法使用构造函数注入(inject)。例子

    public class SomeClass
    {
        readonly Func<string> _createId;
        public SomeClass() : this(null) {}
        public SomeClass(Func<string> createId)
        {
            _createId = createId ?? () => Guid.NewGuid().ToString();
        }
    
        public void SomeMethod()
        {
            var id = _createId();
            // do something
        }
    }
    

    如果你有一个单例类,而不是设置属性注入(inject),我会创建一个配置方法,它与上面示例中的构造函数相同。这样可以更轻松地查看这些功能的配置位置。例子:
    public static class SomeClass
    {
        static Func<string> _createId = () => Guid.NewGuid().ToString();
        public static void Configure(Func<string> createId)
        {
            if(createId == null) throw new ArgumentNullException(nameof(createId));
            _createId = createId;
        }
    
        public static void SomeMethod()
        {
            var id = _createId();
            // do something
        }
    }
    

    另一种选择是在方法参数中询问 Func。
    public class SomeClass
    {
        public void SomeMethod() => 
            SomeMethod(() => Guid.NewGuid().ToString())
    
        public void SomeMethod(Func<string> createId)
        {
            var id = createId();
            // do something
        }
    }
    

    这会让它变得更麻烦,因为客户端每次调用方法时都必须提供一个 Func,但也更灵活和“简单”,因为现在他可以在每次调用时看到会发生什么。
    这允许开发人员自己选择他想如何配置他的代码组织。他可以通过不同的Func在每次调用时(因为它们总是不同的),或者他可以选择创建一个局部变量并一直传递它(因为它总是相同的)。

    以上也适用于单例。

    关于c# - 这是构建灵活实现的合理方法吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39665860/

    相关文章:

    c# - 调用泛型类的方法

    c# - 处理潜在 session 过期的正确方法

    design-patterns - 我的代码去哪里了? Controller 、服务还是模型?

    JavaScript:模块模式差异

    java - 非目标绑定(bind)与 `toInstance()` 绑定(bind)

    c# - 获取 User.identity 的名字和姓氏

    c# - 多线程时进度条不显示正确的值

    java - 将数据从一种数据结构映射到另一种数据结构

    dependency-injection - Angular-Dart DI 库中的工厂注入(inject)

    java - 尝试注入(inject)两个具有通用实现的存储库失败