创建型模式中,常用的有工厂方法模式和抽象工厂模式,平时实践还包括简单工厂模式,虽然简单工厂模式不在23种设计模式中。
为了区分以上几种设计模式的差别,通过以下几个例子介绍这几种模式的使用场景以及优缺点。当然,由于本人接触设计模式时间还不长,难免有疏漏之处,欢迎各位提出意见。
考虑如今有一个产品,比如椅子,该椅子可以有多种风格,比如中式的,或者美式的。如果我们不想通过设计模式,想要什么类型的椅子,就可以直接实例化某个实体类获得。
“ChineseChair.java”——实体类
public class ChineseChair {
public void sit(){
System.out.println("这把椅子很具有文化气息");
}
}
“Client.java”——用户
public class Client {
public static void main(String[] args) {
ChineseChair chair = new ChineseChair();
chair.sit();
}
}
貌似当用户使用这种最基本的通过new关键字实例化一个对象的方式,并没有什么问题。但考虑如果我们要实例化的对象改为USAChair,那么所有之前使用ChineseChair的地方,都要进行修改,这不是一个小工程。
同样的,当前的ChineseChair类实例化的使用,采用的无参构造方法,如果是有参构造方法,那么就不可以避免地要传入参数。但作为用户来说,更希望用更加封装的方式获取某个实例对象,而不用考虑它的构造细节。
另外,在一个大型程序中,我们更希望对象的生命周期被集中统一的管理,当前情况下,每次要调用ChineseChair,都要实例化一次,因此效率不高。
针对这些问题,我们可以采用简单工厂模式。
考虑到如果有多个属于同一类别的产品,用户哪怕需要切换产品,但产品的使用方式总是不变的(比如对于椅子这一类产品,虽然可以有多种风格,但总归都是用来sit的),因此可以用同一的方式返回用户需要的产品。
简单工厂模式的核心包括以下:一个所有产品必须实现的接口(该接口规定了产品的通用对外功能),一个用于生产产品的工厂类。一般为了方便,工厂类中产生产品的方法,通常采用静态方法的方式,代码如下。
“Chair.java”——所有椅子类产品都要遵循的接口
public interface Chair {
public void sit();
}
“ChairType.java”——枚举类,标识椅子类型
public enum ChairType {
CHINESECHAIRTYPE,
USACHAIRTYPE
}
“ChineseChair.java”——中式风格椅子,实现Chair接口
public class ChineseChair implements Chair{
public void sit(){
System.out.println("这把中国椅子充满了文化气息");
}
}
“USAChair.java”——美式风格的椅子,实现Chair接口
public class USAChair implements Chair{
public void sit(){
System.out.println("这把美国椅子很具有后现代风格");
}
}
“ChairFactory.java”——简单工厂类,生产Chair产品
public class ChairFactory {
public static Chair createChair(ChairType type){
switch (type){
case CHINESECHAIRTYPE:
return new ChineseChair();
case USACHAIRTYPE:
return new USAChair();
default:
return null;
}
}
}
“Client.java”——用户
public class Client {
public static void main(String[] args) {
Chair chair = ChairFactory.createChair(ChairType.CHINESECHAIRTYPE);
chair.sit();
}
}
好处:可以看到使用简单工厂模式,用户可以根据需要通过工厂类的静态方法,传入椅子类型的参数,从而获得对应种类的椅子。用户可以使用统一的方式获取想要的产品,而不用理会产品的产生细节。
缺陷:简单工厂模式并不是完美无缺,假如我们要新增一个JapaneseChair产品,那么无可避免的,需要针对工厂类的静态方法中,增加switch选项,这样不符合开闭原则。
重点:简单工厂模式描述了一个类,它拥有一个包含大量条件语句的构建方法,可以根据方法的参数来选择对何种产品进行初始化并将其返回。在大多数情况下,如果产品类型比较少,且比较固定,使用简单工厂模式就可以应付。
工厂方法模式的核心在于,将实例化产品的操作,延迟到工厂子类中执行。因此可以通过抽象类或者接口的方式,定义一个抽象工厂。同样的,也需要一个抽象的产品。之后具体的工厂类可以继承该抽象工厂,并且生产具体类型的产品。
“Chair.java”——抽象产品接口
public interface Chair {
public void sit();
}
“ChineseChair.java”——具体产品1号
public class ChineseChair implements Chair{
@Override
public void sit() {
System.out.println("这把中式椅子很有古典文化气息");
}
}
“USAChair.java”——具体产品2号
public class USAChair implements Chair{
@Override
public void sit() {
System.out.println("这把美国椅子很有后现代主义风格");
}
}
“BaseFactory.java”——抽象工厂类
public abstract class BaseFactory {
public abstract Chair createChair();
//所有子类的共有方法
public void doSth(){
Chair chair = createChair();
chair.sit();
}
}
“ChineseFactory.java”——具体工厂类1
public class ChineseFactory extends BaseFactory{
@Override
public Chair createChair() {
return new ChineseChair();
}
}
“USAFactory.java”——具体工厂类2
public class USAFactory extends BaseFactory{
@Override
public Chair createChair() {
return new USAChair();
}
}
“Client.java”——用户
public class Client {
public static void main(String[] args) {
BaseFactory factory = new ChineseFactory();
factory.doSth();
}
}
好处:之后如果新增产品类型,只需要新增工厂并且继承抽象工厂,重写抽象工厂中生产产品的方法就可以了,而抽象工厂本身不需要变动。
缺点:目前采用工厂方法模式,只能生产某个类型的产品,如果想要生产一系列不同类型的产品,就无能为力,因此需要使用抽象工厂模式。
抽象工厂模式可以用于创建多种类型的产品,其主要原理是利用一个抽象的工厂接口,该接口中有多个抽象方法,每个方法可以生产某种类型的产品。因此如果要生产一系列产品,需要一个具体的工厂类,实现抽象类的所有抽象方法,每个方法返回一种类型的产品。
首先是产品:
“Table.java”——产品类型:桌子
public interface Table {
public void put();
}
“Chair.java”——产品类性:椅子
public interface Chair {
public void sit();
}
“ChineseChair.java”——具体椅子产品1
public class ChineseChair implements Chair{
@Override
public void sit() {
System.out.println("这把中式椅子很有古典气息");
}
}
“USAChair.java”——具体椅子产品2
public class USAChair implements Chair{
@Override
public void sit() {
System.out.println("这把美式椅子彰显了后现代主义风格");
}
}
“ChineseTable.java”——具体桌子产品1
public class ChineseTable implements Table{
@Override
public void put() {
System.out.println("这张中式桌子放了一碗常州鳝丝面");
}
}
“USATable.java”——具体桌子产品2
public class USATable implements Table{
@Override
public void put() {
System.out.println("这张美式桌子上放了一个汉堡");
}
}
“BaseFactory.java”——抽象工厂接口
public interface BaseFactory {
public Chair createChair();
public Table createTable();
}
接下来为工厂:
“BaseFactory.java”——抽象工厂接口
public interface BaseFactory {
public Chair createChair();
public Table createTable();
}
“ChineseFactory.java”——具体工厂1
public class ChineseFactory implements BaseFactory{
@Override
public Chair createChair() {
return new ChineseChair();
}
@Override
public Table createTable() {
return new ChineseTable();
}
}
“USAFactory.java”——具体工厂2
public class USAFactory implements BaseFactory{
@Override
public Chair createChair() {
return new USAChair();
}
@Override
public Table createTable() {
return new USATable();
}
}
最后是用户:
“Client.java”
public class Client {
public static void main(String[] args) {
BaseFactory factory = new ChineseFactory();
Chair chair = factory.createChair();
Table table = factory.createTable();
chair.sit();
table.put();
}
}
(1)工厂方法模式的核心是创建产品的方法(即工厂方法)。如上面有关工厂方法模式的例子可以看出,工厂类中除了有抽象的,用于产品创建的工厂方法,还有其他的非抽象方法。这些非抽象的方法,往往是其他继承抽象工厂类的子类的通用方法。而抽象的工厂方法,则作为抽象方法延后到子类实现。因此工厂方法模式下,工厂类的角色并不单纯是生产产品,也包括了一些其他操作。
(2)工厂方法模式与抽象工厂模式都可以用于产品的创建,且符合开闭原则,不用每次新增新类型产品都修改工厂类。但工厂方法模式只能专注于某种产品的创建,而抽象工厂可以创建一系列的产品。
(3)工厂方法模式通过继承的方式,使子类继承基类并重写工厂方法;而抽象工厂模式,通过在抽象工厂接口中对工厂方法进行组合,从而实现创建多个类型产品的功能,更加灵活。
(1)简单工厂模式:工厂类负责创建的对象不多,且很少发生改变。
(2)工厂方法模式:需要考虑日后新增类型的产品,符合开闭原则。
(3)抽象工厂模式:需要考虑生产一系列类型的产品的创建。
之前的例子中,对于简单工厂模式,如果有新增的产品,就要在工厂类的创建方法中修改switch逻辑,自然不符合“对扩展开放,对修改关闭”这一原则。但有的同学可能会对工厂方法模式和抽象工厂模式有疑问,因为在Client用户端调用工厂类的时候,肯定要指定某个具体的工厂子类来实例化,如果需要修改被实例化的具体工厂类,那么Client客户端的调用代码也需要改变,不符合开闭原则。
因此可行的方案是,工厂类的实例化,可以根据读取配置文件来完成。
比如我们可以在resources文件夹下新增配置文件如下:
“config.properties”
factory=factory.USAFactory
配置文件中通过自定义的"key=value"设置Client想要使用的具体工厂类型。
之后增加配置文件读取类以及对应的通过反射实例化具体工厂类型的方法,具体如下:
“GetInstance.java”
public class GetInstance {
public static String getFactoryName(String fileName){
String res = null;
try{
InputStream inputStream = GetInstance.class.getResourceAsStream(fileName);
Properties properties = new Properties();
properties.load(inputStream);
res = properties.getProperty("factory");
}catch (IOException e){
e.printStackTrace();
}
return res;
}
public static BaseFactory getFactory(String className){
try{
return (BaseFactory) Class.forName(className).newInstance();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
最后,我们对Client的代码做一些修改,如下:
“Client.java”
public class Client {
public static void main(String[] args) {
BaseFactory factory = GetInstance.getFactory(GetInstance.getFactoryName("/config.properties"));
Chair chair = factory.createChair();
Table table = factory.createTable();
chair.sit();
table.put();
}
}
通过以上的方法,我们就可以通过修改配置文件中的内容选择实例化哪种具体工厂,防止对客户端代码进行修改了。
文章可能有不是很恰当的地方,感谢大家提出宝贵意见。
工厂方法和抽象工厂的区别
工厂方法+配置文件
简单工厂模式