前言:
工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用(即使用new运算符)。对象仍将通过new运算符创建,只是该运算符改在工厂方法中调用罢了。工厂方法返回的对象通常被称作“产品”。
目录
3.1、当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。
3.2、如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。
3.3、如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。
一、结构
1.产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。
2.具体产品(Concrete Products)是产品接口的不同实现。
3.创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。注意,尽管它的名字是创建者,但他最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。
4.具体创建者(ConcreteCreators)将会重写基础工厂方法,使其返回不同类型的产品。注意,并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、对象池或其他来源的已有对象。
二、代码实现
/**
* @Description: 按钮根类
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public interface Button {
/**
* 渲染
*/
void render();
/**
* 点击
*/
void onClick();
}
/**
* @Description: html按钮
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public class HtmlButton implements Button {
/**
* 渲染
*/
@Override
public void render() {
System.out.println("<button>Test Button</button>");
onClick();
}
/**
* 点击
*/
@Override
public void onClick() {
System.out.println("Click! Button says - 'Hello World!'");
}
}
/**
* @Description: 界面按钮
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public class WindowsButton implements Button {
private JPanel panel = new JPanel();
private JFrame frame = new JFrame();
private JButton button;
/**
* 渲染
*/
@Override
public void render() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello World!");
label.setOpaque(true);
label.setBackground(new Color(235, 233, 126));
label.setFont(new Font("Dialog", Font.BOLD, 44));
label.setHorizontalAlignment(SwingConstants.CENTER);
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
frame.getContentPane().add(panel);
panel.add(label);
onClick();
panel.add(button);
frame.setSize(320, 200);
frame.setVisible(true);
onClick();
}
/**
* 点击
*/
@Override
public void onClick() {
button = new JButton("Exit");
button.addActionListener(new ActionListener() {
/**
* 事件
*
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
System.exit(0);
}
});
}
}
/**
* @Description: 工厂基类
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public abstract class Dialog {
public void renderWindow() {
Button okButton = createButton();
okButton.render();
}
/**
* 子类将重写此方法,以创建特定的button *对象
*/
public abstract Button createButton();
}
/**
* @Description: 生产html button
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public class HtmlDialog extends Dialog{
/**
* 子类将重写此方法,以创建特定的button *对象
*/
@Override
public Button createButton() {
return new HtmlButton();
}
}
/**
* @Description: 生产Windows button
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public class WindowsDialog extends Dialog{
/**
* 子类将重写此方法,以创建特定的button *对象
*/
@Override
public Button createButton() {
return new WindowsButton();
}
}
/**
* @Description: 客户端代码
*
* @Author HJW
* @Date 2021/4/4
* @Version V1.0
**/
public class Demo {
private static Dialog dialog;
public static void main(String[] args) {
configure();
runBusinessLogic();
}
/**
* 通常根据配置或*环境选项来选择具体工厂
*/
static void configure() {
if (System.getProperty("os.name").equals("Windows 10")) {
dialog = new WindowsDialog();
} else {
dialog = new HtmlDialog();
}
}
/**
* 所有客户端代码都应通过抽象接口与工厂和产品一起使用。这样,它便不在乎与之合作的工厂以及退回哪种产品
*/
static void runBusinessLogic() {
dialog.renderWindow();
}
}
三、应用场景
3.1、当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。
工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。
例如,如果需要向应用中添加一种新产品,你只需要开发新的创建者子类,然后重写其工厂方法即可。
3.2、如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。
继承可能是扩展软件库或框架默认行为的最简单方法。但是当你使用子类替代标准组件时,框架如何辨识出该子类?解决方案是将各框架中构造组件的代码集中到单个工厂方法中,并在继承该组件之外允许任何人对该方法进行重写。
让我们看看具体是如何实现的。假设你使用开源UI框架编写自己的应用。你希望在应用中使用圆形按钮,但是原框架仅支持矩形按钮。你可以使用圆形按钮RoundButton子类来继承标准的按钮Button类。但是,你需要告诉UI框架UIFramework类使用新的子类按钮代替默认按钮。为了实现这个功能,你可以根据基础框架类开发子类圆形按钮 UIUIWithRoundButtons,并且重写其createButton创建按钮方法。基类中的该方法返回按钮对象,而你开发的子类返回圆形按钮对象。现在,你就可以使用圆形按钮 UI类代替UI框架类。
3.3、如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。
在处理大型资源密集型对象(比如数据库连接、文件系统和网络资源)时,会经常碰到这种资源需求。让我们思考复用现有对象的方法:
1.首先,你需要创建存储空间来存放所有已经创建的对象。
2.当他人请求一个对象时,程序将在对象池中搜索可用对象。
3.…然后将其返回给客户端代码。
4.如果没有可用对象,程序则创建一个新对象(并将其添加到对象池中)。
这些代码可不少!而且它们必须位于同一处,这样才能确保重复代码不会污染程序。可能最显而易见,也是最方便的方式,就是将这些代码放置在我们试图重用的对象类的构造函数中。但是从定义上来讲,构造函数始终返回的是新对象,其无法返回现有实例。因此,你需要有一个既能够创建新对象,又可以重用现有对象的普通方法。这听上去和工厂方法非常相像。
四、JDK中的应用
java.util.Calendar#getInstance()
java.util.ResourceBundle#getBundle()
java.text.NumberFormat#getInstance()
java.nio.charset.Charset#forName()
java.net.URLStreamHandlerFactory#createURLStreamHandler(String)
(Returns different singleton objects, depending on a protocol)java.util.EnumSet#of()
javax.xml.bind.JAXBContext#createMarshaller()
and other similar methods.
五、优缺点
- 可以避免创建者和具体产品之间的紧密耦合。
- 单一职责原则。可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
- 开闭原则。无需更改现有客户端代码,就可以在程序中引入新的产品类型。
- 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。
六、与其他模式的关系
- 在许多设计工作的初期都会使用工厂方法(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂、原型或生成器(更灵活但更加复杂)。
- 抽象工厂模式通常基于一组工厂方法,但也可以使用原型模式来生成这些类的方法。
- 可以同时使用工厂方法和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
- 原型并不基于继承,因此没有继承的缺点。另一方面,原型需要对被复制对象进行复杂的初始化。工厂方法基于继承,但是它不需要初始化步骤。
- 工厂方法是模板方法的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。