在现有项目中,我将继承一个声明为Singleton的Controller类(MVC),以便定义我自己的处理。如何恰当地推导出这个Singleton类?
首先,我扩展了上下文并需要这种继承。
我添加到现有软件的应用程序想要使用MVC模块执行与我愿意执行的任务几乎相同的任务。它使用相同的方法签名和稍作修改。重写我自己的MVC模块肯定会重复代码。现有模块本质上面向其应用于软件的另一部分,我不能简单地使用相同的模块。但是它被编写为模型 - 视图 - 控制器模式,其中Controller是Singleton。我已经获得了View。
其次,我怀疑我可以经典地推导出Singleton类。
从继承类调用构造函数只会为父类调用getinstance(),并且无法从派生类(?)返回对象。
第三,我是如何看待某种方式来处理的。请评论/帮助我改进!
我将一个Singleton类复制到一个可以调用AbstractController的类中。我两次推出这个课程。第一个孩子是单身,采用父母阶级的整体治疗。第二个孩子是我应用程序的控制器,有自己重新定义的处理。
谢谢!
答案 0 :(得分:26)
事实是,单身人士和继承人并不能很好地发挥作用。
是的,是的,Singleton爱好者和GoF邪教组织将为此全身心投入,说“好吧,如果你让你的构造者受到保护......”和“你不 在课堂上有一个getInstance
方法,你可以把它......“,但它们只是证明了我的观点。单身人士必须跳过一些篮球才能成为单身人士和基地。
但只是回答这个问题,说我们有一个单身基类。它甚至可以在某种程度上通过继承来强制执行其单一性。 (当构造函数不再是私有的时,它会执行少数可以工作的事情之一:如果另一个Base
已经存在,它会抛出异常。)假设我们还有一个继承自{的类Derived
{1}}。由于我们允许继承,我们还要说可以有Base
的任何其他子类,可能会也可能不会继承Base
。
但是有一个问题 - 你已经或者很快就会遇到这个问题。如果我们在没有构造对象的情况下调用Derived
,我们将得到一个空指针。我们想要找回任何存在的单个对象(可能是Base::getInstance
,和/或Base
和/或Derived
)。但是很难这样做并且仍然遵循所有规则,因为只有几种方法可以做到 - 而且所有这些都有一些缺点。
我们可以创建一个Other
并将其返回。拧Base
和Derived
。最终结果:Other
始终返回Base::getInstance()
。孩子们的课程永远不会发挥。有点失败的目的,IMO。
我们可以在我们的派生类中添加我们自己的Base
,如果他们特别想要getInstance
,则让调用者说Derived::getInstance()
。这显着增加了耦合(因为调用者现在必须知道具体请求Derived
,并最终将自己绑定到该实现)。
我们可以做最后一个的变体 - 但不是获取实例,该函数只创建一个。 (虽然我们正在使用它,但是我们将函数重命名为Derived
,因为我们并不特别关心它是什么 - 我们只是调用它以便它创建一个新的initInstance
并设置作为一个真实的实例。)
所以(除非有任何不明之处),它有点像......
Derived
在您的初始化代码中,您说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
};
或Base::initInstance();
,具体取决于您想要单身人士的类型。当然,您必须从Derived::initInstance();
转换返回值才能使用任何Base::getInstance()
特定的函数,但是如果没有强制转换,您可以使用Derived
定义的任何函数,包括虚拟函数被Base
覆盖的函数。
请注意,这种做法本身也有许多缺点:
它将强制单一性的大部分负担放在基类上。如果基地没有这个或类似的功能,你无法改变它,你就有点不知所措了。
但是基类不能承担所有的责任 - 每个类都需要声明一个受保护的析构函数,或者有人可以在投射后删除一个实例适当地,整个事情变得地狱。更糟糕的是,编译器无法强制执行此操作。
因为我们正在使用受保护的析构函数来阻止某些随机的schmuck删除我们的实例,除非编译器比我担心的更聪明,否则即使运行时也无法在程序结束时正确删除您的实例。再见,RAII ......你好“检测到内存泄漏”的警告。 (当然,内存最终会被任何体面的操作系统回收。但是如果析构函数没有运行,你就不能依赖它来为你做清理。你需要在你之前调用某种类型的清理函数退出,这不会给你任何接近RAII可以给你的保证。)
它公开了Derived
方法,IMO并不真正属于每个人都能看到的API。如果你愿意,你可以让initInstance
私有,让你的init函数成为initInstance
,但是你的类正在对自身之外的代码做出假设,并且耦合的东西会重新发挥作用。
另请注意,上面的代码根本不是线程安全的。如果你需要,那就是你自己。
说真的,不那么痛苦的路线就是忘记试图强化单身性。确保只有一个实例的最简单方法是创建一个实例。如果您需要在多个位置使用它,请考虑依赖注入。 (非框架版本相当于“将对象传递给需要它的东西”。:P)我去设计上面的东西只是为了试图证明自己对单身人士和继承的错误,并且只是重申了自己是多么邪恶这个组合是。我不建议在实际代码中实际执行此操作。
答案 1 :(得分:3)
我不确定我是否理解你正在处理的情况,以及从单身人士中获取是否可能或适当,在很大程度上取决于单身人士的实施方式。
但是既然你提到了“良好做法”,那么在阅读这个问题时会想到一些一般性的观点:
继承通常不是实现代码重用的最佳工具。请参阅:Prefer composition over inheritance?
使用单身人士和“良好做法”一般不会在一起!请参阅:What is so bad about singletons?
希望有所帮助。