设计模式一(工厂方法模式)
原来大学时期学了两年的Java好久没用全给忘了,最近有时间开始重拾Java。而设计模式没有语言限制,了解后对大家的能力提升和工作效率都有帮助。
Java中共有23中设计模式,分成三类:
- 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
今天简单介绍下工厂方法模式,说到工厂方法模式必须先了解一下简单工厂模式。简单工厂、工厂方法和抽象工厂被称为工厂三兄弟。工厂方法模式解决了简单工厂的”开闭原则“。
简单工厂
简单工厂,就是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
简单工厂模式中包含的角色及其相应的职责如下:
- 工厂角色(Creator):这是简单工厂模式的核心,由它负责创建所有的类的内部逻辑。当然工厂类必须能够被外界调用,创建所需要的产品对象。
- 抽象(Product)产品角色:简单工厂模式所创建的所有对象的父类,注意,这里的父类可以是接口也可以是抽象类,它负责描述所有实例所共有的公共接口。
- 具体产品(Concrete Product)角色:简单工厂所创建的具体实例对象,这些具体的产品往往都拥有共同的父类。
下面我们以创建打印机为例看看具体实现,我们先定义一个抽象打印机接口:
/**
* 打印机
*/
public interface Printer {
void print();
}
然后定义它的两个实例喷墨打印机和激光打印机
/**
* 喷墨打印机
*/
class InkJetPrinter implements Printer{
@Override
public void print() {
System.out.println("喷墨打印机doing...");
}
}
/**
* 激光打印机
*/
class LaserPrinter implements Printer{
@Override
public void print() {
System.out.println("激光打印机doing...");
}
}
如果我们想创建打印机的实例,如果平常会直接在需要的地方new一个打印机出来。
//1.0 普通创建形式
InkJetPrinter inkJetPrinter = new InkJetPrinter();
inkJetPrinter.print();
LaserPrinter laserPrinter = new LaserPrinter();
laserPrinter.print();
现在采用简单工厂模式,就需要创建一个工厂用来创建打印机,我们叫这个工厂为SimpleFactory。
enum PrinterType{InkJet, Laser}
public class SimpleFactory {
public static Printer createPrinter(PrinterType type) {
switch (type){
case InkJet :
return new InkJetPrinter();
case Laser:
return new LaserPrinter();
default:
return null;
}
}
}
当需要创建一个工厂的时候,就需要先创建一个简单工厂,然后传入需要创建的打印机类型create一个打印机出来。使用Printer去接受对象,客户端不知道InkJetPrinter和LaserPrinter的存在,只知道怎么使用就行了。
//2.0 简单工厂模式
Printer inkJetPrinter1 = SimpleFactory.createPrinter(PrinterType.InkJet);
inkJetPrinter1.print();
Printer laserPrinter1 = SimpleFactory.createPrinter(PrinterType.Laser);
laserPrinter1.print();
优点:工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
缺点:由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了,这违背了“开闭原则”。
当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;
工厂方法模式
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
- 工厂方法模式是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。
- 工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”
工厂方法模式包含以下角色:
- Product:抽象产品 (Printer)
- ConcreteProduct:具体产品(InkJetPrinter、LaserPrinter)
- Factory:抽象工厂(PrinterFactory)
- ConcreteFactory:具体工厂(InkJetFactory、LaserFactory)
我们接着以刚才的打印机为例,抽象产品打印机和具体产品我们已经有了,下面需要创建一个抽象工厂 :
//抽象工厂
interface PrinterFactory{
public Printer createPrinter();
}
以及生产具体产品的具体工厂:
//喷墨打印机工厂
class InkJetFactory implements PrinterFactory {
@Override
public Printer createPrinter() {
return new InkJetPrinter();
}
}
//激光打印机工厂
class LaserFactory implements PrinterFactory {
@Override
public Printer createPrinter() {
return new LaserPrinter();
}
}
当需要实例化具体打印机的时候先要创建相应的打印机工厂,然后create具体的打印机。
//3.0 工厂方法模式
InkJetFactory inkJetFactory = new InkJetFactory();
Printer inkJetPrinter2 = inkJetFactory.createPrinter();
inkJetPrinter2.print();
LaserFactory laserFactory = new LaserFactory();
Printer laserPrinter2 = laserFactory.createPrinter();
laserPrinter2.print();
工厂方法模式有哪些优势呢?
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
当然工厂方法模式也有他的劣势:
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
工厂方法模式和简单工厂模式虽然都是通过工厂来创建对象,他们之间最大的不同是——工厂方法模式在设计上完全完全符合“开闭原则”。
总结
看到这里可能会有童鞋不理解了,我为什么要这么麻烦先创建一个工厂再创建产品对象呢?我直接new一个对象出来不挺好的吗!我一开始也有这样的疑惑,后来思考了一下。
应该是它主要解决框架或者sdk不想暴露给客户端具体的实例的创建过程,对对象的创建进行分装。
还有一种情况是如果一个对象你在多个不同的文件中创建了实例,当需要改成单例模式或者其他实例化方式的时候就需要改动所有实例化的地方,而使用工厂模式只需要改动相应的工厂一个地方,客户端并不需要任何改动。
更多精彩文章请浏览我的博客:http://lvesli.com,本博客发布也会在 lecoding 微信公众号中同步更新,欢迎大家订阅,有什么问题可以在此一起交流。公众号搜索: 乐Coding 或者 ***lecoding ***或者微信扫描下方二维码: