设计模式之抽象工厂模式

本文介绍了如何从SQLServer数据库查询代码重构开始,逐步引入面向接口编程和工厂方法,最终实现抽象工厂模式,以应对不同数据库的适应性和代码的解耦。
摘要由CSDN通过智能技术生成

抽象工厂(模式)

抽象工厂是一种创建型设计模式,它能创建一系列相关的对象,而无需指定具体的类。

引题

从sqlserver数据库中查询用户列表数据
示意代码如下:

/***
     * 从sqlserver数据库中查询用户列表数据
     * @return
     */
    List<User> getUserList() {
        List<User> rst = new ArrayList<>();
        // 连接sqlserver数据库
        SqlConection conection = new SqlConection();
        conection.url = "";
        conection.pass = "";
        conection.user="";
        // sqlserver命令
        SqlCommand command = new SqlCommand();
        command.text = "";
        
        SqlDataReader reader = command.executeReader();
        return reader.read();

    }

上述代码很好的完成了在sqlserver上查询用户列表功能,但是这个代码不好,因为它与sqlserver数据紧耦合,
假如,我们需求有变,如果查询用户列表要适配mysql数据库和oracle数据库。那么我们如何修改我们的设计。

重构v2

从代码上看,我们可以发现SqlConection conection = new SqlConection(); SqlCommand command = new SqlCommand();
这些类,都是面向具体类编程,没有实现面向接口编程,于是我们首先要定义接口。

接口类

/**
 * 类描述: 数据库连接接口
 */
public interface IDBConnection {
}

public interface IDBCommand {
}

public interface IDataReader {
}

sqlserver实现类:

public class SqlConection implements IDBConnection {
}

public class SqlCommand implements IDBCommand {
}
public class SqlDataReader implements IDataReader {
}

mysql实现类

public class MysqlConnection implements IDBConnection {
}
public class MysqlCommand implements IDBCommand {
}
public class MysqlDataReader implements IDataReader {
}

oracle实现类

public class OracleConnection implements IDBConnection {
}
public class OracleCommand implements IDBCommand {
}
public class OracleDataReader implements IDataReader {
}

我们再来看UserDao中的方法,等号左侧换成我们定义的接口,等号右侧还是具体的实现类,示意代码如下:


public class UserDao {

    /***
     * 从sqlserver数据库中查询用户列表数据
     * @return
     */
    List<User> getUserList() {
        List<User> rst = new ArrayList<>();
        // 等号左侧写成接口
        IDBConection conection = new SqlConection();
        conection.url = "";
        conection.pass = "";
        conection.user="";
        // sqlserver命令
        IDBCommand command = new SqlCommand();
        command.text = "";

        IDataReader reader = command.executeReader();
        return reader.read();

    }

}

这种方式还是没有完全解决
UserDao依赖具体的实现类,因此我们想到我们在工厂方法中的技能,我们可以先使用工厂方法解决,看看带来什么新的问题。

工厂方法解决方案

定义工厂接口

public interface IDBConnectionFactory {
    
    public IDBConnection createConnection();
}

public interface IDBCommandFactory {
    
    public IDBCommand createCommand();
    
}

public interface IDataReaderFactory {

    public IDataReader createDataReader();

}

工厂实现类:
sqlserver实现类

public class SqlConnectionFactory implements IDBConnectionFactory {

    @Override
    public IDBConnection createConnection() {
        return new SqlConection();
    }
}

public class SqlCommandFactory implements IDBCommandFactory {

    @Override
    public IDBCommand createCommand() {
        return new SqlCommand();
    }
}

public class SqlDataReaderFactory implements IDataReaderFactory{

    @Override
    public IDataReader createDataReader() {
        return new SqlDataReader();
    }
}

mysql实现类

public class MysqlConnectionFactory implements IDBConnectionFactory {

    @Override
    public IDBConnection createConnection() {
        return new MysqlConnection();
    }
}

public class MysqlCommandFactory implements IDBCommandFactory {

    @Override
    public IDBCommand createCommand() {
        return new MysqlCommand();
    }
}

public class MysqlDataReaderFactory implements IDataReaderFactory {

    @Override
    public IDataReader createDataReader() {
        return new MysqlDataReader();
    }
}

oracle工厂实现类

public class OracleConnectionFactory implements IDBConnectionFactory {

    @Override
    public IDBConnection createConnection() {
        return new OracleConnection();
    }
}

public class OracleCommandFactory implements IDBCommandFactory {

    @Override
    public IDBCommand createCommand() {
        return new OracleCommand();
    }
}

public class OracleDataReaderFactory implements IDataReaderFactory{

    @Override
    public IDataReader createDataReader() {
        return new OracleDataReader();
    }
}

UserDao中的代码如下:

public class UserDao {

    private IDBConnectionFactory connectionFactory;
    
    private IDBCommandFactory commandFactory;
    
    private IDataReaderFactory dataReaderFactory;
    
    public UserDao(IDBConnectionFactory connectionFactory,
                  IDBCommandFactory commandFactory,
                  IDataReaderFactory dataReaderFactory) {
        this.commandFactory = commandFactory;
        this.connectionFactory = connectionFactory;
        this.dataReaderFactory = dataReaderFactory;
    }
    
    /***
     * 从sqlserver数据库中查询用户列表数据
     * @return
     */
    List<User> getUserList() {
        List<User> rst = new ArrayList<>();
        // 等号右侧写成接口
        IDBConnection conection = connectionFactory.createConnection();
        IDBCommand command = commandFactory.createCommand();
        IDataReader reader = dataReaderFactory.createDataReader();
        return rst;

    }

}

通过编写上述代码,相信你也发现问题了,尝试工厂方法解决,发现类爆炸了;还有一个很重要的问题就是
IDBConnection conection = connectionFactory.createConnection();
IDBCommand command = commandFactory.createCommand();
IDataReader reader = dataReaderFactory.createDataReader();
这三个对象之间没有关系,假如客户端使用过程中传入的是sql的连接,mysql的command,oracle的datareader;
程序是不是就有问题了,因为,我们获取用户列表这个接口要求conection、command、reader是一系列相关的一组接口。
那么,我们如何解决,就是今天的主题,抽象模式

抽象模式解决问题

把三个工厂接口合并为一个工厂接口,IDBFactory

public interface IDBFactory {

    public IDBConnection createConnection();
    public IDBCommand createCommand();
    public IDataReader createDataReader();
    
}


sqlserver 工厂实现类:

public class SqlDBFactory implements IDBFactory {

    @Override
    public IDBConnection createConnection() {
        return new SqlConection();
    }

    @Override
    public IDBCommand createCommand() {
        return new SqlCommand();
    }

    @Override
    public IDataReader createDataReader() {
        return new SqlDataReader();
    }
}

mysql工厂实现类

public class MysqlDBFactory implements IDBFactory {

    @Override
    public IDBConnection createConnection() {
        return new MysqlConnection();
    }

    @Override
    public IDBCommand createCommand() {
        return new MysqlCommand();
    }

    @Override
    public IDataReader createDataReader() {
        return new MysqlDataReader();
    }
}

oracle工厂实现类

public class OracleDBFactory implements IDBFactory {

    @Override
    public IDBConnection createConnection() {
        return new OracleConnection();
    }

    @Override
    public IDBCommand createCommand() {
        return new OracleCommand();
    }

    @Override
    public IDataReader createDataReader() {
        return new OracleDataReader();
    }
}

UserDao类的代码调整

public class UserDao {

    private IDBFactory dbFactory;

    public UserDao(IDBFactory dbFactory) {
        this.dbFactory = dbFactory;
    }

    /***
     * 从sqlserver数据库中查询用户列表数据
     * @return
     */
    List<User> getUserList() {
        List<User> rst = new ArrayList<>();
        // 等号左侧写成接口
        IDBConnection conection = dbFactory.createConnection();
        // sqlserver命令
        IDBCommand command = dbFactory.createCommand();
        IDataReader reader = dbFactory.createDataReader();
        return rst;

    }

}

定义

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

通俗说

工厂的工厂; 一个将单个但相关/从属的工厂分组在一起而没有指定其具体类别的工厂。

维基百科

抽象工厂模式提供了一种封装一组具有共同主题的单个工厂而无需指定其具体类的方法

动机

在软件系统中,经常面临着"一系列相互依赖的对象"的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
如何应对这种变化?如何绕开常规的对象使用构造器创建对象,提供一种"封装机制"来避免客户程序和这种"多系列具体对象创建工作"的紧耦合

本质

抽象工厂的本质是:选择产品族的实现

JAVA案例

javax.xml.parsers.DocumentBuilderFactory
javax.xml.transform.TransformerFactory
javax.xml.xpath.XPathFactory

java8 实现抽象工厂小案例

在Java 8中,我们可以使用接口默认方法、静态方法、Lambda表达式等语法糖来更简洁地实现抽象工厂模式。
下面是一个使用Java 8特性实现的抽象工厂模式的例子:
步骤1.定义产品Car接口和Engine接口

public interface Car {
    void drive();
}

public interface Engine {
    void start();
}

步骤2.我们创建一个表示工厂的接口,并使用默认方法来提供创建产品的默认实现

public interface CarFactory {
    Car createCar();

    default Engine createEngine() {
        return new DefaultEngine();
    }

    static class DefaultEngine implements Engine {
        @Override
        public void start() {
            System.out.println("DefaultEngine started!");
        }
    }
}

步骤3.创建一个具体的工厂类,它实现了CarFactory接口,并提供了创建特定类型汽车的实现

public class SportsCarFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new SportsCar(this::createEngine);
    }

    @Override
    public Engine createEngine() {
        return new HighPerformanceEngine();
    }

    private static class HighPerformanceEngine implements Engine {
        @Override
        public void start() {
            System.out.println("High-performance engine started!");
        }
    }

    private static class SportsCar implements Car {
        private final Engine engine;

        public SportsCar(Engine engine) {
            this.engine = engine;
        }

        public SportsCar(Supplier<Engine> engineSupplier) {
            this.engine = engineSupplier.get();
        }

        @Override
        public void drive() {
            System.out.println("Driving the sports car...");
            engine.start();
        }
    }
}

SportsCarFactory类重写了createCar方法来返回一个SportsCar对象,该对象在构造时接收了一个Supplier类型的参数。
这个Supplier是一个函数式接口,它允许我们延迟创建Engine对象,直到真的需要它时。

步骤4.客户端代码可以这样使用抽象工厂

public class Client {
    public static void main(String[] args) {
        CarFactory factory = new SportsCarFactory();
        
        Car car = factory.createCar();
        car.drive();
    }
}

Client类创建了一个SportsCarFactory对象,并通过调用createCar方法来获取一个Car对象。
然后,它调用drive方法来模拟驾驶汽车,这会导致Engine的start方法被调用。
通过使用Java 8的语法糖,我们可以更加简洁地实现抽象工厂模式,并且利用函数式接口和Lambda表达式来增加代码的灵活性和可读性。

在这个例子中,客户端代码并不直接依赖于具体的 SportsCar 或 HighPerformanceEngine 类,而是依赖于抽象的 CarFactory 接口。
这意味着我们可以轻松地更改工厂的实现,以返回不同类型的汽车和引擎,而不需要修改客户端代码。这种解耦使得系统更加灵活和可扩展。

抽象工厂模式的关键在于它将产品族的创建集中在一个工厂中,而不是为每个产品分别提供工厂。这样,客户端只需要与一个工厂交互,就可以获取到一整套相关的产品对象。

真谛(找到变化,封装变化)

在上面的代码中,我们定义了抽象工厂模式的一个简单示例,包括一个产品接口Car,一个引擎接口Engine,一个抽象工厂接口CarFactory,
以及一个具体的工厂实现SportsCarFactory和两个产品实现SportsCar和HighPerformanceEngine。

稳定部分

  1. 接口定义:Car和Engine接口是稳定的,因为它们定义了产品应有的行为,而不涉及具体的实现细节。这些接口定义了产品的稳定特性,即产品的“骨架”。
  2. 抽象工厂接口:CarFactory接口也是稳定的。它定义了创建产品对象的方法,这些方法不依赖于具体的实现类。这使得客户端代码可以保持与具体实现类解耦,从而保持稳定性。

变化部分

  1. 具体实现类:SportsCar和HighPerformanceEngine是具体的产品实现类,它们可能会随着需求的变化而变化。例如,如果需要添加一个新的汽车类型,那么就需要创建新的具体产品类来实现Car接口。同样,如果引擎的实现需要更改,那么就需要更新HighPerformanceEngine类。
  2. 具体工厂类:SportsCarFactory是具体的工厂实现类,它负责创建具体的产品对象。这个类也是变化的部分,因为它需要根据具体产品的变化来更新创建产品的逻辑。

通过将稳定的接口和抽象类与变化的具体实现类分离,抽象工厂模式允许我们在保持客户端代码稳定的同时,灵活地添加或修改具体的产品和工厂实现。这种分离使得代码更加模块化,提高了系统的可扩展性和可维护性。

需要注意的是,虽然具体实现类是变化的部分,但是它们通常不会频繁地更改。只有在需要添加新功能或修改现有功能时,才会涉及到这些类的变化。因此,在设计和实现抽象工厂模式时,应该尽可能地保持接口和抽象类的稳定性,而将变化的部分限制在最小范围内。

总结

  1. 如果没有应对"多系列对象构建"的需求的变化,则没有必要使用抽象工厂模式,这时候使用简单工厂完全可以
  2. "系列对象"指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖
  3. 抽象工厂模式主要在于应对"新系列"的需求变动。其缺点在于难以应对"新对象"的需求变动
  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值