ChatGPT解决这个技术问题 Extra ChatGPT

GOF 单例模式是否有任何可行的替代方案?

面对现实吧。单例模式是highly controversial主题,在栅栏的双方 两侧都有大批程序员。有些人觉得 Singleton 只不过是一个美化的全局变量,而另一些人则对模式发誓并不断地使用它。但是,我不希望 Singleton Controversy 成为我问题的核心。 每个人都可以来一场拔河比赛,然后一决胜负,看看谁会在我所关心的范围内获胜。我想说的是,我不相信有一个正确的答案,我也不是故意煽动党派争吵。当我问这个问题时,我只是对 singleton-alternatives 感兴趣:

他们是 GOF 单例模式的任何特定替代方案吗?

例如,当我过去多次使用单例模式时,我只是对保留一个或多个变量的状态/值感兴趣。但是,可以使用静态变量而不是使用单例模式在类的每个实例化之间保留变量的状态/值。

你还有什么别的想法?

编辑:我真的不希望这是另一篇关于“如何正确使用单例”的帖子。同样,我正在寻找避免它的方法。为了好玩,好吗?我想我用你最好的电影预告片声音问了一个纯粹的学术问题,“在没有单身的平行宇宙中,我们能做什么?”

什么?它既不好也不坏,但是我该如何替换它呢?对于所有说这很好的人——不要参与。所有说它不好的人,通过向我展示没有它我将如何生活来证明这一点。对我来说听起来很争论。
@CodingWithoutComments:确实阅读了整篇文章。这就是我如何理解“如果你觉得单身人士没问题就不要回复”的感觉。
好吧,如果是这样的话,我道歉。我认为我采取了重大措施来避免两极分化。我想我问这个问题的方式是单身人士的爱好者和憎恨者都可以接受,作为一名程序员,我们都有选择——他们永远不是唯一的正确方式
如果我使用单例,我对如何解决它们没有可能的贡献。对我来说听起来两极分化。
我每天都使用单例,但这是否会阻止我认为可能有更好的做事方式?设计模式才出现了 14 年。我认为它们是圣经真理吗?我们是否停止尝试跳出框框思考?难道我们不努力推进 CS 学科吗?

R
Rasmus Faber

要了解解决单例的正确方法,您需要了解单例的问题(以及一般的全局状态):

单例隐藏依赖项。

为什么这很重要?

因为如果您隐藏依赖项,您往往会失去对耦合量的跟踪。

你可能会争辩说

void purchaseLaptop(String creditCardNumber, int price){
  CreditCardProcessor.getInstance().debit(creditCardNumber, amount);
  Cart.getInstance().addLaptop();
}

比简单

void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart, 
                    String creditCardNumber, int price){
  creditCardProcessor.debit(creditCardNumber, amount);
  cart.addLaptop();
}

但至少第二个 API 清楚地说明了该方法的协作者是什么。

所以解决单例的方法不是使用静态变量或服务定位器,而是将单例类更改为实例,这些实例在它们有意义的范围内实例化,并注入到需要它们的组件和方法中。您可以使用 IoC 框架来处理此问题,也可以手动执行,但重要的是摆脱全局状态并使依赖关系和协作明确。


+1 用于讨论问题的根源,而不仅仅是如何解决它。
在成熟的多层应用程序中,第二种方法通常会创建大量不必要的代码。通过用逻辑污染中间层的代码以传递到较低层,否则在该级别是不必要的对象,我们不是在创建不需要的依赖关系吗?假设导航堆栈中的第 4 层启动后台操作,例如文件上传。现在假设您想在完成时提醒用户,但到那时,用户可能处于应用程序的完全不同部分,该部分仅与初始视图共享第 1 层。大量不需要的代码...
@RasmusFaber 单例模式不会隐藏依赖项。您抱怨隐藏依赖项是与模式不同的问题。的确,模式很容易犯这个错误,但是非常有可能和谐地执行 IoC 和 Singleton 模式。例如,我可以创建一个需要对其实例进行特殊生命周期管理的第 3 方库,并且任何使用我的库的人仍应使用经典的依赖注入而不是“天钩”我的单例将我的单例实例“传递”给他们的类从每个切入点。
@HariKaramSingh,您可以使用订阅/发布模式或系统范围的事件总线(必须在所有相关方之间共享,而不是单例)来解决这个问题。
此外,发布/订阅或观察者模式可能与任何不得不通过严重依赖它们的框架逐步调试的人所证明的“失去耦合”一样糟糕。至少对于单例来说,被调用的方法名称与调用代码在同一个地方:) 就个人而言,我认为所有这些策略都有它们的位置,没有一个可以盲目地实现。邋遢的编码员会做出任何模式的意大利面,负责任的编码员不必为自己的意识形态限制过多地负担,以创建优雅的代码。
C
CodingWithoutComments

Patterns I Hate”中的 Alex Miller 引用了以下内容:

“当单身人士似乎是答案时,我发现通常更明智的是:

创建单例的接口和默认实现 在系统的“顶部”构造默认实现的单个实例。这可能在 Spring 配置中,或者在代码中,或者根据您的系统以各种方式定义。将单个实例传递给需要它的每个组件(依赖注入)


我知道这已经快 5 年了,但你能扩展一下吗?我想我被告知创建单例的正确方法是使用接口。如果我正在做的事情是“好的”,并且不像我被告知的那么糟糕,我会很感兴趣。
s
seuvitor

我遇到的最好的解决方案是使用工厂模式来构造类的实例。使用该模式,您可以确保只有一个类的实例在使用它的对象之间共享。

我虽然管理起来会很复杂,但是在阅读了这篇博文 "Where Have All the Singletons Gone?" 之后,它似乎很自然。顺便说一句,它对隔离单元测试有很大帮助。

总而言之,你需要做什么?每当一个对象依赖于另一个对象时,它将仅通过其构造函数接收它的实例(您的类中没有 new 关键字)。

class NeedyClass {

    private ExSingletonClass exSingleton;

    public NeedyClass(ExSingletonClass exSingleton){
        this.exSingleton = exSingleton;
    }

    // Here goes some code that uses the exSingleton object
}

然后,工厂。

class FactoryOfNeedy {

    private ExSingletonClass exSingleton;

    public FactoryOfNeedy() {
        this.exSingleton = new ExSingletonClass();
    }

    public NeedyClass buildNeedy() {
        return new NeedyClass(this.exSingleton);
    }
}

由于您将只实例化您的工厂一次,因此将有一个 exSingleton 实例化。每次调用 buildNeedy 时,NeedyClass 的新实例都会与 exSingleton 捆绑在一起。

我希望这有帮助。请指出任何错误。


确保仅在需要私有构造函数并将 buildNeedy 方法定义为静态方法时才实例化您的工厂。
Julien 是对的,这个修复有一个根本缺陷,那就是你隐含地说你只能实例化一个工厂。如果您采取必要的预防措施以确保仅实例化一个工厂(就像 Julien 所说的那样),您最终会得到......一个单例!实际上,这种方法只是在单例之上添加了一个不必要的抽象层。
还有另一种方法可以只拥有一个实例。您只能实例化您的工厂一次。编译器无需强制执行此操作。这也有助于您需要不同实例的单元测试。
对于单例错误解决的问题,这是我见过的最好的解决方案 - 添加依赖项而不会使每个方法调用变得混乱(因此重新设计接口并重构其依赖项)。稍微修改一下就很适合我的情况了外部设置公共实例的方法,并在构造期间使用该实例)。
所以基本上这种方法的优点是你只需要围绕一个对象实例(工厂)而不是潜在的一大堆对象(你选择不使用单例)?
K
Kai

Spring 或任何其他 IoC 容器在这方面做得相当不错。由于类是在应用程序本身之外创建和管理的,因此容器可以使简单的类成为单例并在需要的地方注入它们。


有关于上述主题的任何链接吗?
这些框架似乎是特定于 java 的——还有其他特定于语言的选项吗?还是与语言无关?
对于 .NET:Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity 实际上是一个依赖注入框架,但它可能适用于这个主题。
为什么没有太多的选票? IOC 是避免 Singleton 的完美解决方案。
@CodingWithoutComments 我知道 PHP 有 Symfony 服务容器。 ruby dudes 还有其他注入依赖项的方法。你有你感兴趣的特定语言吗?
T
Thomas Owens

您不必特意去避免任何模式。模式的使用要么是设计决策,要么是自然契合(它就在适当的位置)。在设计系统时,您可以选择使用模式或不使用模式。但是,您不应该竭尽全力避免最终成为设计选择的任何事情。

我不会避免单例模式。要么合适,我使用它,要么不合适,我不使用它。我相信就这么简单。

单例的适当性(或缺乏)取决于具体情况。这是一个必须做出的设计决策,并且必须理解(并记录)该决策的后果。


我本来想说同样的话,但这并不能完全回答他的问题:)
请在您的回答中讨论何时合适或不合适。漂亮请。
它确实回答了这个问题。我没有避免使用单例模式的技术,因为我不会竭尽全力避免它,而且我认为任何开发人员都不应该这样做。
我认为您无法一概而论何时合适或不合适。这都是特定于项目的,并且很大程度上取决于相关系统的设计。对于这样的决定,我不使用“经验法则”。
唔。我不明白为什么这会被否决。我回答了这个问题 - 你有什么技巧可以避免使用单例模式? - 说我没有技术,因为我不会竭尽全力避免这种模式。否决者能否详细说明他们的推理?
M
Mark M

Monostate(在 Robert C. Martin 的敏捷软件开发中描述)是单例的替代方案。在这种模式中,类的数据都是静态的,但 getter/setter 是非静态的。

例如:

public class MonoStateExample
{
    private static int x;

    public int getX()
    {
        return x;
    }

    public void setX(int xVal)
    {
        x = xVal;
    }
}

public class MonoDriver
{
    public static void main(String args[])
    {
        MonoStateExample m1 = new MonoStateExample();
        m1.setX(10);

        MonoStateExample m2 = new MonoStateExample();
        if(m1.getX() == m2.getX())
        {
            //singleton behavior
        }
    }
}

Monostate 与单例具有相似的行为,但这样做的方式是程序员不一定知道正在使用单例这一事实。


这值得更多的选票;我从 Google 来到这里,寻找 MonoState 复习!
@Mark 在我看来,这种设计在线程安全方面很难锁定。我是为 MonoStateExample 中的每个静态变量创建一个实例锁,还是创建一个所有属性都利用的锁?两者都有严重的后果,很容易在单例模式中解决。
这个例子是单例模式的替代,但不能解决单例模式的问题。
P
Pop Catalin

存在单例模式是因为在某些情况下需要单个对象来提供一组服务。

即使是这种情况,我仍然考虑通过使用表示实例的全局静态字段/属性来创建单例的方法,这是不合适的。这是不合适的,因为它在静态字段和对象之间的代码中创建了依赖关系,而不是对象提供的服务。

因此,我建议不要使用经典的单例模式,而是将服务“like”模式与服务容器一起使用,而不是通过静态字段使用单例,而是通过请求所需服务类型的方法获取对它的引用。

*pseudocode* currentContainer.GetServiceByObjectType(singletonType)
//Under the covers the object might be a singleton, but this is hidden to the consumer.

而不是单一的全局

*pseudocode* singletonType.Instance

这样,当您想将对象的类型从单例更改为其他对象时,您将可以轻松地做到这一点。另外一个额外的好处是,您不必将对象实例的分配传递给每个方法。

另请参阅Inversion of Control,其想法是通过将单例直接暴露给消费者,您可以在消费者和对象实例之间创建依赖关系,而不是对象提供的对象服务。

我的意见是尽可能隐藏单例模式的使用,因为它并不总是可以避免或可取的。


我不太喜欢您的服务容器示例。你有可以链接到的在线资源吗?
看看“城堡项目 - 温莎集装箱”castleproject.org/container/index.html。不幸的是,很难找到关于这个主题的抽象出版物。
D
David Koelle

如果您使用 Singleton 来表示单个数据对象,则可以改为将数据对象作为方法参数传递。

(虽然,我认为这是首先使用单例的错误方法)


p
plinth

如果你的问题是你想保持状态,你需要一个 MumbleManager 类。在开始使用系统之前,您的客户端会创建一个 MumbleManager,其中 Mumble 是系统的名称。通过它保留状态。您的 MumbleManager 可能会包含一个包含您的状态的属性包。

这种风格感觉很像 C 而不是很像对象——你会发现定义你的系统的对象都会引用同一个 MumbleManager。


在不提倡或反对 Singleton 的情况下,我必须说这个解决方案是一种反模式。 MumbleManager 成为一个包含各种不同知识的“神级”,违反了 Single Responsibility。它也可能是所有应用程序关心的单例。
D
David

使用普通对象和工厂对象。工厂负责仅使用配置信息(例如,它包含)和行为来监管实例和普通对象详细信息。


工厂通常不是单身吗?这就是我通常看到实现工厂模式的方式。
M
Mecki

实际上,如果您从头开始设计避免单例,您可能不必通过使用静态变量来解决不使用单例的问题。当使用静态变量时,您也或多或少地创建了一个单例,唯一的区别是您创建了不同的对象实例,但是在内部它们都表现得好像在使用单例一样。

您能否给出一个详细的示例,您使用单例或当前使用单例并且您试图避免使用它?这可以帮助人们找到一个更奇特的解决方案,如何在没有 Singleton 的情况下处理这种情况。

顺便说一句,我个人对单身人士没有任何问题,我无法理解其他人对单身人士的问题。我看不出他们有什么不好。也就是说,如果您没有滥用它们。每一种有用的技术都可能被滥用,如果被滥用,将导致负面结果。另一种经常被滥用的技术是继承。仍然没有人会因为有些人可怕地滥用它而说继承是不好的。


u
user18834

就我个人而言,实现类似于单例的行为的更明智的方法是使用完全静态的类(静态成员、静态方法、静态属性)。大多数时候我以这种方式实现它(从用户的角度我想不出任何行为差异)


w
workmad3

我认为监管单身人士的最佳地点是在班级设计层面。在这个阶段,你应该能够绘制出类之间的交互,看看是否有什么东西绝对需要在应用程序生命的任何时候都只存在这个类的一个实例。

如果是这种情况,那么你有一个单身人士。如果您在编码过程中为了方便而将单例放入其中,那么您真的应该重新审视您的设计并停止编码所说的单例:)

是的,“警察”是我在这里的意思,而不是“避免”。单例不是要避免的(就像 goto 和全局变量不是要避免的一样)。相反,您应该监控它的使用情况,并确保它是有效完成您想做的事情的最佳方法。


我完全理解你在说什么工作狂。我完全同意。但是,您的回答仍然没有具体回答我的问题。我不希望这是“什么时候适合使用单例?”的另一个问题。我只是对单例的可行替代方案感兴趣。
M
Manrico Corazzi

我主要使用单例作为“方法容器”,根本没有状态。如果我需要与许多类共享这些方法并希望避免实例化和初始化的负担,我会创建一个上下文/会话并在那里初始化所有类;涉及会话的所有内容也可以访问由此包含的“单例”。


s
staticsan

由于没有在高度面向对象的环境(例如 Java)中进行编程,因此我并没有完全理解讨论的复杂性。但是我已经在 PHP 4 中实现了一个单例。我这样做是为了创建一个“黑盒”数据库处理程序,该处理程序自动初始化并且不必在不完整且有些损坏的框架中向上和向下传递函数调用。

阅读了一些单例模式的链接后,我不完全确定我会以完全相同的方式再次实现它。真正需要的是具有共享存储的多个对象(例如实际的数据库句柄),这几乎就是我的调用变成的。

像大多数模式和算法一样,“仅仅因为它很酷”而使用单例是错误的做法。我需要一个看起来很像单身人士的真正“黑盒”电话。 IMO 这就是解决这个问题的方法:注意模式,但也要看看它的范围更广,它的实例需要在什么级别上是独一无二的。


D
DrPizza

你是什么意思,我有什么技巧可以避免它?

为了“避免”它,这意味着在我遇到的许多情况下,单例模式自然很适合,因此我必须采取一些措施来化解这些情况。

但没有。我不必避免单例模式。它根本不会出现。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅