工廠模式

此文章對工廠模式一步一步進行了抽取,對對象的創建過程分佈進行抽取,因爲學習java的時間不長,有些地方可能不對,希望讀者能給出評定,留言給我,在正式寫入工廠方法之前有些準備東西,也就是前面的代碼,可以不用看直接看工廠模式:


注:在書寫的時候,有代碼的大小寫錯誤問題,由於時間關係有的未能改過來,希望讀者見諒,哪有不對的地方請留言!
工廠模式:
第一步:創建數據庫:
(1)在開始菜單啓動項的運行中輸入cmd,彈出控制檯的窗口
(2)在窗口中輸入語句mysql -u root -p  然後回車,在正確連接的情況下,會顯示enter password,這時我們輸入密碼,在我的機器上市root,輸入正確後,我們就可以創建數據庫了,例如create database factory; 即創建了名字爲factory的數據庫(可以使用show databases;語句查看本機上的所有數據庫)
(3)創建表,首先確定使用的數據庫(使用語句use factory;),創建數據庫表:
-------以購物模塊的用戶和對應的收貨地址爲例
    1)------創建用戶表
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL auto_increment,
  `userName` varchar(100) default NULL,
  `password` varchar(100) default NULL,
  `nickName` varchar(100) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
    2)創建地址表
DROP TABLE IF EXISTS `t_address`;
CREATE TABLE `t_address` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) default NULL,
  `phone` varchar(100) default NULL,
  `postcode` varchar(100) default NULL,
  `user_id` int(11) default NULL,
  PRIMARY KEY  (`id`),
  KEY `user_address` (`user_id`),
  CONSTRAINT `user_address` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
(4)創建數據庫關係表多對應的實體類:
1)User實體類:
package cn.hsm.model;


import java.util.List;


public class User {
private int id;
private String userName;
private String password;
private String nickName;
private List<Address> addresses;//不需要進行分頁的


public int getId() {
return id;
}


public void setId(int id) {
this.id = id;
}


public String getUserName() {
return userName;
}


public void setUserName(String userName) {
this.userName = userName;
}


public String getPassword() {
return password;
}


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


public String getNickName() {
return nickName;
}


public void setNickName(String nickName) {
this.nickName = nickName;
}


public List<Address> getAddresses() {
return addresses;
}


public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}


}


2)Address實體類
package cn.hsm.model;


public class Address {
private int id;
private String name;
private String phone;
private String postcode;
private User user;


public int getId() {
return id;
}


public void setId(int id) {
this.id = id;
}


public String getName() {
return name;
}


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


public String getPhone() {
return phone;
}


public void setPhone(String phone) {
this.phone = phone;
}


public String getPostcode() {
return postcode;
}


public void setPostcode(String postcode) {
this.postcode = postcode;
}


public User getUser() {
return user;
}


public void setUser(User user) {
this.user = user;
}


}
 (5)使用帶的輔助類:
-------分頁類
package cn.hsm.util;


public class Pager<T> {
long totalRowsAmount; // 總行數,hibernate語句返回的是Long型的數據
int pageSize = 4; // 每頁行數、即顯示的數據個數
int currentPage = 1; // 當前頁碼
int totalPages; // 總頁數
int pageStartRow;// 顯示頁的開始顯示數據行、返回的結果集中的
int pageEndRow;// 顯示頁的末尾顯示數據行、返回的結果集中的,基本用不到


/*
* @return
*/
public long getTotalRowsAmount() {
return totalRowsAmount;
}


/*
* @param t 設置總行數
*/
public void setTotalRowsAmount(long n) {
this.totalRowsAmount = n;
}


public int getPageSize() {
return pageSize;
}


/*
* @param pageSize 設置每頁顯示的數據個數、即行數
*/
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}


public int getCurrentPage() {
return currentPage;
}


/*
* @param currentPage 設置當前頁碼數
*/
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}


/*
* 總頁數的獲取
*/
public int getTotalPages() {
return (int) ((this.getTotalRowsAmount() - 1) / this.getPageSize() + 1);
}


public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}


public int getPageStartRow() {
if (getCurrentPage() * getPageSize() < getTotalRowsAmount()) {
pageStartRow = getPageEndRow() - getPageSize() + 1;
} else {
pageStartRow = (int) (getPageSize() * (getTotalPages() - 1) + 1);
}
return pageStartRow;
}


/*
* @param pageStartRow 設置當前頁顯示的開始數據行數
*/
public void setPageStartRow(int pageStartRow) {
this.pageStartRow = pageStartRow;
}


public int getPageEndRow() {
if (getCurrentPage() * getPageSize() < getTotalRowsAmount()) {
pageEndRow = getCurrentPage() * getPageSize();
} else {
pageEndRow = (int) getTotalRowsAmount();
}
return pageEndRow;
}


/*
* @param pageStartRow 設置當前頁顯示的末尾數據行數
*/
public void setPageEndRow(int pageEndRow) {
this.pageEndRow = pageEndRow;
}


}




---------資源文件輔助類
package cn.hsm.util;


import java.io.IOException;
import java.util.Properties;


public class PropertiesUtil {
private static Properties jdbcProp;//prop是Property的縮寫

public static Properties getJdbcProp() {
try {
if(jdbcProp==null) {
jdbcProp = new Properties();
jdbcProp.load(PropertiesUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"));
}
} catch (IOException e) {
e.printStackTrace();
}
return jdbcProp;
}
}




--------數據庫處理輔助類
package cn.hsm.util;


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;


public class DBUtil {
public static Connection getConnection() {
Properties prop = PropertiesUtil.getJdbcProp();
String username = prop.getProperty("userName");
String password = prop.getProperty("password");
String url = prop.getProperty("url");
Connection conn = null;
try {
conn = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;


// 數據源的使用
/*Connection con = null;
try {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource) envCtx.lookup("jdbc/msg");
con = ds.getConnection();
} catch (NamingException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return con;*/


}


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


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


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


(6)資源文件:jdbc.properties
userName=root
password=root
url=jdbc:mysql://localhost:3306/factory
driver=com.mysql.jdbc.Driver




(7)自定義異常類:ShopException
package cn.hsm.exception;


public class ShopException extends RuntimeException {


private static final long serialVersionUID = 1465191015391787906L;


public ShopException() {
super();
}


public ShopException(String message, Throwable cause) {
super(message, cause);
}


public ShopException(String message) {
super(message);
}


public ShopException(Throwable cause) {
super(cause);
}


}


(8)創建dao層基本類
package cn.hsm.service.base;


import itat.zttc.shop.model.Pager;
import itat.zttc.shop.model.SystemContext;
import itat.zttc.shop.util.MyBatisUtil;


import java.util.HashMap;
import java.util.List;
import java.util.Map;


import org.apache.ibatis.session.SqlSession;


public class BaseDao<T> {
public void add(T obj) {
SqlSession session = null;
try {
session = MyBatisUtil.createSession();
session.insert(obj.getClass().getName()+".add",obj);
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
} finally {
MyBatisUtil.closeSession(session);
}
}

public void update(T obj) {
SqlSession session = null;
try {
session = MyBatisUtil.createSession();
session.update(obj.getClass().getName()+".update", obj);
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
} finally {
MyBatisUtil.closeSession(session);
}
}

public void delete(Class<T> clz,int id) {
SqlSession session = null;
try {
session = MyBatisUtil.createSession();
session.delete(clz.getName()+".delete", id);
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
} finally {
MyBatisUtil.closeSession(session);
}
}

@SuppressWarnings("unchecked")
public T load(Class<T> clz,int id) {
SqlSession session = null;
T t = null;
try {
session = MyBatisUtil.createSession();
t = (T)session.selectOne(clz.getName()+".load",id);
} finally {
MyBatisUtil.closeSession(session);
}
return t;
}

@SuppressWarnings("unchecked")
public T loadBySqlId(String sqlId,Map<String,Object> params) {
SqlSession session = null;
T t = null;
try {
session = MyBatisUtil.createSession();
t = (T)session.selectOne(sqlId,params);
} finally {
MyBatisUtil.closeSession(session);
}
return t;
}

@SuppressWarnings("unchecked")
public T loadBySqlId(String sqlId,Object obj) {
SqlSession session = null;
T t = null;
try {
session = MyBatisUtil.createSession();
t = (T)session.selectOne(sqlId,obj);
} finally {
MyBatisUtil.closeSession(session);
}
return t;
}
//傳值的時候直接傳入例如User.class,傳值時知道使用那個具體類的
public List<T> list(Class<T> clz,Map<String,Object> params) {
return this.list(clz.getName()+".list", params);
}

public List<T> list(String sqlId,Map<String,Object> params) {
List<T> list = null;
SqlSession session = null;
try{
session = MyBatisUtil.createSession();
list = session.selectList(sqlId,params);
} finally {
MyBatisUtil.closeSession(session);
}
return list;
}

public Pager<T> find(Class<T> clz,Map<String,Object> params) {
return this.find(clz.getName()+".find", params);
}

public Pager<T> find(String sqlId,Map<String,Object> params) {

return null;
}

}




(9)創建實體類對應的數據庫操作的dao層()
1)User實體類對應的dao層
package cn.hsm.dao;


import cn.hsm.model.User;
import cn.hsm.util.Pager;


public interface UserDao {
public void add(User user);
public void delete(int id);
public void update(User user);
public User loadByUsername(String username);
public User load(int id);
public Pager<User> find(String name);//真分頁數據
public User login(String username,String password);
}


------User實體類對應dao的實現類
package cn.hsm.dao.impl;
import cn.hsm.util.Pager;;
import cn.hsm.exception.ShopException;
import cn.hsm.model.User;


import java.util.HashMap;
import java.util.Map;


public class UserMybatisDaoImpl extends BaseDao<User> implements UserDao {
@Override
public void add(User user) {
User tu = this.loadByUsername(user.getUsername());
if(tu!=null) 
{throw new ShopException("要添加的用戶已經存在");}
super.add(user);
}


@Override
public void delete(int id) {
//TODO 需要先刪除關聯對象
super.delete(User.class, id);
}


@Override
public void update(User user) {
super.update(user);
}


@Override
public User loadByUsername(String username) {
return super.loadBySqlId(User.class.getName()+".load_by_username", username);
}


@Override
public User load(int id) {
return super.load(User.class, id);
}


@Override
public Pager<User> find(String name) {
Map<String,Object> params = new HashMap<String, Object>();
if(name!=null&&!name.equals(""))
params.put("name", "%"+name+"%");
return super.find(User.class, params);
}


@Override
public User login(String username, String password) {
User u = this.loadByUsername(username);
if(u==null) throw new ShopException("用戶名不存在!");
if(!password.equals(u.getPassword()))
{ throw new ShopException("用戶名密碼不正確");}
return u;
}
}


2)Address實體類對應的dao
package cn.hsm.dao;


import cn.hsm.model.Address;


import java.util.List;


public interface AddressDao {
public void add(Address address,int userId);
public void update(Address address);
public void delete(int id);
public Address load(int id);
public List<Address> list(int userId);
}
----------Address實體類的dao的實現類
package cn.hsm.dao.impl;


import cn.hsm.model.Address;
import cn.hsm.exception.ShopException;
import cn.hsm.model.User;


import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class AddressMybatisDaoImpl extends BaseDao<Address> implements AddressDao {
private IUserDao userDao;
public AddressDao() {
userDao = DAOFactory.getUserDao();
}
@Override
public void add(Address address, int userId) {
User u = userDao.load(userId);
if(u==null) 
  {throw new ShopException("添加地址的用戶不存在");}
address.setUser(u);
super.add(address);
}


@Override
public void update(Address address) {
super.update(address);
}


@Override
public void delete(int id) {
super.delete(Address.class, id);
}


@Override
public List<Address> list(int userId) {
Map<String,Object> params = new HashMap<String, Object>();
params.put("userId", userId);
return super.list(Address.class, params);
}
@Override
public Address load(int id) {
return super.load(Address.class, id);
}


}


(10)工廠模式介紹:
工廠模式是由一個工廠對象決定創建出哪一種產品類的實例,從而讓對象的調用者和對象的創建過程分離,當對象的調用者需要對象時,直接向工廠請求,從而避免了對象的
調用者與對象的實現類以硬編碼方式耦合,以提高系統的可維護性、可拓展性。工廠模式也有一個小小的缺陷:當產品修改時,工廠類也要做相應的修改。

1)靜態工廠方法(簡單工廠方法):多個靜態方法

工廠設計模式的初步設計:增加dao的工廠類DaoFactory
package cn.hsm.dao;


public class DAOFactory {


public static AddressDao getAddressDao() {
return new AddressJDBCDaoImpl();
}


public static UserDao getUserDao() {
return new UserJDBCDaoImpl();
}


}
--------使用某個對象的時候直接調用工廠中的方法,例如DaoFactory.getUserDao,這裏就會調用UserJDBCDaoImpl類的實例對象了


?問題:簡單工廠違背了java的OCP(Open Close Principle)原則,即對增加開放,對修改關閉。所以我們使用多態,針對接口編程來符合OCP原則,這樣就不會對原來的程序進行任何的修改了,而我們增加實現類的時候也不會對原來的程序進行修改。像上面的程序我們每次修改dao都要在工廠當中進行修改違背了 修改Close。


-------綜上,工廠會有dao的工廠,xml文件的工廠,數據庫的工廠,再有多個工廠的時候我們首先確定一個接口DaoFactory


2)多個工廠方法,通過不同的方法創建不同類的對象

工廠模式的進一步優化設計:
-------創建模塊的dao工廠
package cn.hsm.dao;


public interface DaoFactory {
public UserDao createUserDao();
public AddressDao createAddressDao();
}
-------Mysql的dao實現dao 的工廠,因爲使用同一個工廠不需要請求不同的多個工廠對象,所以使用了單例模式
-----單例模式:有些時候,允許自由創建某個類的實例沒有意義,還可能造成系統性能下降。如果一個類始終只能創建一個實例,則這個類被稱爲單例類,這種模式就被稱爲單例模式。
單例模式主要有如下兩個優勢:
減少創建Java實例所帶來的系統開銷
便於系統跟蹤單個Java實例的生命週期、實例狀態等。


package cn.hsm.dao;


public class MysqlDaoFactory implements DaoFactory {
private static DaoFactory factory = new MysqlDaoFactory();
private MysqlDaoFactory() {}

public static DaoFactory getInstance() {
return factory;
}

@Override
public AddressDao createAddressDao() {
return new AddressMysqlDao();
}


@Override
public UserDao createUserDao() {
return new UserMysqlDao();
}
}


---這樣獲取UserDao的實例就可以寫成:
private UserDao  userDao=new MysqlDaoFactory().createUserDao();


------jdbc的實現dao的工廠,單例享元工廠
package cn.hsm.dao;


public class JDBCDaoFactory implements DaoFactory {
private static DaoFactory factory = new JDBCDaoFactory();
/*
* 私有的構造方法防止此類以外的其他類創建該類的實例
*/
private JDBCDaoFactory(){}

public static DaoFactory getInstance() {
return factory;
}


@Override
public UserDao createUserDao() {
return new UserJDBCDao();
}


@Override
public AddressDao createAddressDao() {
return new AddressJDBCDao();
}


}


---這樣獲取UserDao的實例就可以寫成:
private UserDao  userDao=new JDBCDaoFactory().createUserDao();




------我們這裏假設剛開始使用的是mysql的dao處理,但是某天想用Oracle的dao做處理了,那麼我們要想上賣弄那樣在建立一個Oracle的dao實現DaoFactory接口,然後使用
----要先添加UserOracleDao實現UserDao中的方法等等。其他的照做。這樣又出現了一個問題,在由mysql修改到Oracle的到的時候,我們又要通過new來修改,又違背了OCP原則。
3)使用配置文件,通過字符串判斷使用那個工廠

工廠模式進一步優化:(使用配置文件來解決問題)
----使用配置文件的好處;增強代碼的靈活性
添加一個dao.properties資源配置文件:
dao.properties中存放的是鍵值對:
factory=cn.hsm.dao.MysqlDaoFactory
這樣代表使用的是mysql的dao實現
以後如果我們要改用使用Oracle的dao實現的時候,我們只需要將key對應的value值改爲cn.hsm.dao.OracleDaoFactory




--------創建dao的輔助類DaoUtil
-----首先在PropertiesUtil類中添加如下語句用來讀取dao.properties配置文件:
private static Properties daoProp;


public static Properties getDaoProp() {
try {
if(daoProp==null) {
daoProp = new Properties();
daoProp.load(PropertiesUtil.class.getClassLoader().getResourceAsStream("dao.properties"));
}
} catch (IOException e) {
e.printStackTrace();
}
return daoProp;
}


----DaoUtil類


package cn.hsm.util;


import cn.hsm.dao.DaoFactory;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;


public class DaoUtil {
//測試
/*public static void main(String[] args) {
System.out.println(createDaoFactory());
}*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static DaoFactory createDaoFactory() {
DaoFactory f = null;
try {
Properties prop = PropertiesUtil.getDaoProp();
String fs = prop.getProperty("factory");
Class clz = Class.forName(fs);//使用反射只需要讀取字符串就可以創建對象
String mn = "getInstance";//通過單例創建對象的名稱創建對象
Method m = clz.getMethod(mn);//每個工廠獲取對象的方法都約定爲getInstance
f = (DaoFactory)m.invoke(clz);//獲取dao的工廠對象
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return f;
}
}


------此時在創建UserDao的實例對象我們寫成如下的代碼:
private UserDao userDao=DaoUtil.createDaoFactory().createUserDao();
----這樣的話如果我們想修改爲jdbc的dao,那麼只需要在配置文件裏面進行修改,即把factory對應的值改爲cn.hsm.dao.JDBCDaoFactory
想改爲Oracle的dao,那麼只需要在配置文件裏面把factory的值改爲cn.hsm.dao.OracleDaoFactory即可,反射面向的是字符串來創建對象,我們通過讀取配置文件的值就可以使用不同




-----這時候會發現每個創建的實體類的dao 在工廠接口裏面定義的,在工廠類方法中實現的對象的創建還是用使用的new 關鍵字來創建的,做到這裏的時候我們會感覺很彆扭




4)工廠方法,一個工廠方法,通過字符串判斷,創建不同類的對象

工廠模式的再次優化:使用這個版本基本符合了OCP的原則了。
---------DaoFactory類中的代碼改爲一個方法:
package cn.hsm.dao;


public interface DaoFactory {
public Object getDao(String name);//addressDao、userDao等爲參數的值
}


---------將dao.properties配置文件改爲:
dao.properties配置文件中的內容:


factory=cn.hsm.dao.PropertiesFactory
userDao=cn.hsm.dao.UserMysqlDao
address=cn.hsm.AddressMysqlDao




---------具體的工廠類改爲一個:
package cn.hsm.dao;


import cn.hsm.util.PropertiesUtil;


import java.util.HashMap;
import java.util.Map;
import java.util.Properties;


public class PropertiesFactory implements DaoFactory {
private static PropertiesFactory f = new PropertiesFactory();
private static Map<String,Object> daos = new HashMap<String, Object>();
private PropertiesFactory() {}
public static IFactoryDao getInstance() {//單例
return f;
}
@Override
public Object getDao(String name) {//傳遞的參數名爲userDao、addressDao等等
try {
if(daos.containsKey(name)){//判斷是否已經有過這個dao
return daos.get(name);
}
Properties prop = PropertiesUtil.getDaoProp();
String cn = prop.getProperty(name);
Object obj = Class.forName(cn).newInstance();
daos.put(name, obj);//dao不存在的時候,添加到map集合中去
return obj;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}


}
---------這時候獲取實體類dao的語句改爲;
private UserDao userDao =(UserDao) DaoUtil.createDaoFactory().getDao("userDao");//從配置文件中讀取出字符串後創建對象


---------如果想使用Oracle的UserDao的話,那麼只需要將dao.properties配置文件中的userDao改掉就可以了,即:
userDao=cn.hsm.dao.UserOracleDao


對於單例的時候,如果涉及到類中屬性的設置的時候,我們不適用單例,例如實體類,如果使用單例,那麼實體的對象都用一個id了
但是在實體類的dao中,不涉及屬性的設置只涉及方法的調用,那麼對於實體類的dao類,我們可以使用單例:


設計方面的一個方法:
方法1:我們可以把已經有的dao給存起來,下一次再用的時候就在存儲的空間中來拿,這樣就不用在new了,使用Map來存儲,就像上面的程序中就使用了這樣的一種形式。
方法2:既然是單例,那麼就在dao類中正常的寫單例模式就可以了,然後將上面的代碼改成反射使用Method的,但是這種使用起來太麻煩了,尤其是當類特別多的時候。




--------注:在Spring中有使用依賴注入的方法,用起來更加的簡潔,我們只需要聲明類的字段,然後有setter和getter方法就可以直接使用了,Spring會幫我們創建相應的類的實例。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章