码迷,mamicode.com
首页 > 其他好文 > 详细

Static需谨慎

时间:2018-12-28 12:32:57      阅读:149      评论:0      收藏:0      [点我收藏+]

标签:pes   machine   instance   pos   pts   contains   private   etc   color   

Static Cling

Sticking Your Code To Things Unnecessarily

 

 Static Cling is a code smell used to describe the undesirable coupling introduced by accessing static (global) functionality, either as variables or methods. This coupling can make it difficult to test or modify the behavior of software systems.

 

Consider the following example:

public class CheckoutController
{
    public void Checkout(Order order)
    {
        // verify payment

        // verify inventory

        LogHelper.LogOrder(order);
    }
}

public static class LogHelper
{
    public static void LogOrder(Order order)
    {
        using (System.IO.StreamWriter file = 
            new System.IO.StreamWriter(@"C:\Users\Steve\OrderLog.txt", true))
        {
            file.WriteLine("{0} checked out.", order.Id);
        }
    }
}

public class Order
{
    public int Id { get; set; }
}

In the above code, any attempt to unit test the Checkout method will be made much more difficult by the static LogOrder method, which has a dependency on the file system and a particular file path.

While it’s certainly possible to write an integration test that will still log to the chosen path, or to refactor this code so that the file path comes from configuration or something similar, it would be far better if the dependency on the file system didn’t exist, since it isn’t important to what Checkout() is trying to do.

 

To refactor away from Static Cling, replace the static method call with an instance method call on an instance type (frequently implementing an interface), and use the strategy design pattern (also known as dependency injection) to inject the dependency into the class that needs the functionality.

In the case where the static functionality is not code you control, you can access it through an Adapter. This approach is shown below:

public class CheckoutController
{
    private readonly IOrderLoggerAdapter _orderLoggerAdapter;

    public CheckoutController(IOrderLoggerAdapter orderLoggerAdapter)
    {
        _orderLoggerAdapter = orderLoggerAdapter;
    }

    public CheckoutController()
        : this(new FileOrderLoggerAdapter())
    {
            
    }

    public void Checkout(Order order)
    {
        // verify payment

        // verify inventory

        _orderLoggerAdapter.LogOrder(order);
    }
}

public static class LogHelper
{
    public static void LogOrder(Order order)
    {
        using (System.IO.StreamWriter file = 
            new System.IO.StreamWriter(@"C:\Users\Steve\OrderLog.txt", true))
        {
            file.WriteLine("{0} checked out.", order.Id);
        }
    }
}

public interface IOrderLoggerAdapter
{
    void LogOrder(Order order);
}

public class FileOrderLoggerAdapter : IOrderLoggerAdapter
{
    public void LogOrder(Order order)
    {
        LogHelper.LogOrder(order);
    }
}

public class Order
{
    public int Id { get; set; }
}

In the above code, the OrderController no longer has a direct dependency on the static LogHelper.LogOrder() method.

It now follows the Explicit Dependencies Principle, since its constructor declares the collaborating types it requires to function.

 

 

This would allow the code to be modified in the future by simply passing in a different implementation of the IOrderLoggerAdapter,

and would also allow unit tests to test the other behavior in the Checkout() method without the need for certain drives, paths, or files to exist on the test machine.

 

If the application is using a container to resolve class dependencies, configuring the runtime behavior of how OrderController will get the classes it depends on would be done in the container’s configuration.

 

 

If a container is not in use, or if existing client code needs to continue to call the default constructor of OrderController, a technique called poor man’s dependency injection can be used.

With this technique, a default constructor is configured to call through to the constructor that accepts dependencies, with instances configured that provide the original behavior.

In this case, the default constructor passes a new instance of the FileOrderLoggerAdapter, which contains the original behavior of calling LogHelper.LogOrder().

 

Although Static Cling refers specifically to references to static methods (or properties), the same consequences occur when instance variables are instantiated and immediate called within a method.

Be careful of where in your code you make decisions about a method or class’s collaborators, and remember that New is Glue if you choose to instantiate a type that has dependencies on infrastructure concerns (e.g. file system, database, etc).

 

 

相关链接:

antipatterns

Principles

 

Static需谨慎

标签:pes   machine   instance   pos   pts   contains   private   etc   color   

原文地址:https://www.cnblogs.com/panpanwelcome/p/10189759.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!