多態和簡單對象工廠
Java 的反射技術和多態特性是框架開發、組件解耦的核心,在這方面,Spring 的 IOC 和 DI 為我們提供了一個極好的學習範例,Spring 的 IOC 使用反射技術創建、管理對象,DI 使用多態技術為組件註入依賴對象。
在沒有學習 Spring 之前,簡單的解決方案是使用一個 .properties 文件保存程序中使用的接口、實現類類型鍵值信息,然後在程序中使用一個全局 Properties 對象保存這些信息,並且使用反射技術把這些實現類初始化、提供一個靜態的方法獲取指定接口的實現類對象,在組件中就可以使用依賴對象的鍵獲取需要的對象。
這樣的方案帶來的好處就是:當我們需要修改某個組件的實現方式時,比如把之前 JDBC 的 DAO 實現改為 Hibernate 實現,只要把這些新的實現類放到 classpath 下,把 .properties 文件對應接口的實現類類型改成新的 Hibernate 實現類,而不需要修改依賴組件的代碼。
1、接口和抽象類
共同點:
(1)二者都不能創建對象;
(2)抽象方法要被實現,不能是靜態的,也不能是私有的;
(3)一個類如果沒有實現父類或接口的全部抽象方法,那麽該類只能為抽象類
區別:
(1)接口可以繼承接口,而且可以多繼承,但是不能實現;類只能繼承一個父類或抽象類,但可以實現多個接口;
(2)抽象類可以有構造方法,接口不能有構造方法;
(3)接口都是抽象方法;抽象類可以沒有抽象方法,而且一個類如果有抽象方法,那麽這個類就必須是抽象類;
(4)接口只能定義公共靜態的常量;抽象類中可以定義普通變量;
(5)接口更適合做子系統、組件、模塊的技術、功能規範;抽象類更適合做通用業務邏輯的抽象和封裝。比如:DAO、Service 層的核心功能就需要使用接口來規範;而DAO層實現類都具有的一些通用邏輯就可以放在一個抽象類中,然後讓具體的DAO實現類來繼承這個抽象類。
2、方法的重寫和重載
方法的重寫 Override 和重載 Overload 都是 Java 多態的不同表現。重寫 Override 是父類與子類之間多態性的一種表現,重載 Overload 是一個類中多態性的一種表現。
重寫:也叫做覆蓋。子類中的方法與父類中的某一方法具有相同的方法名、返回值類型和參數列表,則該方法將覆蓋父類方法。有幾點需要註意:
(1)子類方法不能拋出比父類方法更多或“更大”的異常;
(2)子類方法的訪問權限級別不能比父類方法更嚴格;
(3)一定要有相同的返回值類型;
(4)參數列表需要相同,如果不同,那麽這個方法相當於重載,而不是重寫
重載:一個類中定義了多個同名的方法,它們的參數個數或參數類型不同,這種情況就是方法重載。但是和返回值的類型無關。
3、示例代碼概述
目錄結構如下:
4、DAO接口和默認實現
UserDao接口
1 public interface UserDao { 2 3 public void add(); 4 5 public void del(); 6 7 public void update(); 8 9 public void get(); 10 }
UserDaoImpl 實現類
1 public class UserDaoImpl implements UserDao { 2 3 public void add() { 4 System.out.println("使用的是demo.dao.impl.UserDaoImpl的添加方法"); 5 } 6 7 public void del() { 8 9 System.out.println("使用的是demo.dao.impl.UserDaoImpl的刪除方法"); 10 } 11 12 public void update() { 13 14 System.out.println("使用的是demo.dao.impl.UserDaoImpl的更新方法"); 15 } 16 17 public void get() { 18 19 System.out.println("使用的是demo.dao.impl.UserDaoImpl的獲取方法"); 20 } 21 }
5、Service接口和默認實現
UserService 接口
1 public interface UserService { 2 3 public void addUser(); 4 5 public void delUser(); 6 7 public void updateUser(); 8 9 public void getUser(); 10 }
UserServiceImpl 實現類,註意在這裏需要使用對象工廠註入依賴的 DAO 對象
1 public class UserServiceImpl implements UserService { 2 3 private UserDao userDao; 4 5 public UserServiceImpl() { 6 super(); 7 this.userDao = (UserDao) ObjectFactory.getObject("userDao"); 8 } 9 10 public void addUser() { 11 this.userDao.add(); 12 System.out.println("使用的是demo.service.impl.UserServiceImpl的addUser方法"); 13 } 14 15 public void delUser() { 16 this.userDao.del(); 17 System.out.println("使用的是demo.service.impl.UserServiceImpl的delUser方法"); 18 } 19 20 public void updateUser() { 21 this.userDao.update(); 22 System.out 23 .println("使用的是demo.service.impl.UserServiceImpl的updateUser方法"); 24 } 25 26 public void getUser() { 27 this.userDao.get(); 28 System.out.println("使用的是demo.service.impl.UserServiceImpl的getUser方法"); 29 } 30 }
6、ObjectFactory 類和 objects.properties
首先,看一下 objects.properties 配置文件,主要配置程序使用的接口實現類類型信息
1 userDao=demo.dao.impl.UserDaoImpl 2 userService=demo.service.impl.UserServiceImpl
我們需要寫個 ObjectFactory 類讀取配置並實例化對象,然後保存到全局“工廠”
1 public final class ObjectFactory { 2 3 /** 4 * 保存接口的鍵(通常使用接口的簡單類名首字母小寫)和實現類對象信息<br/> 5 * 6 * 使用者可以調用getObject方法傳入配置文件中key獲取對應實現類對象<br/> 7 */ 8 private static Map<String, Object> objectMap = new HashMap<String, Object>(); 9 10 /** 11 * 用於加載配置文件 12 */ 13 private static Properties prop = new Properties(); 14 15 static { 16 try { 17 // 讀取配置文件 18 prop.load(ObjectFactory.class.getClassLoader().getResourceAsStream("objects.properties")); 19 // 叠代properties,對實現類逐一進行實例化 20 // 然後把實現類對象保存到objectMap中 21 for(Object k : prop.keySet()) { 22 String key = k.toString(); 23 // 獲取值,即實現類的權限定名 24 String className = prop.get(key).toString(); 25 // 加載類並實例化 26 Class<?> cls = Class.forName(className); 27 Object obj = cls.newInstance(); 28 // 把實現類對象保存到objectMap 29 objectMap.put(key, obj); 30 } 31 } catch (IOException e) { 32 e.printStackTrace(); 33 throw new RuntimeException("加載全局接口實現類配置文件失敗", e); 34 } catch (ClassNotFoundException e) { 35 e.printStackTrace(); 36 throw new RuntimeException("有實現類未找到", e); 37 } catch (InstantiationException e) { 38 e.printStackTrace(); 39 throw new RuntimeException("類實例化出錯", e); 40 } catch (IllegalAccessException e) { 41 e.printStackTrace(); 42 throw new RuntimeException("類訪問出錯", e); 43 } 44 } 45 46 /** 47 * 根據指定接口的鍵(通常使用接口的簡單類名首字母小寫)獲取對應配置的實現類對象 48 */ 49 public static Object getObject(String key) { 50 return objectMap.get(key); 51 } 52 }View Code
7、Servlet 和 JSP
DecoupleServlet 類,需要使用對象工廠獲取 Service 對象
1 public class DecoupleServlet extends HttpServlet { 2 3 private static final long serialVersionUID = 1L; 4 5 public DecoupleServlet() { 6 super(); 7 } 8 9 protected void doGet(HttpServletRequest request, 10 HttpServletResponse response) throws ServletException, IOException { 11 doPost(request, response); 12 } 13 14 protected void doPost(HttpServletRequest request, 15 HttpServletResponse response) throws ServletException, IOException { 16 17 request.setCharacterEncoding("UTF-8"); 18 response.setContentType("text/html; charset=utf-8"); 19 20 UserService userService = (UserService) ObjectFactory 21 .getObject("userService"); 22 23 userService.addUser(); 24 25 request.setAttribute("service", userService.getClass().getName()); 26 27 request.getRequestDispatcher("demo.jsp").forward(request, response); 28 } 29 }
index.jsp 和 demo.jsp 省略。
啟動項目後訪問 http://localhost:8080/demo/
點擊頁面鏈接
從頁面和控制臺輸出,可以看出程序確實使用了默認的 DAO 和 Service
8、修改 DAO 實現類配置
首先,編寫一個新的 DAO 實現類 UserHibernateDao
1 public class UserHibernateDao implements UserDao { 2 3 public void add() { 4 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的添加方法"); 5 } 6 7 public void del() { 8 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的刪除方法"); 9 } 10 11 public void update() { 12 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的更新方法"); 13 } 14 15 public void get() { 16 System.out.println("使用的是demo.dao.hibernate.UserHibernateDao的獲取方法"); 17 } 18 }
修改配置
1 # userDao=demo.dao.impl.UserDaoImpl 2 userDao=demo.dao.hibernate.UserHibernateDao 3 userService=demo.service.impl.UserServiceImpl
重啟項目,重新訪問
可以看到控制臺輸出,已經成功地使用了新的 DAO 實現類。
多態和簡單對象工廠