【问题标题】:Should I pass an app-wide object into objects that need it, or should I use a singleton?我应该将应用程序范围的对象传递给需要它的对象,还是应该使用单例?
【发布时间】:2013-03-23 05:19:17
【问题描述】:

在我的具体情况下,我正在通过嵌套视图控制器向下传递一个“facebook”对象(即 MonoTouch.FacebookConnect.Facebook 的一个实例),并且它正在向项目中添加一些不错的代码。对象在 AppDelegate 中实例化后始终只有一个实例,并且在应用程序中的大多数视图控制器中都有使用。所有使用 facebook 对象的视图控制器在开头都有这样的内容:

public class MyViewController : UIViewController
{
    Facebook facebook;

    public MyViewController (Facebook facebook)
    {
        this.facebook = facebook;
    }
    // ...
}

...除了顶层视图控制器,对象被实例化的地方。

还有一种情况是它“通过”嵌套视图控制器,只是为了将它传递给使用它的更深层嵌套的视图控制器。

我知道单例通常不受欢迎,并且违反了 OOP 原则。我希望代码易于维护。我是否采取了正确的方法,或者单身人士会在不影响代码质量的情况下做到这一点?

【问题讨论】:

    标签: c# ios oop xamarin.ios singleton


    【解决方案1】:

    我会不惜一切代价避免单例。您的方法鼓励松散耦合,并允许您通过模拟对象来提高可测试性(如果您从具体实现转换它)。它通常会添加更多代码,但您应该在应用程序中具有更好的可测试性。这允许您使用 IoC 容器并利用依赖注入。网络上和 StackOverflow 上有许多关于此类主题的精彩文章和帖子。

    也许你的单身人士可能看起来像这样:

    public class MyViewController : UIViewController
    {
        public MyViewController ()
        {
        }
    
        public void Foo()
        {
            FaceBookSingleton.Instance.DoSomeAction();
            FaceBookSingleton.Instance.Something = 4;
        }
    }
    

    您如何围绕Foo() 进行测试?如果您引入了重大更改,您怎么能更快地知道?你怎么知道你的依赖图是什么样的?像这样测试代码更难。不要指望人们浏览您的代码库来尝试找出他们是否破坏了某些东西。通过编写可测试的代码帮助他们和你自己。

    现在也许看看这样的东西:

    public interface ISocialMediaWidget
    {
        ISocialMediaWidgetResponse DoSomeUnitOfWork();
    }
    
    
    public class ConcreteSocialMediaWidgetService
    {
        protected readonly ISocialMediaWidget socialMediaWidget;
    
        public ConcreteSocialMediaWidgetService(ISocialMediaWidget widget)
        {
            this.socialMediaWidget = widget;
        }
    
        public ISocialMediaWidgetResponse Foo()
        {
            return socialMediaWidget.DoUnitOfWork();
        }
    }
    

    围绕上述内容编写测试要容易得多。您还可以模拟来自每个接口的响应以创建各种测试。

    public void SocialMediaWidgetTest()
    {   //pseudo code
        var testService = new MockObject(ConcreteSocialMediaWidgetService(MockObject_of_ISocialMediaWidget));
        Assert.Equals(someImplementation_of_ISocialMediaWidgetResponse, testService.Foo());
    }
    

    【讨论】:

    • 好点。我还没有对这个项目实施单元测试,但我计划在未来这样做。谢谢!
    • 再次感谢另一个例子。
    • 更多代码意味着更多测试。它还使重构变得更加困难,这对代码库的长期稳定性造成了打击。单例是不可测试的,这是一个神话。您提供的更可测试的代码在保持 Facebook 实例的单例中也可以正常工作 - 测试前后代码可以轻松地根据需要重置单例实例。
    • @KendallHelmstetterGelner:我没有说它们不可测试。我说它们更难测试。该代码只是一个sn-p。作为 Singleton 进行测试更难,因为您不能保证对 Facebook.Instance.Foo() 的调用不会在对 DoSomeAction() 的调用中产生意外后果。
    • DoSomeAction() 与您的 DoUnitOfWork() 调用没有什么不同。所以基本上带有单例的代码看起来像:FaceBookSingleton.Instance.DoUnitOfWork()。它同样可笑。同时,使用这种方法,如果他需要更改代码或支持多个社交媒体服务,他避免了大量的代码会妨碍他。如果他坚持到底,所有的 VC 代码都必须知道什么是 ISocialMediaWidget/Facebook 对象,从而增加耦合。只是在它周围创造的问题比它解决的问题要多。
    【解决方案2】:

    首先,单例在很大程度上只是其他完全可接受的 OO 策略(如“工厂”和记忆化)的替代语法。

    这是一个语法问题,以及每种语法在您的组织中的含义。如果您这样做,您的组织是否暗示可能会对产品 MyClass.Current 进行一些工作?

    public class MyClass
    {
      public static MyClass Current
      {
        get
        {
          if (SomeSortOfCache['MyClass.Current'] == null) {
            // do something to get populate it.
          }
          return SomeSortOfCache['MyClass.Current'];
        }
      }
    }
    

    或者,您的组织是否更喜欢这样的东西:

    public class MyClass
    {
      public static MyClass GetCurrent()
      {
        if (SomeSortOfCache['MyClass.Current'] == null) {
          // do something to get populate it.
        }
        return SomeSortOfCache['MyClass.Current'];
      }
    }
    

    或者,如果您想将其视为工厂,请调用方法BuildMyClass()CreateMyClass()。在引擎盖下,机制大致相同。这主要是您的组织对每种语法 + 命名约定的假设问题。

    其次,您已经确定您只希望或需要允许 的一个实例存在。所以,我建议使用一种可以优雅地促进这一点的模式,并且使用最少的代码。

    【讨论】:

    • 我的组织是一家小型初创公司,我们没有首选的方法。我主要想知道使用单例方法是否会影响代码的可维护性。
    • @JonathanNesbitt 我在我的应用程序中没有遇到任何问题。相反,我实际上遇到了 许多 的问题,这些问题在整理出无休止的“参数链”和冗余数据获取时,这些问题会在严格避免使用单例和/或工厂类型模式时蔓延开来。无论你做什么,都要有目的地去做,并在你的团队中建立对每种模式所暗示的理解。换句话说,当我点击 ApplicationUser.Current 或 HttpContext.Current 时,我可以预期它们在后台的行为相似。没有人写入我的数据库等...
    • 我在一家小型初创公司工作,我是唯一的 iOS 开发人员。我现在没有时间实施单元测试——我的主要目标是让 v1.0 快速进入 App Store。我希望不必复制/粘贴重复的代码来节省一点时间。不过,我不想让代码在未来无法测试。
    • @JonathanNesbitt 对于我自己的教育,测试问题到底是什么?
    • @jonathanNesbitt 你有时间做单元测试。你花一些时间编写单元测试;您可以节省时间轻松发现问题,否则会发现困难。 Xcode 让添加单元测试真的变得容易。
    【解决方案3】:

    在这种情况下,单例似乎是更好的解决方案,因为您可以消除大量重复代码,并在快速构建应用程序时增加更改的灵活性。

    单例也可以充当 Facebook 对象的中介,让应用程序的各个部分更易于使用。

    任何本质上受限的资源都适合通过单例访问。

    【讨论】:

    • 我喜欢消除重复代码的想法(这正是我首先提出要求的原因),但 svidgen 在保持代码可测试方面提出了一个非常好的观点。我计划在未来实施单元测试,所以我会保留我正在使用的方法。谢谢!
    • 您也可以轻松地模拟出单例,记住(正如另一个回复所说)单例实际上是一种工厂 - 在运行测试时您可以轻松地换出单例实例方法马具。 iOS 作为一个系统很好地利用了单例(如 UIApplication 或 UIDevice),因此单例的使用自然而然地适合整个系统。
    • @KendallHelmstetterGelner 谢谢你:“单身是一种工厂”
    【解决方案4】:

    我更喜欢使用单例方法,因为您总是只需要一个实例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 1970-01-01
      • 2012-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多