C++ Singleton类 - 继承良好实践

kir*_*off 8 c++ oop inheritance singleton design-patterns

在现有项目中,我将继承一个声明为Singleton的Controller类(MVC),以便定义我自己的处理.如何恰当地推导出这个Singleton类?

首先,我扩展了上下文并需要这种继承.

我添加到现有软件的应用程序想要使用MVC模块,该模块执行与我愿意执行的任务几乎相同的任务.它使用相同的方法签名和稍作修改.重写我自己的MVC模块肯定会重复代码.现有模块本质上面向其应用于软件的另一部分,我不能简单地使用相同的模块.但是它被编写为模型 - 视图 - 控制器模式,其中Controller是Singleton.我已经得到了View.

其次,我怀疑我可以经典推导出Singleton类.

从继承类调用构造函数只会为父类调用getinstance(),并且无法从派生类(?)返回对象.

第三,我是如何看待某种方式来处理的.请评论/帮助我改进!

我在一个可以调用AbstractController的类中复制了整个Singleton类.我两次推出这个课程.第一个孩子是单身,采用父母阶级的整体治疗.第二个孩子是我应用程序的控制器,具有自己重新定义的处理.

谢谢!

cHa*_*Hao 28

事实是,单身人士和继承人并不能很好地融合在一起.

是啊,是啊,辛格尔顿爱好者和GoF的崇拜将是对我的一切对于这一点,说:"好了,如果你把你的构造保护..."和"你不具备getInstance对类的方法,你可以把它......",但它们只是证明了我的观点.单身人士必须跳过一些箍,才能成为单身人士和基地.

但只是回答这个问题,说我们有一个单身基类.它甚至可以在某种程度上通过继承来强制执行其单一性.(当构造函数不再是私有的时,它会执行少数可以工作的事情之一:如果另一个Base已经存在,它会引发异常.)假设我们还有一个Derived继承自的类Base.既然我们允许继承,那么我们也可以说可以有任意数量的其他子类Base,可能会也可能不会继承Derived.

但是有一个问题 - 你要么已经遇到了,要么很快就会遇到问题.如果我们在Base::getInstance没有构造对象的情况下调用,我们将得到一个空指针.我们想要找回任何存在的单个对象(它可能是a Base,和/或a Derived,和/或a Other).但是很难这样做并且仍然遵循所有规则,因为只有几种方法可以这样做 - 而且所有这些都有一些缺点.

  • 我们可以创建一个Base并返回它.螺丝DerivedOther.最终结果:Base::getInstance()始终返回完全一个Base.孩子们的课程永远不会发挥.有点失败的目的,IMO.

  • 我们可以getInstance在我们的派生类中放置一个自己的,并让调用者说Derived::getInstance()如果他们特别想要一个Derived.这显着增加了耦合(因为调用者现在必须知道具体请求a Derived,并最终将自己绑定到该实现).

  • 我们可以做最后一个的变体 - 但不是获取实例,而是函数只创建一个.(虽然我们正在使用它,但是我们将函数重命名为initInstance,因为我们并不特别关心它得到什么 - 我们只是调用它以便它创建一个新的Derived并将其设置为One True Instance.)

所以(除非有任何不明之处),它有点像这样......

class Base {
    static Base * theOneTrueInstance;

  public:
    static Base & getInstance() {
        if (!theOneTrueInstance) initInstance();
        return *theOneTrueInstance;
    }
    static void initInstance() { new Base; }

  protected:
    Base() {
         if (theOneTrueInstance) throw std::logic_error("Instance already exists");
         theOneTrueInstance = this;
    }

    virtual ~Base() { } // so random strangers can't delete me
};

Base* Base::theOneTrueInstance = 0;


class Derived : public Base {
  public:
    static void initInstance() {
        new Derived;  // Derived() calls Base(), which sets this as "the instance"
    }

  protected:
    Derived() { }   // so we can't be instantiated by outsiders
    ~Derived() { }  // so random strangers can't delete me
};
Run Code Online (Sandbox Code Playgroud)

在你的初始化代码中,你说Base::initInstance();或者Derived::initInstance();,取决于你想要单身人士的类型.当然,你必须转换返回值Base::getInstance()才能使用任何Derived特定的函数,但是如果没有强制转换,你可以使用任何定义的函数Base,包括被重写的虚函数Derived.

请注意,这种方式也有其自身的一些缺点,但是:

  • 它将强制单一性的大部分负担放在基类上.如果基地没有这个或类似的功能,你无法改变它,你就有点紧张.

  • 但是,基类不能承担所有责任 - 每个类都需要声明一个受保护的析构函数,或者有人可以在适当地转换它之后删除一个实例,整个事情就变成了地狱.更糟糕的是,编译器无法强制执行此操作.

  • 因为我们使用受保护的析构函数来阻止某些随机的schmuck删除我们的实例,除非编译器比我担心的更聪明,即使运行时也无法在程序结束时正确删除您的实例.再见,RAII ......你好"检测到内存泄漏"的警告.(当然,内存最终会被任何体面的操作系统回收.但是如果析构函数没有运行,你就不能依赖它来为你做清理.你需要在你之前调用某种类型的清理函数退出,这不会给你任何接近RAII可以给你的保证.)

  • 它公开了一种initInstance方法,IMO并不真正属于每个人都能看到的API.如果你愿意,你可以initInstance私有化,让你的init函数成为一个friend,但是你的类正在对自身之外的代码做出假设,并且耦合的东西重新发挥作用.

另请注意,上面的代码根本不是线程安全的.如果您需要,那就是您自己.

说真的,不那么痛苦的路线是忘记试图强化单身性.确保只有一个实例的最简单的方法是只创建一个实例.如果您需要在多个位置使用它,请考虑依赖注入.(非框架版本相当于"将对象传递给需要它的东西".:P)我去设计上面的东西只是为了试图证明自己对单身人士和继承的错误,并且只是重申了自己是多么邪恶这个组合是.我不建议在实际代码中实际执行此操作.

  • 这是一个很好的答案,非常详细,并且有一些丰富多彩的语言和幽默。令人惊奇的是它在 SO 雷达下飞行。 (2认同)

je4*_*e4d 5

我不确定我是否完全理解您正在处理的情况,从单例派生是否可能或合适在很大程度上取决于单例的实现方式。

但是既然你提到了“良好的实践”,那么在阅读这个问题时会想到一些一般性的观点:

  1. 继承通常不是实现代码重用的最佳工具。请参阅:更喜欢组合而不是继承?

  2. 使用单例和“良好实践”通常不会一起使用!请参阅:单身人士有什么不好?

希望有帮助。