文章目录
- 前言
- 1.产品的理解
- 2.简单工厂模式
- 3.工厂方法模式
- 4.抽象工厂模式
- 5.小结
前言
工厂模式有三种:
- 简单工厂模式
- 工厂方法模式
- 抽象方法模式
工厂模式,如果从代码层面来看,是隐去了对new的使用,使得调用者不必关心new的对象是什么,只需要关注功能的实现,例如对于一把椅子,不管是红木的还是马扎,最基础的功能都是坐。从更深一点的思想层面的理解,是隐去了背后的实现,提供了统一的功能接口。
本文将会从提出问题,分析问题,解决问题的思路去解释三种设计模式的思想,语言基于C++,如有错误还请指正。
1.产品的理解
假设我们现在有一个需求为实现一个按钮,使用按钮时,要求根据当前所处的平台,WIN还是Linux,分别打印对应的平台按钮。最直观的实现就是:
class Button
{
public:
Button() {}
void print_iplatform(string platform)
{
if (platform == "Windows")
{
cout << "Windows Button" << endl;
}
else if (platform == "Linux")
{
cout << "Linux Button" << endl;
}
}
};
int main()
{
Button* button = new Button();
button->print_iplatform("Windows");
}
现在,我们拥有了一个按钮,对于输入不同的参数,按下按钮时打印出不同的数字。看起来一切都很美好,但是,如果按钮需要别的功能呢,比如改变按钮颜色等等,同时还需要支持别的平台呢,例如MAC,对于每一个新功能,都需要添加很多的if else
兼容不同平台,而且,当功能发生改变的时候,不得不去对Button
类找到对应的if else
做修改,在兼容性上,这就是一种灾难。
如何规避这种问题呢,我们想到,可以实现一个抽象类Button
,规定每一个按钮必须实现哪些功能,我们只需要再创建平台按钮类型继承于抽象类Button
,看起来一切都可以解决。代码示例如下:
//抽象类,规定了每一种按钮必须实现的功能
class Button
{
public:
Button() {}
virtual void print_iplatform() = 0;
virtual void change_color() = 0;
};
class WindowsButton : public Button
{
public:
void print_iplatform() override
{
std::cout << "Windows Button" << std::endl;
}
void change_color() override
{
std::cout << "change Windows Button color" << std::endl;
}
};
class LinuxButton : public Button
{
public:
void print_iplatform() override
{
std::cout << "Linux Button" << std::endl;
}
void change_color() override
{
std::cout << "change Linux Button color" << std::endl;
}
};
int main()
{
Button* button = new WindowsButton();
button->print_iplatform();
button->change_color();
}
现在,只需要new不同类型的对象,即可使用不同平台的按钮。这些按钮在工厂模式里就是产品。
因为很多教程或者博客,都喜欢使用一些实物去举例子,在阅读过程中,很难将诸如披萨,蛋糕等例子应用到实际的编程实践中,这里我简单按照我的理解来概括一下产品:一些类型,这些类型必须在不同环境下实现相同的功能,椅子就是坐,按钮就是按下触发一些功能,这些类型往往需要继承于一个抽象类,抽象类中会规定这些继承的类必须实现的一些功能,从而在新建对象的时候,变量只要使用父类型,new的时候使用子类型,即可屏蔽掉不同子类型实现上的差异。在示例中,Button
就是抽象类,我们在使用的时候也是Button* button = new WindowsButton();
,而非定义变量类型为WindowsButton
。我们不关心产品是什么,只关注产品具有什么功能。
这里还有一个需要注意的是产品的继承,只解决掉了一个问题,那就是在每次创建一个新功能的时候,我们不需要重复写一堆if else
,但是在实际使用产品的时候,还是需要一堆if else
,因为你需要根据你所处的平台,来决定到底new
哪一个按钮。
2.简单工厂模式
很多时候,我们并不想把全部的代码都提供给用户,往往只是一个API,例如getButton
,对于用户来说只需要完成一定的功能,而非自己去实例化管理创建按钮。
另外,尽管我们上面的例子中,在main
函数里直接使用了WinButton
,但是如果你在做一套UI控件或者引擎,怎么可能在代码里直接定义平台呢?如果不直接定义,我们也不希望在主程序里使用if else
来创建不同的示例。例如:
int main()
{
Button* button;
if (platform == "Windows")
button = new WindowsButton();
else if (platform == "Linux")
button = new LinuxButton();
button->print_iplatform();
button->change_color();
}
这种在主程序里根据不同平台实例化显得异常丑陋。
所以我们需要一个实例化的中心,将本来应该在主程序里实例化的步骤,推迟到一个类型中,这个类型就是工厂,工厂负责创建产品。
/**
* 具体的Button实现可以看上文,这里读者也可以复制到自己的
* pc里跑一下看看
*/
//Button工厂
class IPlatformButton
{
public:
Button* create_button(string platform)
{
if (platform == "Windows")
return new WindowsButton();
else if (platform == "Linux")
return new LinuxButton();
return nullptr;
}
};
int main()
{
IPlatformButton* button_factory = new IPlatformButton();
Button* button = button_factory->create_button("Windows");
button->print_iplatform();
button->change_color();
}
这里我们实现了一个平台按钮的创建器,我们只需要在main
里实例化IPlatformButton
,然后使用该类型创建需要的按钮即可,不把if else
放在主函数里,使得我们的代码看起来简洁美观。那么到现在,我们就得到了一个简单工厂模式。这个工厂模式负责根据不同的类型创建不同的对象。
3.工厂方法模式
看起来简单工厂模式已经足够我们使用了,但是现实中的需求无穷无尽。我们看到IPlatformButton
作为我们的工厂,函数实现里也有许多if else
,如果工厂也需要添加一些额外功能呢,我们又陷入到每次创建一个新功能但是需要if else
的境地了。拥有一个工厂,是为了将main
丑陋的if else
移动到一个封装好的类里,但是这个工厂类扩展新功能时是很不方便的。还记得我们是怎么解决产品里的if else
风暴吗,继承。我们要做的,就是将工厂像产品一样,抽调出共有的部分,作为抽象类,然后去做继承实现具体的工厂:
class IPlatformButton
{
public:
virtual Button* create_button() = 0;
};
class WindowsPlatformButton : public IPlatformButton
{
public:
Button* create_button() override
{
return new WindowsButton();
}
};
class LinuxPlatformButton : public IPlatformButton
{
public:
Button* create_button() override
{
return new LinuxButton();
}
};
int main()
{
IPlatformButton* button_factory = new LinuxPlatformButton();
Button* button = button_factory->create_button();
button->print_iplatform();
button->change_color();
}
这里我们就得到了工厂方法模式。相比于简单工厂模式,将具体的实例化对象延迟到了一个类中,工厂方法模式将实例化对象延迟到了子类里。
有的同学会问,不对呀,这里不就又需要将if else
的判断加到main
里面了吗,这不是还是很丑陋,还需要再加一层工厂的工厂?实际上,这个问题确实存在,可能你也确实需要再加一层配置层,但是就像我们看到的一样,现在我们对Button
,以及IPlatformButton
已经完成了良好的封装。具体的按钮是为了实现功能,简单工厂模式是为了延迟实例化,避免main
里出现丑陋的if else
。工厂方法模式,是为了解决简单工厂存在的增加新功能需要添加大量if else
的问题,如果需要封装的话,当然还可以继续对工厂封装,例如工厂的工厂,只是为了决定该实例化哪种工厂,我们是为了解耦已经分层,最终会有一层只执行单一的创建何种工厂的功能。
实际上对于我们的按钮创建,即使使用简单工厂,依旧是足够的,因为我们只需要一个创建一个按钮而不需要这个工厂干别的事情。但是如果我们是创建一个椅子呢,工厂还需要选择快递或者售后呢?工厂还需要增加新的功能,为了避免if else
,就可以选择继续进行继承已经创建工厂的工厂,设计模式的目的不是拘泥于设计,而是找到适合自己的方式完成解耦。
4.抽象工厂模式
抽象工厂模式是工厂模式的进阶,通过上文,我们实现了一个制造按钮的工厂模式,可是平台控件只有一个按钮吗,加入我们需要滑块,或者光标之类的控件呢?工厂方法模式只会产生一种物品,抽象工厂模式,实现的工厂,生产的是多种物品。这就是定义里的抽象工厂(AbstractFactory)模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
class IPlatform
{
public:
virtual Button* create_button() = 0;
virtual Slider* create_slider() = 0;
};
class WindowsPlatform : public IPlatform
{
public:
Button* create_button() override
{
return new WindowsButton();
}
Slider* create_Slider() override
{
return new WindowsSlider();
}
};
class LinuxPlatform : public IPlatform
{
public:
Button* create_button() override
{
return new LinuxButton();
}
Slider* create_button() override
{
return new LinuxSlider();
}
};
int main()
{
IPlatform* iPlatform = new LinuxPlatform();
Button* button = iPlatform->create_button();
button->print_iplatform();
button->change_color();
Slider* slider = iPlatform->create_slider();
}
我们修改了一些代码,即可完成抽象工厂模式。我们把之前的IPlatformButton
修改为了IPlatform
,要求它不仅可以完成按钮的创建,还可以完成Slider
的创建。这就是抽象工厂模式,解决了工厂方法模式只会创建一种类型的缺点。
但是但是这里有一点值得注意,抽象工厂模式完成的是同一类型产品的创建,例如这里的按钮和滑块,都是产品,产品隶属的类型是平台类型。如果你的产品是一把椅子和一个桌子,这俩是产品,但是他们可能有不同的风格,例如欧式风格是他们所属的类型。你不能在一个创建桌椅的工厂里,创建一个按钮。
抽象工厂模式系统(Iplatform
)中有多个产品族(Button
,Slider
),每个具体工厂(Win
,Linux
)创建同一族但属于不同等级结构的产品。系统一次只可能消费其中某一族(Win
)产品,即同族的产品一起使用。
5.小结
本文解释了何为产品:需要实现的具体功能的对象,这些对象有必须实现的共性。解释了为什么需要简单工厂:对使用哪一个产品进行封装。而工厂方法模式解决了简单工厂扩展性不足的问题,抽象工厂模式解决了工厂方法只能生产一种产品的问题,三者是层层递进的,需要结合自己的需求使用不同的方法模式。没有必要强行使用抽象工厂。本质就是初始工厂的进一步封装。