【问题标题】:Internal global property..bad smell?内部全球财产..难闻的气味?
【发布时间】:2008-11-24 06:05:34
【问题描述】:

我在处理一些代码时遇到了一些设计问题:

我的基本代码如下所示:

主 COM 包装器:

public class MapinfoWrapper 
{
    public MapinfoWrapper()
    {
        Publics.InternalMapinfo = new MapinfoWrapper();
    }

    public void Do(string cmd) 
    {
        //Call COM do command
    }

    public string Eval(string cmd)
    {
        //Return value from COM eval command
    }
}

用于保存对包装器的内部引用的公共静态类

internal static class Publics
{
    private static MapinfoWrapper _internalwrapper;
    internal static MapinfoWrapper InternalMapinfo 
    { 
        get
        {
            return _internalwrapper;     
        }
        set
        {
            _internalwrapper = value;
        }
    }
}

使用内部包装实例的代码:

    public class TableInfo
    {
        public string Name {
            get { return Publics.InternalMapinfo.Eval("String comman to get the name"); }
            set { Publics.InternalMapinfo.Do("String command to set the name"); }
        }
    }

这对任何人来说都难闻吗?我应该使用内部属性来保存对主包装器对象的引用还是应该在这里使用不同的设计?

注意:MapinfoWrapper 对象将被外部世界使用,所以我真的不想让它成为单例。

【问题讨论】:

    标签: c# design-patterns com word-wrap


    【解决方案1】:

    通过不将 MapInfoWrapper 注入类本身,您正在降低 TableInfo 类的可测试性。是否使用这些 MapInfoWrapper 类的全局缓存取决于类 - 您需要确定是否有必要,但将包装器传递到 TableInfo 并在那里使用它会改进您的设计,而不是直接引用全局副本在 TableInfo 方法中。结合接口的定义(即“重构为接口”)执行此操作。

    我还会在 Publics 的 getter(s) 中进行惰性实例化,以确保对象在尚未创建时可用,而不是在 MapInfoWrapper 的构造函数中设置它。

    public class TableInfo
    {
         private IMapinfoWrapper wrapper;
    
         public TableInfo() : this(null) {}
    
         public TableInfo( IMapinfoWrapper wrapper )
         {
              // use from cache if not supplied, could create new here
              this.wrapper = wrapper ?? Publics.InternalMapInfo;
         }
    
         public string Name {
            get { return wrapper.Eval("String comman to get the name"); }
            set { wrapper.Do("String command to set the name"); }
         }
    }
    
    public interface IMapinfoWrapper
    {
        void Do( string cmd );
        void Eval( string cmd );
    }
    
    public class MapinfoWrapper 
    {
        public MapinfoWrapper()
        {
        }
    
        public void Do(string cmd) 
        {
            //Call COM do command
        }
    
        public string Eval(string cmd)
        {
            //Return value from COM eval command
        }
    }
    
    internal static class Publics
    {
        private static MapinfoWrapper _internalwrapper;
        internal static MapinfoWrapper InternalMapinfo 
        { 
            get
            {
                if (_internalwrapper == null)
                {
                    _internalwrapper = new MapinfoWrapper();
                }
                return _internalwrapper;     
            }
        }
    }
    

    现在,当您测试 TableInfo 方法时,您可以通过向构造函数提供您自己的实现来轻松模拟 MapInfoWrapper。例如(假设是模拟手):

    [TestMethod]
    [ExpectedException(typeof(ApplicationException))]
    public void TestTableInfoName()
    {
         IMapinfoWrapper mockWrapper = new MockMapinfoWrapper();
         mockWrapper.ThrowDoException(typeof(ApplicationException));
    
         TableInfo info = new TableInfo( mockWrapper );
         info.Do( "invalid command" );
    }
    

    【讨论】:

    • 酷我本来打算这样开始的,但我认为它可能是错误的,但现在我看到它通过测试实现了它使堆更有意义。为此干杯!
    • 我唯一的问题是从用户的角度来看它的阅读效果不佳。我不确定我喜欢这个:TableInfo tab = new TableInfo(Nothing);
    【解决方案2】:

    我曾考虑将此添加到我的原始回复中,但这确实是一个不同的问题。

    如果您存储和使用缓存副本,您可能需要考虑 MapinfoWrapper 类是否需要是线程安全的。每当您使用单个全局副本时,您都需要考虑它是否一次被多个线程使用并构建它,以便任何关键部分(任何可能更改或必须假定不更改的数据)都是线程-安全的。如果必须支持多线程环境——比如在网站中——那么这可能会反对使用单个全局副本,除非创建类的成本非常高。当然,如果您的类依赖于其他也不是线程安全的类,那么您可能仍需要使您的类成为线程安全的。

    【讨论】:

      猜你喜欢
      • 2010-09-20
      • 1970-01-01
      • 2011-12-21
      • 2012-03-16
      • 2015-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多