Java中几种常见的设计模式--工厂设计模式

一、什么是工厂设计模式

当需要获取对象时,并不是直接去实例化,而是通过一个工厂类间接获取,以实现创建者和调用者的分离,实现更好的解耦。直白一点说,就是调用者不用也根本不会知道对象时如何创建的,它只需要工厂提出条件即可,由工厂来根据提交创建具体的实例。

二、使用场景

当框架需要深度解耦,高度可扩展时就需要用到工厂设计模式,这种设计模式在SpringBoot、Mybatis框架中很常见。

三、几种类型

如果详细划分,工厂设计模式可细分为:

  • 简单工厂模式(静态工厂模式)
  • 工厂方法模式
  • 抽象工厂模式

接下来,我们将一个一个详细讲解。再此之前,有几个面向对象设计的基本原则,理解这些原则可以帮助我们更好地理解工厂设计模式。

  1. OCP(开闭原则,Open-Close-Principle):一个软件实体应当对扩展开放,对修改关闭。
  2. DIP(依赖倒置原则,Dependence Invision Principle):要针对接口编程,不要针对实现编程。
  3. LoD(迪米特法则,Law of Demeter):只与你的朋友通信,而避免与陌生人通信。

四、简单工厂模式

 举个例子,假如我们在做一个用户注册功能,再Dao中提供注册服务,用户信息的实体类如下:

public class User {
    public String name;
    public String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

我们打算把新注册的用户信息插入到数据库,假设我们目前使用的数据库是Mysql,我们的Dao接口和实现类分别如下:

public interface DaoRegisterService {
    void insert(User user);
}
public class DaoMysqlRegisterServiceImpl implements DaoRegisterService {
    @Override
    public void insert(User user) {
        System.out.print("注册用户:"+user.name+"到Mysql数据库成功");
    }
}

在客户端调用结果如下:

public class Client {
    public static void main(String[] args){
        User user=new User();
        user.name="张三";
        user.password="123456";

        DaoRegisterService registerService=new DaoMysqlRegisterServiceImpl();
        registerService.insert(user);
    }
}

结果:

注册用户:张三到Mysql数据库成功


现在这种做法完全可以满足我们的使用要求,但随着业务量的增加,Mysql数据库越发不能满足我们的业务需要,我们需要使用性能更好的Oracle数据库,于是我们需要再写一个Oracle的Dao服务实现类。

public class DaoOracleRegisterServiceImpl implements DaoRegisterService {
    @Override
    public void insert(User user) {
        System.out.print("注册用户:"+user.name+"到Oracle数据库成功");
    }
}

我们的客户端也需要进行对应的更改。

public class Client {
    public static void main(String[] args){
        User user=new User();
        user.name="张三";
        user.password="123456";

        DaoRegisterService registerService=new DaoOracleRegisterServiceImpl();
        registerService.insert(user);
    }
}

结果:

注册用户:张三到Oracle数据库成功

 

项目里如果只有一处需要将DaoMysqlRegisterServiceImpl类改为DaoOracleRegisterServiceImpl的地方还好,如果有十几处、几十处,那么这个修改工作显然出力不讨好。

那如何解决这个问题呢,让程序框架更容易实现扩展功能,这个时候我们就需要用到工厂设计模式。首先我们创建一个工厂类,里面有个静态方法,通过数据名称可为我们提供DaoRegisterService实例。

public class DaoFactory {
public static DaoRegisterService createDaoRegisterService(String databaseName){ switch (databaseName){ case "mysql": return new DaoMysqlRegisterServicelImpl(); case "oracle": return new DaoOracleRegisterServiceImpl(); default: return null; } } }

在客户端,我们只需要向工厂输入条件,就可以得到我们想要的对象实例,而不需要自己去创建。

public class Client {
    public static void main(String[] args){
        User user=new User();
        user.name="张三";
        user.password="123456";
        //项目初期
        //DaoService Service= DaoFactory.createDaoRegisterService("mysql");
        //Service.insert(user);

        //项目拓展期
        DaoService Service= DaoFactory.createDaoRegisterService("oracle");
        Service.insert(user);
    }
}

现在我们来看看这种简单工厂模式的优缺点,优点是它很简单,也确实能实现工厂模式的功能,即调用者和创建者实现解耦。当同时这种简单工厂模式也存在却缺点,现在我们只有两个目标(DaoMysqlRegisterServiceImpl和DaoOracleRegisterServiceImpl服务),当我们目标增多时(比如增加一个DaoDB2RegisterServiceImpl),我们就需要更改工厂类的内部代码,使其兼容新的内容,这明显违背了我们上面讲的开闭原则,即宁愿增加新的类来扩展,也不要对原来的类做修改,所以出现了工厂方法模式。

五、工厂方法模式

工厂方法模式是对简单工厂模式的抽象升级,将工厂这个概念抽象出来成为接口,然后针对每种目标实现类创建一个工厂实现,一对一来实现,当新增了目标实现,只要同时新增一个工厂实现即可。

同样还是上面数据库的例子,我们对工厂类升级未接口,然后对每一个目标创建一个工厂接口的实现,当有新的目标出现时,只需要再增加对应的工厂接口实现,而不需要对原来的代码做任何的修改。

工厂接口:

public interface DaoFactory {
    DaoRegisterService createDaoRegisterService();
}

mysql的工厂实现类:

public class DaoMysqlFactory implements DaoFactory {
    @Override
    public DaoRegisterService createDaoRegisterService() {
        return new DaoMysqlRegisterServiceImpl();
    }
}

oracle的工厂实现类:

public class DaoOracleFactory implements DaoFactory {
    @Override
    public DaoRegisterService createDaoRegisterService() {
        return new DaoOracleRegisterServiceImpl();
    }
}

客户端:

public class Client {
    public static void main(String[] args){
        User user=new User();
        user.name="张三";
        user.password="123456";

        //项目初期
        //DaoFactory daoFactory=new DaoMysqlFactory();
        //DaoRegisterService registerService= daoFactory.createDaoRegisterService();
        //registerService.insert(user);

        DaoFactory daoFactory=new DaoOracleFactory();
        DaoRegisterService registerService=daoFactory.createDaoRegisterService();
        registerService.insert(user);
    }
}

当项目需要再拓展一个DB2的服务时,只需要再增加一个DB2的Dao实现类和一个工厂实现类即可。

再说说工厂方法模式的优缺点以及使用场景

优点:

用户只需关心所需产品对应的工厂,无须关心创建细节(屏蔽产品类),甚至不需要知道具体产品的类名;

典型的解耦框架,高层模块需要知道产品的抽象类,其他的实现类都不用关心;

良好的封装性,代码结构清晰,优秀的扩展性,同时符合开闭原则。

缺点:

在添加新产品时成对增加了类的个数,增加了系统的复杂度,编译和运行更多的类也会增加系统的开销;

考虑到可扩展性引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度。

使用场景:

需要灵活、可扩展的框架时;

当一个类(比如客户端类)不知道所需要的对象的类时(需要知道其对应的工厂);

个类通过其子类来确定创建那个对象。

六、抽象工厂模式

抽象工厂模式的定义是:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

抽象工厂模式是工厂方法模式的泛化版,即工厂方法模式只是抽象工厂模式的一种特殊情况。

我们还是以Dao提供的数据库服务为例,上面我们提供了一个用户注册的服务,下面我们再提供一个用户登录的服务。

接口:

public interface DaoLoginService {
    void checkValid(User user);
}

Mysql实现类:

public class DaoMysqLoginServicelImpl implements DaoLoginService {
    @Override
    public void checkValid(User user){
        if (user.name=="张三"&&user.password=="123456"){
            System.out.println("使用Mysql验证用户:"+user.name+"通过");
        }else{
            System.out.println("使用Mysql验证用户:"+user.name+"失败");
        }
    }
}

Oracle实现类

public class DaoOracleLoginServiceImpl implements DaoLoginService {
    @Override
    public void checkValid(User user){
        if (user.name=="张三"&&user.password=="123456"){
            System.out.println("使用Oracle验证用户:"+user.name+"通过");
        }else{
            System.out.println("使用Oracle验证用户:"+user.name+"失败");
        }
    }
}

我们让工厂接口可以提供所有的服务

public interface DaoFactory {
    DaoRegisterService createDaoRegisterService();
    
    DaoLoginService createDaoLoginService();
}
public class DaoMysqlFactory implements DaoFactory {
    @Override
    public DaoRegisterService createDaoRegisterService() {
        return new DaoMysqlRegisterServiceImpl();
    }

    @Override
    public DaoLoginService createDaoLoginService() {
        return new DaoMysqLoginServicelImpl();
    }
}
public class DaoOracleFactory implements DaoFactory {
    @Override
    public DaoRegisterService createDaoRegisterService() {
        return new DaoOracleRegisterServiceImpl();
    }

    @Override
    public DaoLoginService createDaoLoginService(){
        return new DaoOracleLoginServiceImpl();
    }
}

使用客户端验证如下:

public class Client {
    public static void main(String[] args){
        User user=new User();
        user.name="张三";
        user.password="123456";

        //项目初期
        //DaoFactory daoFactory=new DaoMysqlFactory();
        //DaoRegisterService registerService= daoFactory.createDaoRegisterService();
        //registerService.insert(user);

        DaoFactory daoFactory=new DaoOracleFactory();
        DaoRegisterService registerService=daoFactory.createDaoRegisterService();
        DaoLoginService loginService=daoFactory.createDaoLoginService();
        registerService.insert(user);
        loginService.checkValid(user);
    }
}

结果:

注册用户:张三到Oracle数据库成功
使用Oracle验证用户:张三通过

抽象工厂是所有形式的工厂模式中最为抽象和最具一般性的一种形态,其优缺点大致如下:

  1. 隔离了具体类的生成,使得客户并不需要知道什么被创建,具有良好的封装性。
  2. 横向扩展容易。同个产品族如果需要增加多个 产品,只需要增加新的工厂类和产品类即可。
  3. 纵向扩展困难。如果增加新的产品组,抽象工厂类也要添加创建该产品组的对应方法,这样一来所有的具体工厂类都要做修改了,严重违背了开闭原则。

在实际的应用开发中,一般将具体类的类名写入配置文件中,再通过Java的反射机制读取XML格式的配置文件,根据存储在XML文件中的类名字符串生成对象。

新建XML文件config.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <FactoryName>DaoMysqlFactory</FactoryName>
</config>

新建工具类XmlUtil,通过反射机制获取工厂类名称。

public class XmlUtil {
    public static Object getDaoFactroyBean(){
        try{
            //创建DOM对象
            DocumentBuilderFactory builderFactory=DocumentBuilderFactory.newInstance();
            DocumentBuilder builder=builderFactory.newDocumentBuilder();
            Document doc=builder.parse(new File("./src/config.xml"));

            //获取包含类名的文本点
            NodeList list=doc.getElementsByTagName("FactoryName");
            Node node=list.item(0).getFirstChild();
            String factoryName=node.getNodeValue();

            //利用反射机制,通过类名创建实例
            Class c=Class.forName("Facotry."+factoryName);
            Object obj=c.newInstance();
            return obj;

        }catch (Exception ex){
            ex.printStackTrace();
        }
        return null;
    }
}

在客户端验证

public class Client {
    public static void main(String[] args){
        User user=new User();
        user.name="张三";
        user.password="123456";

        //项目初期
        //DaoFactory daoFactory=new DaoMysqlFactory();
        //DaoRegisterService registerService= daoFactory.createDaoRegisterService();
        //registerService.insert(user);

        DaoFactory daoFactory=(DaoFactory) XmlUtil.getDaoFactroyBean();
        DaoRegisterService registerService=daoFactory.createDaoRegisterService();
        DaoLoginService loginService=daoFactory.createDaoLoginService();
        registerService.insert(user);
        loginService.checkValid(user);
    }
}

结果:

注册用户:张三到Mysql数据库成功
使用Mysql验证用户:张三通过

同样,如果需要使用Oracle数据,只需要将config.xml中的FactoryName的值修改为DaoOracleFactory即可。

-------------------------------------------------------------------------------------------------------------------------------------------------------------

参考博文:https://www.cnblogs.com/LangZXG/p/6249425.html

参考博文:https://blog.csdn.net/yeyazhishang/article/details/95173103

参考博文:https://www.cnblogs.com/V1haoge/p/10491982.html

 

 

 

 

 

    

 

posted @ 2020-07-15 22:50  Jerryoned  阅读(369)  评论(0编辑  收藏  举报