DAO设计模式和工厂模式

  最近开始学习一些常用的Java设计模式,工厂模式是其中一种。与此同时 DAO 作为数据库操作对象是 Web 中开发中必不可少的一环。

  因为 DAO 的设计往往基于工厂模式,所以文章将先后介绍工厂模式和 DAO 的设计。



工厂设计模式

当我们明确地计划不同条件下创建不同实例时,可使用工厂设计模式,由工厂来负责创建对象

简单工厂

描述工厂对象通过具体消息来实例化相应的产品对象
使用场景工厂负责对象较少
特点写法简单,但耦合度高

  假如现在市面上有两种鼠标:戴尔和惠普都满足鼠标的设计标准,鼠标可以实现点击操作,通过一个工厂按照对应厂商的标准可以生产出相应的鼠标。

interface Mouse {    // 鼠标统一接口
    public void click();
}

class DellMouse implements Mouse 
{    
    public void click() {
        System.out.println("戴尔鼠标");
    }
}

class HpMouse implements Mouse 
{
    public void click() {
        System.out.println("惠普鼠标");
    }
}


class MouseFactory     // 鼠标生产工厂
{
    Mouse createMouse(String name) 
    {
        if(name.equals("Dell"))
            return new DellMouse();
        if(name.equals("Hp"))
            return new HpMouse();
        return null;
    }
}


class MainClass 
{
    public static void main(String[] args)
    {
        MouseFactory factory = new MouseFactory();

        Mouse dellmouse = factory.createMouse("Dell");
        Mouse hpmouse = factory.createMouse("Hp");    // 工厂根据参数生产对应鼠标

        dellmouse.click();
        hpmouse.click();
    }
}

/*输出:
戴尔鼠标
惠普鼠标
*/


工厂方法模式

描述设计工厂接口,对于不同的产品对象设计对应的子类工厂来生产
使用场景明确地计划不同条件下创建不同实例时
特点产品可扩展性好

用于消除简单工厂对参数传递的依赖的问题。当我们需要新增一个产品时使用简单工厂需要修改工厂源码,不符合开闭原则。

  比如对于上面的例子,惠普和戴尔鼠标工厂分家了,变成两家工厂生产对应的鼠标

interface Mouse {    // 鼠标统一接口
    public void click();
}

class DellMouse implements Mouse 
{    
    public void click() {
        System.out.println("戴尔鼠标");
    }
}

class HpMouse implements Mouse 
{
    public void click() {
        System.out.println("惠普鼠标");
    }
}



interface MouseFactory {
    public Mouse createMouse();
}

class DellMouseFactory implements MouseFactory
{
    public Mouse createMouse() {
        return new DellMouse();
    }
}

class HpMouseFactory implements MouseFactory
{
    public Mouse createMouse() {
        return new HpMouse();
    }
}


class MainClass 
{
    public static void main(String[] args)
    {
        MouseFactory dellfactory = new DellMouseFactory();
        MouseFactory hpfactory = new HpMouseFactory();

        Mouse dellmouse = dellfactory.createMouse();
        Mouse hpmouse = hpfactory.createMouse();

        dellmouse.click();
        hpmouse.click();
    }
}

/*输出:
戴尔鼠标
惠普鼠标
*/


抽象工厂模式

描述提供一个创建一系列相关或相互依赖对象的接口
使用场景用于多产品族
特点产品族扩展麻烦

区别于工厂方法,抽象工厂用于多产品族对象的生产。就本质而言,抽象工厂是工厂方法的一个多产品族的泛化,即工厂方法是产品族数量为一个的时候的抽象工厂。

  同样是上面的例子,除了鼠标各个厂商还可以生产键盘

interface Mouse {    // 鼠标统一接口
    public void click();
}

class DellMouse implements Mouse 
{    
    public void click() {
        System.out.println("戴尔鼠标");
    }
}

class HpMouse implements Mouse 
{
    public void click() {
        System.out.println("惠普鼠标");
    }
}


interface Keyboard {    // 键盘统一接口
    public void tap();
}

class DellKeyboard implements Keyboard
{
    public void tap() {
        System.out.println("戴尔键盘");
    }
}
class HpKeyboard implements Keyboard
{
    public void tap() {
        System.out.println("惠普键盘");
    }
}

interface PCFactory {    // 超级工厂
    public Mouse createMouse();
    public Keyboard createKeyboard();
}

class DellPCFactory implements PCFactory
{
    public Mouse createMouse() {
        return new DellMouse();
    }
    public Keyboard createKeyboard() {
        return new DellKeyboard();
    }
}

class HpPCFactory implements PCFactory
{
    public Mouse createMouse() {
        return new HpMouse();
    }
    public Keyboard createKeyboard() {
        return new HpKeyboard();
    }
}


class MainClass 
{
    public static void main(String[] args)
    {
        PCFactory dellfactory = new DellPCFactory();
        PCFactory hpfactory = new HpPCFactory();

        Mouse dellmouse = dellfactory.createMouse();
        Mouse hpmouse = hpfactory.createMouse();

        dellmouse.click();
        hpmouse.click();


        Keyboard dellkeyboard = dellfactory.createKeyboard();
        Keyboard hpkeyboard = hpfactory.createKeyboard();

        dellkeyboard.tap();
        hpkeyboard.tap();
    }
}

/*输出:
戴尔鼠标
惠普鼠标
戴尔键盘
惠普键盘
*/



DAO 设计模式

DAO (Data Access Object) 数据库访问对象,用于进行数据操作。属于JavaEE 三层架构中的数据访问层。

本质上是使用工厂模式来操作管理数据库。

组成

DBConnection负责数据库的连接和关闭
PO由 setter, getter 属性设置器组成每个属性都对应着表的一个字段
DAO定义操作的接口定义一系列原子操作
Impl接口的实现类实现接口的操作但不负责数据库打开与关闭
Proxy代理类代理模式
Factory工厂类工厂模式


示例:学生管理系统

建立数据库

# StudentManager 数据库

create table student (
    sid char(3) primary key,
    name varchar(11) not null,
    age int default 20
);


insert into student values("001", "AA", 20);
insert into student values("002", "BB", 21);
insert into student values("003", "CC", 22);


DBConnection

禁止构造函数并使用静态块来预注册,从而使 DBConnection 成为一个“静态类”

import java.sql.*;

public class DBConnection
{
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String HOST = "jdbc:mysql://localhost/StudentManager?useSSL=false";
    private static final String USR = "root";
    private static final String PASSWD = "123456";

    private DBConnection() {}  // 禁止构造函数

    static {    // 静态块预注册
        try {Class.forName(DRIVER);}
        catch(Exception e) {e.printStackTrace();}
    }

    public static Connection getConnection() {
        Connection conn = null;
        try {conn = DriverManager.getConnection(HOST, USR, PASSWD);}
        catch(SQLException e) {e.printStackTrace();}
        return conn;
    }

    public static void close(Connection conn) {
        try {if( conn != null) conn.close();}
        catch( SQLException e) {e.printStackTrace();}
    }
}


StudentPO

将 PO 对象的每一项属性和数据库表中的字段一一对应

public class StudentPO
{
    private String sid, name;
    private Integer age;

    public String getSid() {return sid;}
    public void setSid(String sid) {this.sid = sid;}

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

    public Integer getAge() {return age;}
    public void setAge(Integer age) {this.age = age;}
}


UserDAO

定义增删改查原子操作

public interface UserDAO {
    public boolean insert(Object o) throws Exception;
    public boolean delete(Object key) throws Exception;
    public boolean update(Object key, Object o) throws Exception;
    public Object find(Object key) throws Exception;
}


StudentDAO

Student的PO类对于DAO接口原子操作的实现,不关心连接的开启与关闭

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class StudentDAO implements UserDAO
{
    private Connection conn = null;
    public StudentDAO(Connection conn) {
        this.conn = conn;
    }

    public boolean insert(Object o) throws Exception
    {
        if( o == null) return false;

        try {
            StudentPO stu = (StudentPO) o;

            Statement sql = conn.createStatement();
            String cmd = String.format(
                "insert into student values(%s, '%s', %d)",
                stu.getSid(), stu.getName(), stu.getAge()
            );
            sql.executeUpdate(cmd);
        }
        catch(Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean delete(Object key) throws Exception
    {
        if( key == null) return false;

        try {
            Statement sql = conn.createStatement();
            String cmd = "delete from student where sid=" + (String) key;
            sql.executeUpdate(cmd);
        }
        catch(Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean update(Object key, Object o) throws Exception
    {
        if( key == null || o == null) return false;

        try {
            StudentPO stu = (StudentPO) o;

            Statement sql = conn.createStatement();
            String cmd = String.format(
                "update student set name='%s', age=%d where sid=%s",
                stu.getName(), stu.getAge(), stu.getSid()
            );
            sql.executeUpdate(cmd);
        }
        catch(Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public Object find(Object key) throws Exception
    {
        if( key == null) return null;
        StudentPO stu = null;

        try {
            stu = new StudentPO();

            Statement sql = conn.createStatement();
            String cmd = "select * from student where sid=" + (String) key;
            ResultSet rs = sql.executeQuery(cmd);

            if( rs.next()) {
                stu.setSid(rs.getString("sid"));
                stu.setName(rs.getString("name"));
                stu.setAge(Integer.parseInt(rs.getString("age")));
            }
            else stu = null;
        }
        catch(Exception e) {
            e.printStackTrace();
            return null;
        }
        return stu;
    }
}


StudentProxy

使用代理模式来设计,管理实现类的数据库连接与关闭,并且又可以以单例模式来设计,这样保证对象仅出现一次

import java.sql.Connection;


public class StudentProxy implements UserDAO
{
    private static StudentProxy instance = null;

    private StudentProxy() {}
    public static StudentProxy getInstance()
    {
        if( instance == null)
            instance = new StudentProxy();
        return instance;
    }

    @Override
    public boolean insert(Object o) throws Exception
    {
        boolean fg = true;
        Connection conn = null;

        try {
            conn = DBConnection.getConnection();

            StudentDAO sdao = new StudentDAO(conn);
            fg = sdao.insert(o);
        }
        catch(Exception e) {
            e.printStackTrace();
            fg = false;
        }
        finally {
            DBConnection.close(conn);
        }
        return fg;
    }

    @Override
    public boolean delete(Object key) throws Exception
    {
        boolean fg = true;
        Connection conn = null;

        try {
            conn = DBConnection.getConnection();

            StudentDAO sdao = new StudentDAO(conn);
            fg = sdao.delete(key);
        }
        catch(Exception e) {
            e.printStackTrace();
            fg = false;
        }
        finally {
            DBConnection.close(conn);
        }
        return fg;
    }

    @Override
    public boolean update(Object key, Object o) throws Exception
    {
        boolean fg = true;
        Connection conn = null;

        try {
            conn = DBConnection.getConnection();

            StudentDAO sdao = new StudentDAO(conn);
            fg = sdao.update(key, o);
        }
        catch(Exception e) {
            e.printStackTrace();
            fg = false;
        }
        finally {
            DBConnection.close(conn);
        }
        return fg;
    }

    @Override
    public Object find(Object key) throws Exception
    {
        Object res = null;
        Connection conn = null;

        try {
            conn = DBConnection.getConnection();

            StudentDAO sdao = new StudentDAO(conn);
            res = sdao.find(key);
        }
        catch(Exception e) {
            e.printStackTrace();
            res = null;
        }
        finally {
            DBConnection.close(conn);
        }
        return res;
    }
}


UserFactory

工厂方法模式来设计,提供产生代理类的对象

public interface UserFactory {
    public UserDAO createProxy();
}


StudentFactory

对于工厂接口的具体实现

public class StudentFactory implements UserFactory
{
    @Override
    public UserDAO createProxy() {
        return StudentProxy.getInstance();
    }
}


  为什么要有代理层?代理层主要负责在原子操作的基础上管理数据源的连接和关闭,而实现原子操作的类只需要关心接口的实现。从而降低了实现类和连接类这两个模块的耦合度。

  以上是仅关于学生表的设计,同时也可以加入教师等PO对象,并实现UserDAO接口。当加入多个UserDAO下的 PO 对象时,就可以体现出使用工厂方法设计的作用。



参考

工厂模式
Java进阶之设计模式
工厂模式,工厂方法模式,抽象工厂模式 详解
DAO设计模式简介

-------------本文结束-------------